If I assign a lambda in C++ with this:
auto&& mylambda = [&](int someparam)
{
some_function();
return 42;
};
Is this wrong? I know that this triggers auto type deduction, but is the lambda (which I think of it as an instantiated anonymous struct with an overload of operator()
) in this case an rvalue and mylambda
is an rvalue reference?
Can this lambda be safely passed to another function which accepts it like this?
template <class Callable>
void ThisAcceptsALambda(Callable f)
{
Callable *addr = &f;
store(addr);
do_something_with_the_stored_addr_and_call_the_lambda();
}
I still don't understand the difference between using auto
and auto&&
in this case.
If you don't know who Scott Meyers is (hard to believe it's been about 10 years since he retired from C++), he was once one of the most influential people in the field (he even humbly ranked himself as #3 most influential of all time - see The Most Important C++ People... Ever). I strongly suggest you get a copy of his book "Effective Modern C++ - 42 Specific Ways to Improve Your Use of C++11 and C++14" (there are likely "free" copies of it floating around no doubt, but it's your decision whether to download one or not - I don't condone it personally). Yes, it only targets up to C++14 (at the time he retired), but it's still highly relevant. "Item 1: Understand template type deduction" and "Item 2: Understand auto type deduction" in particular are a must-read for all C++ developers (among some other items in the book). It's one of the few places on the web where you can actually find a really good (practical) explanation of the rules for type deduction that cuts through all the legalese of the standard itself (and by someone you can definitely trust). It answers your question as well (in the "Item 2" section, but you need to read "Item 1" first).
For your particular code though, I've removed your call to some_function
for brevity (also, read on for a link to my free header-only library which has a template you can use to display the user-friendly name of any type for learning purposes, with an example for your own code):
auto&& mylambda1 = [&](int someparam)
{
return 42;
};
This creates an rvalue reference to the closure type for your lambda (the compiler-generated class for the lambda's functor), as would likely be expected by most (but Scott's book explains why). Its type is therefore this (where MyLambdaClass
is whatever class the compiler creates for your lambda behind the scenes - what you would get by applying decltype
to your lambda):
MyLambdaClass &&
Note that because it's named (mylambda1
), it's still an "lvalue" (yes, it's very confusing), but you'll need to read up on value categories to understand the details. A simple rule of thumb to remember, however, that usually works in C++ is that if you can take the address of something, in this case you can, i.e., &mylambda1
, then it's normally an lvalue, and this applies even when the named object's own type is an rvalue reference, like the above.
This, however (&&
now removed):
auto mylambda2 = [&](int someparam)
{
return 42;
};
Results in the following type instead, as would be expected by most (and should be used in most cases, for what you're doing - the use of your auto &&
serves no purpose in your situation, since it merely binds your temporary lambda to the rvalue reference you're creating, which keeps the temporary lambda alive as long as the reference itself remains alive, but it's pointless in this case and only confuses things - you should read up on rvalue references and forward referencing to acquire an understanding):
MyLambdaClass
And this (const
and &
applied):
const auto &mylambda3 = [&](int someparam)
{
return 42;
};
Would result in this, as would be expected by most:
const MyLambdaClass &
But this (&
applied but no longer const
, like the example just above):
auto &mylambda4 = [&](int someparam)
{
return 42;
};
Would fail compilation because the type would be this:
MyLambdaClass &
And since you're trying to assign a temporary to it (the lambda you're creating on-the-fly, which is an rvalue, a "prvalue" to be specific), it's not legal since you can't bind an rvalue to a non-const lvalue reference (yes, all these rules can cause brain damage).
The rules for all this are nevertheless explained in Scott's book, so I won't repeat the details here (too much to get into). Just read the first two items in particular and you'll fully understand the details (though it takes some practice, of course, to instantly apply them when you look at a given piece of code).
Lastly, if you wish to experiment, you can use the TypeName_v
helper from my FunctionTraits library (click its triangle at the top of the 1st link to expand it) to display the user-friendly name of any C++ type (which includes the &
or &&
if present), so it can be very helpful when you're trying to see the actual type of something (here's your own example again). Note, however, that because TypeName_v
needs to rely on some undocumented though well-known techniques to pull this off (since there is no other way until reflection is available in C++26), for lambdas you'll actually get a weird name for its closure type (as described by TypeName_v
). Clang, for instance, actually uses the source file and line/column number for the name, like "(lambda at /app/example.cpp:18323:32)". Non-lambda types, however, normally result in WYSIWYG names (though MSVC may also attach the word class
or struct
to it, depending on the context). It's therefore easier to experiment with non-lambda types when learning the rules in Scott's book (lambdas are more verbose, of course, so they only complicate the learning process IMHO, though a type is a type, including lambda types - it's the presence of &
and &&
that are normally confusing, so any type can be used to learn the rules - you therefore might as well choose something simpler than a lambda until you know the rules).
That was much longer than expected when I started, but I hope it helps (worth the effort though, since this is an important but esoteric and very confusing area of the language, and even experienced developers like myself can sometimes get tripped up).