C++ Lambda Code Generation with Init Captures in C++ 14 - c++

I am trying to understand / clarify the code code that is generated when captures are passed to lambdas especially in generalized init captures added in C++14.
Give the following code samples listed below this is my current understanding of what the compiler will generate.
Case 1: capture by value / default capture by value
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Would equate to:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int x) : __x{x}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
So there are multiple copies, one to copy into the constructor parameter and one to copy into the member, which would be expensive for types like vector etc.
Case 2: capture by reference / default capture by reference
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Would equate to:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int& x) : x_{x}{}
void operator()() const { std::cout << x << std::endl;}
private:
int& x_;
};
The parameter is a reference and the member is a reference so no copies. Nice for types like vector etc.
Case 3:
Generalised init capture
auto lambda = [x = 33]() { std::cout << x << std::endl; };
My under standing is this is similar to Case 1 in the sense
that it is copied into to the member.
My guess is that the compiler generates code similar to...
class __some_compiler_generated_name {
public:
__some_compiler_generated_name() : __x{33}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
Also if I have the following:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
What would the constructor look like? Does it also move it into the member?

Case 1 [x](){}: The generated constructor will accept its argument by possibly const-qualified reference to avoid unnecessary copies:
__some_compiler_generated_name(const int& x) : x_{x}{}
Case 2 [x&](){}: Your assumptions here are correct, x is passed and stored by reference.
Case 3 [x = 33](){}: Again correct, x is initialized by value.
Case 4 [p = std::move(unique_ptr_var)]: The constructor will look like this:
__some_compiler_generated_name(std::unique_ptr<SomeType>&& x) :
x_{std::move(x)}{}
so yes, the unique_ptr_var is "moved into" the closure. See also Scott Meyer's Item 32 in Effective Modern C++ ("Use init capture to move objects into closures").

There's less of a need to speculate, using cppinsights.io.
Case 1:
Code
#include <memory>
int main() {
int x = 33;
auto lambda = [x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 2:
Code
#include <iostream>
#include <memory>
int main() {
int x = 33;
auto lambda = [&x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int & x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int & _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 3:
Code
#include <iostream>
int main() {
auto lambda = [x = 33]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
class __lambda_4_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_4_16(const __lambda_4_16 &) = default;
// inline /*constexpr */ __lambda_4_16(__lambda_4_16 &&) noexcept = default;
public: __lambda_4_16(int _x)
: x{_x}
{}
};
__lambda_4_16 lambda = __lambda_4_16(__lambda_4_16{33});
}
Case 4 (unofficially):
Code
#include <iostream>
#include <memory>
int main() {
auto x = std::make_unique<int>(33);
auto lambda = [x = std::move(x)]() { std::cout << *x << std::endl; };
}
Compiler generates
// EDITED output to minimize horizontal scrolling
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int, std::default_delete<int> > x =
std::unique_ptr<int, std::default_delete<int> >(std::make_unique<int>(33));
class __lambda_6_16
{
std::unique_ptr<int, std::default_delete<int> > x;
public:
inline void operator()() const
{
std::cout.operator<<(x.operator*()).operator<<(std::endl);
}
// inline __lambda_6_16(const __lambda_6_16 &) = delete;
// inline __lambda_6_16(__lambda_6_16 &&) noexcept = default;
public: __lambda_6_16(std::unique_ptr<int, std::default_delete<int> > _x)
: x{_x}
{}
};
__lambda_6_16 lambda = __lambda_6_16(__lambda_6_16{std::unique_ptr<int,
std::default_delete<int> >
(std::move(x))});
}
And I believe this last piece of code answers your question. A move occurs, but not [technically] in the constructor.
Captures themselves aren't const, but you can see that the operator() function is. Naturally, if you need to modify the captures, you mark the lambda as mutable.

This question cannot be fully answered in code. You might be able to write somewhat "equivalent" code, but the standard is not specified that way.
With that out of the way, let's dive into [expr.prim.lambda]. The first thing to note is that constructors are only mentioned in [expr.prim.lambda.closure]/13:
The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]). [ Note: These special member functions are implicitly defined as usual, and might therefore be defined as deleted. — end note ]
So right off the bat, it should be clear that constructors are not formally how capturing objects is defined. You can get pretty close (see the cppinsights.io answer), but the details differ (note how the code in that answer for case 4 does not compile).
These are the main standard clauses needed to discuss case 1:
[expr.prim.lambda.capture]/10
[...]
For each entity captured by copy, an unnamed non-static data member is declared in the closure type.
The declaration order of these members is unspecified.
The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.
A member of an anonymous union shall not be captured by copy.
[expr.prim.lambda.capture]/11
Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [...]
[expr.prim.lambda.capture]/15
When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object, and the non-static data members corresponding to the init-captures are initialized as indicated by the corresponding initializer (which may be copy- or direct-initialization). [...]
Let's apply this to your case 1:
Case 1: capture by value / default capture by value
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
The closure type of this lambda will have an unnamed non-static data member (let's call it __x) of type int (since x is neither a reference nor a function), and accesses to x within the lambda body are transformed to accesses to __x. When we evaluate the lambda expression (i.e. when assigning to lambda), we direct-initialize __x with x.
In short, only one copy takes place. The constructor of the closure type is not involved, and it is not possible to express this in "normal" C++ (note that the closure type is not an aggregate type either).
Reference capture involve [expr.prim.lambda.capture]/12:
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. [...]
There is another paragraph about reference capture of references but we're not doing that anywhere.
So, for case 2:
Case 2: capture by reference / default capture by reference
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
We don't know whether a member is added to the closure type. x in the lambda body might just directly refer to the x outside. This is up to the compiler to figure out, and it will do this in some form of intermediate language (which differs from compiler to compiler), not a source transformation of the C++ code.
Init captures are detailed in [expr.prim.lambda.capture]/6:
An init-capture behaves as if it declares and explicitly captures a variable of the form auto init-capture ; whose declarative region is the lambda-expression's compound-statement, except that:
(6.1)
if the capture is by copy (see below), the non-static data member declared for the capture and the variable are treated as two different ways of referring to the same object, which has the lifetime of the non-static data member, and no additional copy and destruction is performed, and
(6.2)
if the capture is by reference, the variable's lifetime ends when the closure object's lifetime ends.
Given that, let's look at case 3:
Case 3: Generalised init capture
auto lambda = [x = 33]() { std::cout << x << std::endl; };
As stated, imagine this as a variable being created by auto x = 33; and explicitly captured by copy. This variable is only "visible" within the lambda body. As noted in [expr.prim.lambda.capture]/15 earlier, the initialization of the corresponding member of the closure type (__x for posterity) is by the given initializer upon evaluation of the lambda expression.
For the avoidance of doubt: This does not mean things are initialized twice here. The auto x = 33; is an "as if" to inherit the semantics of simple captures, and the described initialization is a modification to those semantics. Only one initialization happens.
This also covers case 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
The closure type member is initialized by __p = std::move(unique_ptr_var) when the lambda expression is evaluated (i.e. when l is assigned to). Accesses to p in the lambda body are transformed into accesses to __p.
TL;DR: Only the minimal number of copies/initializations/moves are performed (as one would hope/expect). I would assume that lambdas are not specified in terms of a source transformation (unlike other syntactic sugar) exactly because expressing things in terms of constructors would necessitate superfluous operations.
I hope this settles the fears expressed in the question :)

Related

C++ confusing closure captures [v] vs [v = v]

In the following code, it seems that the compiler sometimes prefer to call the templated constructor and fails to compile when a copy constructor should be just fine. The behavior seems to change depending on whether the value is captured as [v] or [v = v], I thought those should be exactly the same thing. What am I missing?
I'm using gcc 11.2.0 and compiling it with "g++ file.cpp -std=C++17"
#include <functional>
#include <iostream>
#include <string>
using namespace std;
template <class T>
struct record {
explicit record(const T& v) : value(v) {}
record(const record& other) = default;
record(record&& other) = default;
template <class U>
record(U&& v) : value(forward<U>(v)) {} // Removing out this constructor fixes print1
string value;
};
void call(const std::function<void()>& func) { func(); }
void print1(const record<string>& v) {
call([v]() { cout << v.value << endl; }); // This does not compile, why?
}
void print2(const record<string>& v) {
call([v = v]() { cout << v.value << endl; }); // this compiles fine
}
int main() {
record<string> v("yo");
print1(v);
return 0;
}
I don't disagree with 康桓瑋's answer, but I found it a little hard to follow, so let me explain it with a different example. Consider the following program:
#include <functional>
#include <iostream>
#include <typeinfo>
#include <type_traits>
struct tracer {
tracer() { std::cout << "default constructed\n"; }
tracer(const tracer &) { std::cout << "copy constructed\n"; }
tracer(tracer &&) { std::cout << "move constructed\n"; }
template<typename T> tracer(T &&t) {
if constexpr (std::is_same_v<T, const tracer>)
std::cout << "template constructed (const rvalue)\n";
else if constexpr (std::is_same_v<T, tracer&>)
std::cout << "template constructed (lvalue)\n";
else
std::cout << "template constructed (other ["
<< typeid(T).name() << "])\n";
}
};
int
main()
{
using fn_t = std::function<void()>;
const tracer t;
std::cout << "==== value capture ====\n";
fn_t([t]() {});
std::cout << "==== init capture ====\n";
fn_t([t = t]() {});
}
When run, this program outputs the following:
default constructed
==== value capture ====
copy constructed
template constructed (const rvalue)
==== init capture ====
copy constructed
move constructed
So what's going on here? First, note in both cases, the compiler must materialize a temporary lambda object to pass into the constructor for fn_t. Then, the constructor of fn_t must make a copy of the lambda object to hold on to it. (Since in general the std::function may outlive the lambda that was passed in to its constructor, it cannot retain the lambda by reference only.)
In the first case (value capture), the type of the captured t is exactly the type of t, namely const tracer. So you can think of the unnamed type of the lambda object as some kind of compiler-defined struct that contains a field of type const tracer. Let's give this structure a fake name of LAMBDA_T. So the argument to the constructor to fn_t is of type LAMBDA_T&&, and an expression that accesses the field inside is consequently of type const tracer&&, which matches the template constructor's forwarding reference better than the actual copy constructor. (In overload resolution rvalues prefer binding to rvalue references over binding to const lvalue references when both are available.)
In the second case (init capture), the type of the captured t = t is equivalent to the type of tnew in a declaration like auto tnew = t, namely tracer. So now the field in our internal LAMBDA_T structure is going to be of type tracer rather than const tracer, and when an argument of type LAMBDA_T&& to fn_t's constructor must be move-copied, the compiler will choose tracer's normal move constructor for moving that field.
For [v], the type of the lambda internal member variable v is const record, so when you
void call(const std::function<void()>&);
void print1(const record<string>& v) {
call([v] { });
}
Since [v] {} is a prvalue, when it initializes const std::function&, v will be copied with const record&&, and the template constructor will be chosen because it is not constrained.
In order to invoke v's copy constructor, you can do
void call(const std::function<void()>&);
void print1(const record<string>& v) {
auto l = [v] { };
call(l);
}
For [v=v], the type of the member variable v inside the lambda is record, so when the prvalue lambda initializes std::function, it will directly invoke the record's move constructor since record&& better matches.

decltype() on std::move() of a value capture in a lambda results in an incorrect type

It seems like there either is something wrong with Clang (9.0.0) or my understanding of how decltype() is specified to work in the standard. With reference to the following code,
#include <utility>
#include <string>
template <typename...> class WhichType;
template <typename T>
std::remove_reference_t<T>&& move_v2(T&& t) {
WhichType<std::remove_reference_t<T>&&>{};
return static_cast<std::remove_reference_t<T>&&>(t);
}
int main() {
auto x = std::string{"a"};
[v = x]() {
// move_v2(v);
// WhichType<decltype(move_v2(v))>{};
WhichType<decltype(std::move(v))>{};
}();
}
The code above has the compiler output implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>' instead of the expected const std::__1::basic_string<char> && in the template parameters of WhichType. Using move_v2 or WhichType in move_v2 itself seems to output the correct thing though.
However, Clang also seems to do overload resolution on the std::move(v) expression as I expected https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX. This makes some of my worries go away, but I still don't understand the behavior of decltype() inside the lambda.
GCC does not seem to have this inconsistency in this particular case https://wandbox.org/permlink/5mhrOzLn5XZO8LNB.
Could someone correct me if I'm wrong with my understading of decltype() or point me to the exact places where this bug manifests in clang? Seems a bit scary from first glance. This can cause problems when used in SFINAE or something similar.
Clang is wrong. decltype(std::move(v)) should be const && because the cv-qualifier v (which is equivalent to this->v) is the union of the cv-qualifiers of *this (which is const in the operator() of a non-mutable lambda) and v (which is none), so v is a const lvalue. Then, std::move converts to an xvalue of the corresponding type, so decltype should be const &&.
decltype behaves specially when applied to an id-expression or a member access expression (this->v), but that's not the case here. std::move(v) is a complicated expression, so it is treated as an ordinary expression.
I have done a bit of digging, and it seems to me that the answer is a bit more nuanced than some of the comments makes it seem.
First there is this answer to an earlier question. To quote the important part:
[C++11: 5.1.2/14]: An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [..]
Then there is this answer to another question. Again quote:
5 The closure type for a non-generic lambda-expression has a public
inline function call operator [...] This function call operator or
operator template is declared const (9.3.1) if and only if the
lambda-expression’s parameter-declaration-clause is not followed by
mutable.
I then put this together in a small test:
#include <iostream>
using std::cout;
using std::endl;
void foo(const std::string&) {
cout << "void foo(const std::string&)" << endl;
}
void foo(std::string&) {
cout << "void (std::string&)" << endl;
}
struct klaf
{
std::string b;
void bla() const
{
cout << std::boolalpha << std::is_const<decltype(b)>::value << endl;
foo(b);
}
};
int main()
{
klaf k;
k.bla();
std::string s;
const std::string s2;
auto lam = [=]() {
cout << std::boolalpha << std::is_const<decltype(s)>::value << endl;
foo(s);
cout << std::boolalpha << std::is_const<decltype(s2)>::value << endl;
foo(s2);
};
lam();
}
Which outpouts (and the outputs are the same in GCC, Clang, and MSVC):
false
void foo(const std::string&)
false
void foo(const std::string&)
true
void foo(const std::string&)
klaf::b is (obviously) not const, but since the klaf::bla function is const, then klaf::b is treated as const in the call to foo.
The same is true in lam where s is captured by value with a type of std::string. However, s2 has been declared as const std::string and that carries over to the type of the data member in the lambda.
In short: Capture by value in a lambda does not make the captured member itself const, but since operator() for the lambda is const, then the members are promoted to const in that function (unless the lambda is declared mutable).
EDIT:
Inspired by comment from #arnes I found a difference is GCC and Clang:
int main()
{
int i = 12;
auto lam = [=, ic = i]() {
cout << std::boolalpha << std::is_const<decltype(i)>::value << endl;
cout << std::boolalpha << std::is_const<decltype(ic)>::value << endl;
};
lam();
}
This produces false false in Clang, but false true in GCC. In other words, a capture with an initializer becomes const in GCC but not in Clang.

Why does returning a std::optional sometimes move and sometimes copy?

See the below example of returning an optional of UserName - a movable/copyable class.
std::optional<UserName> CreateUser()
{
UserName u;
return {u}; // this one will cause a copy of UserName
return u; // this one moves UserName
}
int main()
{
auto d = CreateUser();
}
Why does return {u} cause a copy and return u a move?
Here's the related coliru sample: http://coliru.stacked-crooked.com/a/6bf853750b38d110
Another case (thanks to the comment from #Slava):
std::unique_ptr<int> foo()
{
std::unique_ptr<int> p;
return {p}; // uses copy of unique_ptr and so it breaks...
}
Because returning a name of an object with automatic storage duration is treated as returning an rvalue of the object. Note this works only if the expression in the return statement is a (possibly parenthesized, not including braces) name, like return u; or return (u);, so return {u}; works as usual, i.e. the copy constructor is invoked.
Related part in the standard [class.copy.elision]/3:
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
...
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
This is a kind of braced-init-list. [dcl.init.list]/1.3
To be even more specific, it is an " expr-or-braced-init-list [dcl.init]/1
of a return statement " [stmt.return]/2
A return statement with any other operand shall be used only in a
function whose return type is not cv void; the return statement
initializes the glvalue result or prvalue result object of the
(explicit or implicit) function call by copy-initialization from the
operand.
From this point, let me qoutes xskxzr's answer that mention [class.copy.elision]/3
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
In normal human words, the reason that copy is called instead of move because braced-init-list call u that happened to be lvalue.
So, you may wanna know if braced-init-list call u that is rvalue ...
return {std::move(u)};
Well, u is moved to a new rvalue of UserName and copy elision works right after.
So this takes one move as in
return u;
godbolt.org/g/b6stLr
wandbox.org/permlink/7u1cPc0TG9gqToZD
#include <iostream>
#include <optional>
struct UserName
{
int x;
UserName() : x(0) {};
UserName(const UserName& other) : x(other.x) { std::cout << "copy " << x << "\n"; };
UserName(UserName&& other) : x(other.x) { std::cout << "move " << x << "\n"; };
};
std::optional<UserName> CreateUser()
{
UserName u;
return u; // this one moves UserName
}
std::optional<UserName> CreateUser_listinit()
{
UserName u;
auto whatever{u};
return whatever;
}
std::optional<UserName> CreateUser_listinit_with_copy_elision()
{
UserName u;
return {u};
}
std::optional<UserName> CreateUser_move_listinit_with_copy_elision()
{
UserName u;
return {std::move(u)};
}
int main()
{
std::cout << "CreateUser() :\n";
[[maybe_unused]] auto d = CreateUser();
std::cout << "\nCreateUser_listinit() :\n";
[[maybe_unused]] auto e = CreateUser_listinit();
std::cout << "\nCreateUser_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto f = CreateUser_listinit_with_copy_elision();
std::cout << "\nCreateUser_move_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto g = CreateUser_move_listinit_with_copy_elision();
}
print
CreateUser() :
move 0
CreateUser_listinit() :
copy 0
move 0
CreateUser_listinit_with_copy_elision() :
copy 0
CreateUser_move_listinit_with_copy_elision() :
move 0
return { arg1, arg2, ... } ;
is copy-list-initialization. the (return) object is initialized from the initializer list by copy-initialization for copy-list-initialization

Generic lambda with std::function does not capture variables

I'm trying to use the generic lambda of C++14, but got a trouble with std::function.
#include <iostream>
#include <functional>
int main()
{
const int a = 2;
std::function<void(int)> f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
f(3);
}
This fails to compile with an error message saying that error: ‘a’ was not declared in this scope.
It works if I change it to (int b).
Is it a bug? or am I missing something?
The version of GCC i'm using is 4.9.2.
I can reproduce this unless I do any of the following:
remove const from a
name a in the capture-list
change std::function<void(int)> to auto
make the lambda non-generic by changing auto b to int b
use Clang (e.g. v3.5.0)
I believe that this is a compiler bug related to optimisations and a failure to detect odr-use in a generic lambda (though it's interesting that setting -O0 has no effect). It could be related to bug 61814 but I don't think it's quite the same thing, therefore:
I have raised it as GCC bug 64791.
(Update: this bug has since been marked as fixed in GCC 5.0.)
Certainly I can't find anything obvious in the C++14 wording that should disallow your code, though there's very little "obvious" in general in the new C++14 wording. :(
[C++14: 5.1.2/6]: [..] For a generic lambda with no lambda-capture, the closure type has a public non-virtual non-explicit const conversion function template to pointer to function. The conversion function template has the same invented template-parameter-list, and the pointer to function has the same parameter types, as the function call operator template. [..]
[C++14: 5.1.2/12]: A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture's associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:
odr-uses (3.2) the entity, or
names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.
[ Example:
void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
const int x = 17;
auto g = [](auto a) {
f(x); // OK: calls #1, does not capture x
};
auto g2 = [=](auto a) {
int selector[sizeof(a) == 1 ? 1 : 2]{};
f(x, selector); // OK: is a dependent expression, so captures x
};
}
—end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture. —end note ]
[C++14: 5.1.2/13]: An entity is captured if it is captured explicitly or implicitly. An entity captured by a lambda-expression is odr-used (3.2) in the scope containing the lambda-expression. [..]
int main() {
const int a = 2;
auto f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
f(3);
}
Don't know if it should work with std::function but this works for sure.
Further investigation:
I created a class to mimic as closely as possible the lambda:
class Functor {
private:
int const x;
public:
Functor() : x{24} {}
auto operator()(int b) const -> void { cout << x << " " << b << endl; }
};
std::function<auto(int)->void> f2 = Functor{};
f2(3); // <- this works
This suggests that your example should have worked. After all lambdas are the same in behavior with an object who has the operator() overloaded and fields for the captured variables.
If we change the class to get to the auto part:
This doesn't work:
class Functor {
private:
int const x;
public:
Functor() : x{24} {}
auto operator()(auto b) const -> void { cout << x << " " << b << endl; }
};
std::function<auto(int)->void> f2 = Functor{}; // <-- doesn't work
However this works:
class Functor {
private:
int const x;
public:
Functor() : x{24} {}
template <class T>
auto operator()(T b) const -> void { cout << x << " " << b << endl; }
};
std::function<auto(int)->void> f2 = Functor{}; // <-- this works
So most likely it is related to the use of auto as parameter of lambda/functions, a feature new to C++14 so most likely without a mature implementation.

Overloading Based on L-Value versus R-Value

I found in a C++ book the following:
Although we will not be doing it in this book, you can overload a
function name (or operator) so that it behaves differently when used
as an l-value and when it is used as an r-value. (Recall that an
l-value means it can be used on the left-hand side of an assignment
statement.) For example, if you want a function f to behave
differently depending on whether it is used as an l-value or an
r-value, you can do so as follows:
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
I tried this and it didn't work:
class Foo {
public:
int& id(int& a);
const int& id(int& a) const;
};
int main() {
int a;
Foo f;
f.id(a) = 2;
a = f.id(a);
cout << f.id(a) << endl;
}
int& Foo :: id(int& a) {
cout << "Bar\n";
return a;
}
const int& Foo :: id(int& a) const {
cout << "No bar !\n";
return a;
}
Have I wrongly understood it ?
Either the book's example is flat-out wrong, or you copied the wrong example from the book.
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
With this code, when you call s.f() where s is an object of type SomeClass, the first version will be called when s is non-const, and the second version will be called when s is const. Value category has nothing to do with it.
Ref-qualification looks like this:
#include <iostream>
class SomeClass {
public:
int f() & { std::cout << "lvalue\n"; }
int f() && { std::cout << "rvalue\n"; }
};
int main() {
SomeClass s; s.f(); // prints "lvalue"
SomeClass{}.f(); // prints "rvalue"
}
Ofcourse the book is correct. Let me explain the workings of an example of what the author meant :
#include <iostream>
using namespace std;
class CO
{
int _m;
public:
CO(int m) : _m(m) {}
int& m() { return _m; } // used as an l-value
int const& m() const { return _m; } // used as an r-value
};
int main()
{
CO a(1);
cout << a.m() << endl;
a.m() = 2; // here used as an l-value / overload resolution selects the correct one
cout << a.m() << endl;
return 0;
}
Output is
1
2
What you misunderstood is the function signature. You see when you have an argument &arg (as in id(&arg)) you pretty much predefine the l-valuness of it, so returning it through a const or non const member function does not change a thing.
The author refers to a common writting style that allows for 'getters' and 'setters' to be declared with a signature different only in const qualifires yet compile and behave correctly.
Edit
To be more pedantic, the following phrase
Recall that an l-value means it can be used on the left-hand side of an assignment statement.
is not valid anymore. lr valuness applies to expressions, and the shortest way to explain it, is that an expression whose adress we can take, is an l-value; if it's not obtainable it's an r-value.
So the syntax to which the author refers to, enforces the member function to be used correctly (correct compilation / overload resolution) at both sides of the assignment operator. This nowdays is no longer relevant to lr valueness.
A const member function can only be called on a const object. It makes no difference what you do with the return value. In your example, f is non-const, so it always calls the non-const version of f(). Note that you can also overload on r-value references (&&) in C++11.