I am playing with lambda expressions and I am using auto as input parameter.
I did try this code below
auto f2 = [](auto a){ return a;};
std::cout << f2(10) << std::endl;
std::cout << f2("hi there!") << std::endl;
With my big surprise it compiles and run ok! How is that possible?
If I am not wrong (this comes with the C++14) the operator() of the function object is template since it uses auto as input parameter.
How does it manage multiple return types? First line returns an int and the second line returns const char*.
Is the compiler creating multiple operator() behind the scenes?
As you say, the operator() of generic lambda is effectively a function template. Something similar to:
struct noname
{
template<typename T>
auto operator ()(T a) const { return a; }
};
Your separate calls instantiate two different functions.
auto operator ()(int a) const { return a; }
auto operator ()(const char* a) const { return a; }
From there, the rules of auto return type apply. In the first function, the first and only return statement returns an int, therefore int is deduced as the return type. Same for const char*
Related
I have a struct with a method called call which has a const overload. The one and only argument is a std::function which either takes a int reference or a const int reference, depending on the overload.
The genericCall method does exactly the same thing but uses a template parameter instead of a std::function as type.
struct SomeStruct {
int someMember = 666;
void call(std::function<void(int&)> f) & {
f(someMember);
std::cout << "call: non const\n";
}
void call(std::function<void(const int&)> f) const& {
f(someMember);
std::cout << "call: const\n";
}
template <typename Functor>
void genericCall(Functor f) & {
f(someMember);
std::cout << "genericCall: non const\n";
}
template <typename Functor>
void genericCall(Functor f) const& {
f(someMember);
std::cout << "genericCall: const\n";
}
};
When I now create this struct and call call with a lambda and auto & as argument the std::function always deduces a const int & despite the object not being const.
The genericCall on the other hand deduces the argument correctly as int & inside the lamdba.
SomeStruct some;
some.call([](auto& i) {
i++; // ?? why does auto deduce it as const int & ??
});
some.genericCall([](auto& i) {
i++; // auto deduces it correctly as int &
});
I have no the slightest clue why auto behaves in those two cases differently or why std::function seems to prefer to make the argument const here. This causes a compile error despite the correct method is called. When I change the argument from auto & to int & everything works fine again.
some.call([](int& i) {
i++;
});
When I do the same call with a const version of the struct everything is deduced as expected. Both call and genericCall deduce a const int & here.
const SomeStruct constSome;
constSome.call([](auto& i) {
// auto deduces correctly const int & and therefore it should
// not compile
i++;
});
constSome.genericCall([](auto& i) {
// auto deduces correctly const int & and therefore it should
// not compile
i++;
});
If someone could shine some light on this I would be very grateful!
For the more curious ones who want to dive even deeper, this problem arose in the pull request: https://github.com/eclipse-iceoryx/iceoryx/pull/1324 while implementing a functional interface for an expected implementation.
The issue is that it's a hard error to try to determine whether your lambda is Callable with const int & returning void, which is needed to determine whether you can construct a std::function<void(const int&)>.
You need to instantiate the body of the lambda to determine the return type. That's not in the immediate context of substituting a template argument, so it's not SFINAE.
Here's an equivalent error instantiating a trait.
As #aschepler notes in the comments, specifying a return type removes the need to instantiate the body of your lambda.
The problem is that generic lambdas (auto param) are equivalent to a callable object whose operator() is templated. This means that the actual type of the lambda argument is not contained in the lambda, and only deduced when the lambda is invoked.
However in your case, by having specific std::function arguments, you force a conversion to a concrete type before the lambda is invoked, so there is no way to deduce the auto type from anything. There is no SFINAE in a non-template context.
With no specific argument type, both your call are valid overloads. Actually any std::function that can match an [](auto&) is valid. Now the only rule is probably that the most cv-qualified overload wins. You can try with a volatile float& and you will see it will still choose that. Once it choose this overload, the compilation will fail when trying to invoke.
I have a namespace with several structs and enum classes inside of it. For each type, I have a toString() method. Here is a small example:
namespace test {
struct A {
int i;
};
struct B {
float j;
};
std::string toString(const A &a){
return to_string(a.i);
}
std::string toString(const B &b){
return to_string(b.j);
}
}
I want to provide a templated operator<< which captures only these types, but not for types outside of this namespace:
template<class T>
std::ostream & operator<<(std::ostream &out, const T &t){
out << toString(t);
return out;
}
However, this gives me the following compilation error:
error: ambiguous overload for 'operator<<' (operand types are 'std::stringstream {aka std::__cxx11:basic_stringstream<char>}' and 'const char*')
How can I write a templated operator overload for this?
I solved it using concept & requires of C++20 (gcc >= 10.1):
template <typename T>
concept HaveToString = requires (T t) {
{ toString(t) };
};
template<HaveToString T>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
int main() {
test::A a;
std::cout << a << std::endl;
return EXIT_SUCCESS;
}
EDIT
For C++11:
template<typename T, typename = decltype(toString(std::declval<T>()))>
std::ostream & operator<<(std::ostream &out, const T& t){
out << toString(t);
return out;
}
Or as #MooingDuck mentioned in the comments:
template<typename T>
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) {
out << toString(t);
return out;
}
Explanations
First of all, a really good article about unevaluated operands. It will help to understand what is going on in the expressions: decltype(toString(std::declval<T>())) and decltype(out<<toString(t)) which are both basically doing the same thing-> Setting a rule that any call to this function, have to support the call to toString function with the T parameter type.
First Approach
decltype(toString(std::declval<T>()))
Let's split this complex expression into sub expressions, from the inside out:
decltype(toString( std::declval<T>() ))
std::declval<T>() In some very simple words - means that we are "assuming" we created a variable of the type T at a compile time (If you didn't read the article yet, now it's a really good time to do so). The important thing to know before continue- we didn't do it, the important word is assuming.
decltype( toString(std::declval<T>()) )
The magic continue all the way up to decltype which checking for the type of the unevaluated expression within it. So, if toString that calls T type variable exists, it will return the value that toString function returns. If this function doesn't exist, a compile time error will be thrown (or in this context, the compiler won't deduce this function for the given type).
typename = decltype(toString(std::declval<T>()))
This section in the template meant to enable this function whenever the type returning from decltype is legal.
#MooingDuck Approach
auto operator<<(std::ostream &out, const T& t) -> decltype(out<<toString(t)) { /*...*/ }
Return value: auto
C++11: Deduced by the expression the after the operator ->.
After C++14: Calculated at compile time by the return expression inside the function (if there is no return expression, the return value deduced at compile time to void).
-> decltype(out<<toString(t))
Define the return value type.
decltype(out<<toString(t))
As explained before, whatever comes inside decltype is unevaluated expression. The compiler won't evaluate this expression, but it will make sure that the expression can be evaluated at runtime (or else an exception will be thrown, or in this case, the compiler won't deduce this function), and it will return the type of the returned value from this expression.
I have the code below and why visitor1 and visitor2 gives errors?
Does that mean the visitor cannot return one type within the variant?
#include <iostream>
#include <variant>
struct Visitor1
{
template <class T>
T operator()(const T & t) const
{
return (t);
}
};
struct Visitor2
{
int operator()(const int & t) const
{
return std::get<int>(t);
}
char operator()(const char & t) const
{
return std::get<char>(t);
}
};
struct Visitor3
{
void operator()(const int & t) const
{
std::cout<<t;
}
void operator()(const char & t) const
{
std::cout<<t;
}
};
int main()
{
std::variant<int, char> v{char(100)};
std::visit(Visitor3{}, v);
auto t = std::visit(Visitor2{}, v); //fails
//auto t = std::visit(Visitor1{}, v); //fails
std::cout << t;
}
I know I can use std::get(), but the issue is I can only use auto with std::get(), if I do something like below, the x is not accessible outside of the if/else scope:
bool b;
Variant v;
if (b)
{
auto x = std::get<int>(v);
}
else
{
auto x = std::get<char>(v);
}
// I want to do something with x here out of if/else
A language could exist with many features of C++ that does what you want.
In order to do what you want, when you call std::visit, N different implementations of the rest of the function would have to be written.
In each of those N different implementations (2 in your case), the type of a variable would be different.
C++ doesn't work that way.
The only part of code that is "multiplied" by the visit call is the visitor.
int main()
{
std::variant<int, char> v{char(100)};
std::visit([&](auto && t){
std::cout << t;
}, v);
}
I put the rest of the body of the function within the visitor. That code is instantiated once for every type that can be stored within the visitor.
Anything that returns from the visit goes back to the "single instance" body of the calling scope.
Basically, [&](auto&& t) lambdas do what you seem to want.
Now, we can do some tricks to change the syntax a bit.
My favorite is:
v->*visit*[&](auto&& val) {
std::cout << val;
return [val](auto&& x) { x << val; };
}->*visit*[&](auto&& outputter) {
outputer(std::cout);
};
where ->*visit* uses a relatively ridiculous amount of metaprogramming to allow
Named operators to cause visiting,
Fusing the return values of the visits into a variant.
but no sane person would write that code.
I have the code below and why visitor1 and visitor2 gives errors?
Because C++ is a strongly typed language.
When you write
auto t = std::visit(Visitor2{}, v); //fails
the compiler must decide compile-time which type is t, so must decide which type return std::visit(Visitor2{}, v).
If Visitor2 return a char, when v contains a char, or a int, when v contain a int, the compiler can't choose (compile-time!) the type returned from std::visit() [there is also the problem (Visitor2 only) that t, inside operator()'s, is a int or a char, so you can't apply std::get() to it].
Same problem with Visitor1: the template operator() return the template type so int or char for a std::variant<int, char>.
Visitor3 works because both operator() return void, so the compiler can resolve (compile-time) that std::visit(Visitor3{}, v) return (in a sense) void.
Maybe is better explained in this page:
[std::visit()] Effectively returns
std::invoke(std::forward<Visitor>(vis), std::get<is>(std::forward<Variants>(vars))...)
, where is... is vars.index().... The return type is deduced from the returned expression as if by decltype.
The call is ill-formed if the invocation above is not a valid expression of the same type and value category, for all combinations of alternative types of all variants.
You can do
bool b;
Variant v;
std_optional<char> x_char;
std_optional<int> x_int;
if (b)
{
x_int = std::get<int>(v);
}
else
{
x_char = std::get<char>(v);
}
I did read that C++14 generic lambdas with auto parameters are actually templates, so the following is valid C++14
auto glambda = [] (auto a) { return a; };
cout << glambda(23); // Prints 23
cout << glambda('A'); // Prints A
This doesn't quite stack up with what I know from templates.. where's the instantiation point? What is it stored in the glambda variable if the first call instantiates a template with int and the second one with char?
It's not that the "lambda is a template" -- that doesn't make sense, a lambda is an expression. Rather, the type of the closure object that's defined by the lambda expression has an overloaded function call operator that is defined by a member function template. So the instantiation point is the first use of the respective call operator.
In other words, a lambda [a, &b](auto x, auto y) -> R { /* ... */ } has a type like:
struct __lambda
{
__lambda(const A & __a, B & __b) : a(__a), b(__b) {}
template <typename T1, typename T2>
R operator()(T1 x, T2 y) const { /* ... */ }
private:
A a;
B & b;
};
A generic lambda is an object of a compiler generated type that has a template method called operator(). Your code can be rewritten with this equivalent:
struct MyLambda {
template<typename T>
T operator() (T val) {
return val;
}
};
int main() {
MyLambda func;
std::cout << func('A');
std::cout << func(42);
}
The compiler will instantiate operator() when needed.
Hope it helped
I'd like to find the max Foo and call inc() on it, which is a non-const method. Of course in finding the max, I don't want to create any copies or moves, i.e. I don't want Foo foo = std::max(foo1, foo2). I tried writing my own max, and g++ insists I return a const&.
#include <iostream>
class Foo
{
public:
Foo(int x) : x_(x) { std::cout << "const" << std::endl; }
Foo(const Foo& foo) : x_(foo.x_) { std::cout << "copy const" << std::endl; }
Foo(Foo&& foo) : x_(foo.x_) { std::cout << "move const" << std::endl; }
bool operator< (const Foo& foo) const { return x_ < foo.x_; }
bool operator> (const Foo& foo) const { return x_ > foo.x_; }
void inc() { ++x_; }
int x_;
};
/*
* Doesn't compile. Must return const T& or must accept non-const T&
*
template<typename T>
inline T& my_max(const T& f1, const T& f2)
{
return f1 > f2 ? f1 : f2;
}
*
*/
int main()
{
Foo foo1(6);
Foo foo2(7);
Foo& foo = std::max(foo1, foo2); //Doesn't compile. Must be const Foo&. But then next line fails
foo.inc();
std::cout << foo.x_ << std::endl;
return 0;
}
You have 2 issues here:
Missing const qualifier in result
It is dangerous to return reference to const reference parameter
In such case:
Foo& foo = std::max(Foo(6), Foo(7));
compiler will construct temporary objects for parameters before function call and will destroy them after function call - so you will end up with reference to garbage. Of course if you will always use existing objects it will work - but it is easy to forget about such limitations.
You could remove const from parameters which will resolve both issues and it should be ok for you as you intend to modify object anyway.
template<typename T>
T my_max(T&& f1, T&& f2) {
return std::forward<T>(f1 > f2 ? f1 : f2);
}
the above is relatively solid, and will do what you need. It does require the two parameters to have the same r/l/const ness, which std::max does not. Which is why max uses const&.
A far more complex version that finds the common reference category can be written, but it can act in surprising ways.
So not be fooled by lack of & in return value above: in your use case, the above returns a reference. If passed rvalues it returns a value.
Here is an attempt at a super_max that, if passed lvalues of the same type, returns an lvalue. If passed two different types, or an rvalue, returns a copy:
template<class A, class B>
struct max_return:std::common_type<A,B>{};
template<class A>
struct max_return<A&,A&>{
using type=A&;
};
template<class A, class B>
using max_return_t = typename max_return<A,B>::type;
template<class T, class U>
max_return_t<T,U> super_max(T&& t, U&& u) {
if (t < u)
return std::forward<U>(u);
else
return std::forward<T>(t);
}
it also only uses <, and prefers the left hand side on a tie.
live example