How can std::function be copied if it captures a unique_ptr?

This code doesn't actually "work" in that sense. It compiles, but only because you don't ever call queue_read.

If you try to use it (which forces the compiler to actually instantiate the R<P>::queue_read, which it doesn't have to do until that point) then you get an error from every compiler:

template<class P> 
class R{
    public:
    void queue_read(P *t)
    {
        doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });
    }

    void doSomething(std::function<void()> f) {
        (void)f;
    }

    void test(std::unique_ptr<P> p) {
        (void)p;
    }
};

int main()
{
    R<int> r;
    r.queue_read(new int(1));

    return 0;
}

clang 9.0:

error: call to deleted constructor of 'std::unique_ptr<int>'
    doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });

gcc 9.2:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
11 |         doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });

MSVC 19.22:

error C2280: 'std::unique_ptr<P,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function

https://godbolt.org/z/KmjVJB (thanks Richard!)

Again, the key here is that the compiler didn't actually compile the code of queue_read because there was no need. The function is implicitly inline by virtue of being defined within the class body. Instantiating R<P> for some P causes only the declarations, but not the definitions of its member functions to be instantiated. Only once you actually call queue_read does the compiler have to complain.

This is a good thing by the way. You can use std::vector<MoveOnlyType> and do everything that doesn't require copying, even though some of the std::vector member functions require a copyable type. But as long as you never use the latter functions, everything is fine. If the compiler always instantiated all the member function definitions and reported errors in those (even if never used) it would be way more cumbersome.


How can std::function be copied if it captures an unique_ptr?

A std::function doesn't capture anything.

A lambda that captures a non-copyable object such as std::unique_ptr is itself non-copyable. Such lambda doesn't satisfy, nor does any other non-copyable function object type satisfy the requirements of std::function which requires the functor to be copyable. Standard rule (from latest draft):

[func.wrap.func.con]

template<class F> function(F f);

Requires: F shall be Cpp17CopyConstructible.


However the code compiles fine.

This is typical when the ill-formed function is an unused function of a template. It should fail to compile if you attempt to call the function.

Tags:

C++