Zebrafish
Zebrafish

Reputation: 13948

No suitable copy constructor after capturing a class with lambda

I had the following code:

#include <iostream>

class Foo
{public:
    Foo() {}
    int a;
};

int main()
{
    Foo foo;

    auto lambda = [=]() mutable { std::cout << foo.a; };

}

And things were working fine, until I needed to add a copy constructor to my Foo class:

Foo(Foo& t) {}

And it wouldn't compile anymore, giving the message:

class 'Foo': no copy constructor available or copy constructor is declared 'explicit'

I've made the lambda mutable because I didn't want capture a const Foo, but what I think is happening is that the lambda can't be copied. Another compiler had a more useful error message:

error: use of deleted function ‘main()::< lambda()>::< lambda>(main()::< lambda()>&&)’

and:

main()::< lambda()>::< lambda>(main()::< lambda()>&&)’ is implicitly deleted because the default definition would be ill-formed:

But I don't really understand this. Is that implicitly deleted function the move constructor of the lambda? I can't understand why just adding a copy constructor to the captured class (not the lambda) makes this happen.

This is what I picture lambda/functor as looking like:

class lambda
{public:

     Foo foo; // <---- My captured variable/class
     void operator()(){ std::cout << foo.a; }
}

So then a copy of one of these lambdas to another involves calling Foo's assignment operator or copy constructor? I don't understand how just Foo having a copy constructor makes this fail or what is "ill-formed". The other thing I noticed is that there's no problem when the lambda captures by reference [&].

Edit: It doesn't compile on this compiler:

https://www.jdoodle.com/online-compiler-c++/

I'm on Visual Studio and it wouldn't compile. However when I made a much smaller example it would compiler, but still underline the error. In my larger project it doesn't compile.

Upvotes: 3

Views: 1280

Answers (3)

Evg
Evg

Reputation: 26322

To get a compilation error, you should compile this code under C++11 or C++14 standard. In C++17 it is valid. Demo.

Let's consider

struct Foo {
    Foo() {}
    Foo(Foo&) {}
};

In C++17 we can write

auto f = Foo{};

But in C++11/14 this line will fail to compile. The reason is that in C++17 we have mandatory copy elision, and the correctness of copy constructor invocation, which would be ill-formed because Foo& cannot bind to a temporary Foo{} and Foo(Foo&&) is deleted, is not even checked by the compiler.

This translates directly into a lambda (rafix07's answer explains how), because it captures Foo by value. The lambda itself is fine. For example, you can write

[=] { std::cout << foo.a; };

But lambda's move constructor is ill-formed, and in C++11/14 it has to be well-formed for the line

auto lambda = [=] { std::cout << foo.a; };

to compile.

Upvotes: 1

rafix07
rafix07

Reputation: 20936

auto lambda = [=]() mutable { std::cout << foo.a; };

on the right you create temporary closure. Based on this temporary closure is constructed another by calling default move constructor generated by compiler.

closure c(closure{});

default implementation of move constructor just moves all data members one by one:

struct closure {
    Foo foo;

    closure (closure&& theOther) : foo(std::move(theOther.foo))   // <--- [1]
    {}                              // binding rvalue ref to lvalue ref
};

your Foo ctor takes Foo&, but it is not allowed to bind rvalue reference to lvalue reference.

It works in MSVC because it has an extension to deal with such thing. Under G++/Clang it must fail.

With const Foo&, works fine because temporary can be bound to const lvalue ref.

Upvotes: 1

Thomas Caissard
Thomas Caissard

Reputation: 866

The copy constructor prototype is A(const A&). You are effectively missing the const qualifier on your copy constructor which is why the error happens.

Upvotes: 2

Related Questions