Error, cannot construct a copy of a lambda in constructor body

Viewed 1800

In the following, one way of initialising a member is accepted and the other is an error. I don't understand:

template <typename T>
struct LambdaHolder
{
    LambdaHolder(T lambdaIn) { lambda = lambdaIn; } // This will not work
    LambdaHolder(T lambdaIn) : lambda(lambdaIn) { } // This will work fine
    T lambda;
};


int main()
{
    auto lambda = []() { return 0; };
    LambdaHolder<decltype(lambda) > foo(lambda); // This will work only if the constructor argument is assigned in the initialiser list

// On the other hand
    int(*lambdaPtr)() = lambda; // A function pointer
    LambdaHolder<decltype(lambdaPtr)> foo(lambdaPtr); // Will work either in initialiser list or constructor body

}

I'm not understanding a couple of things. I know that certain members must be initialised in the member initialiser list, like references and const members. What I don't understand is why it will let me copy the lambda in the initialiser list but not in the constructor body. The error is "you cannot construct a copy of a lambda".

Also, I was able to create a pointer to the lambda, and initialising the pointer in either the initialiser list or the constructor body worked. Now, I know that lambdas and function pointers aren't exactly the same type, but the thing is that I thought that a captureless lambda is implemented as a free function, which is why you can set a normal function pointer to it, and also thought that when passing as an argument it would definitely decay to a pointer.

Basically I would like some clarification this, what's the difference for the distinction between initialising in the constructor body or initialiser list?

Edit: It seems, as Whoan has pointed out, that a lambda's default constructor's deleted before C++14, and non-existent after C++14 I think.

2 Answers

That is because lambdas are not Default Constructible:

Closure types are not DefaultConstructible. Closure types have "a deleted (until C++14) | no (since C++14)" default constructor.

In this constructor:

LambdaHolder(T lambdaIn) { lambda = lambdaIn; } // This will not work

...lambda needs to have been default constructed before being assigned.

First off, there's a difference between initialization and assignment, although they sometimes look similar.

SomeClass obj1 = val;

is an initialization. It attempts to pass the expression val to a constructor of SomeClass in order to create obj1. Note even if you've defined one or more operator= for SomeClass, none of them will be called by the above code.

SomeClass obj2;
obj2 = val;

In this example, the first statement still needs to initialize obj2 to create a valid object that can be used, so it requires calling a default constructor of SomeClass (either defined by you or automatically by the compiler). Then the second statement assigns to the already initialized object obj2, using an operator= function (also either defined by you or by the compiler).

Whenever a constructor is called, all base classes and members must be initialized before the constructor body starts. The body might try to do anything to members, so they need to be valid initialized objects by that point.

LambdaHolder(T lambdaIn) : lambda(lambdaIn) { }

For any class type T, this constructor initializes the member called lambda using the argument lambdaIn. Since they have the same type, that means calling a copy constructor. This one is perfectly valid.

LambdaHolder(T lambdaIn) { lambda = lambdaIn; }

For any class type T, since the mem-initializer for member lambda is missing but the member must be initialized before the body starts, the compiler attempts to call a default constructor for T to create lambda. Then the statement in the body lambda = lambdaIn; tries to call an operator= function to assign the member from the argument.

Yes, the type of a lambda is a class type. A lambda is essentially a shortcut for making a class whose single most useful member is a function named operator(), so you can call it like a function.

But lambda types have deleted default constructors and deleted copy assignment operators. This means you're never allowed to call them, so the above code is invalid in two different ways.

And by the way, lambda expressions never automatically "decay" into pointers to functions in the sense that C-style arrays decay into pointers to the first element and names of functions decay into pointers to functions. That type of decay only happens to raw array and function types. Instead, there's an implicit conversion from the type of a lambda with no capture list to a function pointer. The implicit conversion only gets used when you actually try to initialize or assign a specific matching function pointer type from the lambda.

Related