Why Can't C++11 Move a Noncopyable Functor to a Std::Function

Why can't C++11 move a noncopyable functor to a std::function?

The short answer is that the C++11 specification requires your A to be CopyConstructible to be used with std::function.

The long answer is this requirement exists because std::function erases the type of your functor within the constructor. To do this, std::function must access certain members of your functor via virtual functions. These include the call operator, the copy constructor and the destructor. And since these are accessed via a virtual call, they are "used" whether or not you actually use std::function's copy constructor, destructor or call operator.

Can std::function be move-constructed from rvalue reference to a temporary functor object?

This object is really heavyweight, so it's marked as uncopyable, but it does have a move constructor.

If a functor is non-copyable, it does not meet the necessary requirements for being used with std::function. Paragraph 20.8.11.2.1/7 of the C++11 Standard specifies:

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes
and return type R. The copy constructor and destructor of A shall not throw exceptions.

How to store non-copyable std::function into a container?

std::ref and std::cref are meant to be used in this cases to avoid copying the object (see http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper).

Not sure that I got your question right, but this compiles for me:

#include <vector>
#include <functional>

class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable &) = delete;
NonCopyable(NonCopyable &&) = default;
};

int main() {
std::vector<std::function<void()>> callbacks;
callbacks.emplace_back([] {});

NonCopyable tmp;
auto fun = std::bind([](const NonCopyable &) {}, std::cref(tmp));
callbacks.emplace_back(fun);

return 0;
}

EDIT: As mentioned in the comments, be careful about the life cycle of the referenced variable!

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 to create an std::function from a move-capturing lambda expression?

template<class F> function(F f);

template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible. f shall be Callable for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

§20.9.11.2.1 [func.wrap.func.con]

Note that operator = is defined in terms of this constructor and swap, so the same restrictions apply:

template<class F> function& operator=(F&& f);

Effects: function(std::forward<F>(f)).swap(*this);

§20.9.11.2.1 [func.wrap.func.con]

So to answer your question: Yes, it is possible to construct a std::function from a move-capturing lambda (since this only specifies how the lambda captures), but it is not possible to construct a std::function from a move-only type (e.g. a move-capturing lambda which move-captures something that is not copy constructible).

Passing a lambda with moved capture to function

The error happens because your lambda has non-copyable captures, making the lambda itself not copyable. std::function requires that the wrapped object be copy-constructible.

If you have control over call_func, make it a template:

template<typename T>
void call_func(T&& func)
{
func();
}

int main()
{
std::fstream fs{"test.txt", std::fstream::out};
auto lam = [fs = std::move(fs)] { const_cast<std::fstream&>(fs).close(); };
call_func(lam);
}

Following is my take on your idea in (2). Since std::function requires the wrapped object to be copy-constructible, we can make our own function wrapper that does not have this restriction:

#include <algorithm>
#include <fstream>
#include <iterator>
#include <utility>
#include <memory>
#include <sstream>
#include <vector>

template<typename T>
void call_func(T&& func) {
func();
}

// All functors have a common base, so we will be able to store them in a single container.
struct baseFunctor {
virtual void operator()()=0;
};

// The actual functor is as simple as it gets.
template<typename T>
class functor : public baseFunctor {
T f;
public:
template<typename U>
functor(U&& f)
: f(std::forward<U>(f))
{}
void operator()() override {
f();
}
};

// In C++17 you don't need this: functor's default constructor can already infer T.
template<typename T>
auto makeNewFunctor(T&& v) {
return std::unique_ptr<baseFunctor>(new functor<T>{std::forward<T>(v)});
}

int main() {
// We need to store pointers instead of values, for the virtual function mechanism to behave correctly.
std::vector<std::unique_ptr<baseFunctor>> functors;

// Generate 10 functors writing to 10 different file streams
std::generate_n(std::back_inserter(functors), 10, [](){
static int i=0;
std::ostringstream oss{"test"};
oss << ++i << ".txt";
std::fstream fs{oss.str(), std::fstream::out};
return makeNewFunctor([fs = std::move(fs)] () mutable { fs.close(); });
});

// Execute the functors
for (auto& functor : functors) {
call_func(*functor);
}
}

Note that the overhead from the virtual call is unavoidable: Since you need functors with different behavior stored in the same container, you essentially need polymorphic behavior one way or the other. So you either implement this polymorphism by hand, or use virtual. I prefer the latter.

Returning a lambda function with a move-capture

The problem is the std::function<int ()> return type, which is attempting to make a copy of the lambda. This will fail because the copy constructor is implicitly deleted due to the presence of the std::unique_ptr. Instead of storing the lambda in a std::function object, use return type deduction, now the lambda will be moved.

auto makeLambda(unique_ptr<int> ptr) {
return [ ptr( move(ptr) ) ] () {
return *ptr;
};
}

Live demo

You should also probably change the argument type of makeLambda to unique_ptr<int>&&

How to create a container of noncopyable elements

No, non-copyable elements can't be in C++ container classes.

According to the standard, 23.1 paragraph 3, "The type of objects stored in these components must met the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignable types."



Related Topics



Leave a reply



Submit