In C++14, a lambda expression can capture variables by moving from them using capture initializers. However, this makes the resulting closure object non-copyable. If I have an existing function that takes a std::function
argument (that I cannot change), I cannot pass the closure object, because std::function
's constructor requires the given functor to be CopyConstructible
.
#include <iostream>
#include <memory>
void doit(std::function<void()> f) {
f();
}
int main()
{
std::unique_ptr<int> p(new int(5));
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
}
This gives the following errors:
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error:
call to implicitly-deleted copy constructor of '<lambda at test.cpp:10:7>'
new _Functor(*__source._M_access<_Functor*>());
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in
instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
>::_M_clone' requested here
_M_clone(__dest, __source, _Local_storage());
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in
instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
>::_M_manager' requested here
_M_manager = &_My_handler::_M_manager;
^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void
()>::function<<lambda at test.cpp:10:7>, void>' requested here
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
^
test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted
copy constructor
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note:
'unique_ptr' has been explicitly marked deleted here
unique_ptr(const unique_ptr&) = delete;
^
Is there a reasonable workaround?
Testing with Ubuntu clang version 3.5-1~exp1 (trunk)
There is this approach:
template< typename signature >
struct make_copyable_function_helper;
template< typename R, typename... Args >
struct make_copyable_function_helper<R(Args...)> {
template<typename input>
std::function<R(Args...)> operator()( input&& i ) const {
auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) );
return [ptr]( Args... args )->R {
return (*ptr)(std::forward<Args>(args)...);
};
}
};
template< typename signature, typename input >
std::function<signature> make_copyable_function( input && i ) {
return make_copyable_function_helper<signature>()( std::forward<input>(i) );
}
where we make a shared pointer to our data, then make a copyable lambda that captures that shared pointer, then we wrap that copyable lambda into a std::function
of the requested signature.
In your case above, you'd just:
doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
A slightly more advanced version defers the type erasure and adds a layer of perfect forwarding to reduce overhead:
template<typename input>
struct copyable_function {
typedef typename std::decay<input>::type stored_input;
template<typename... Args>
auto operator()( Args&&... args )->
decltype( std::declval<input&>()(std::forward<Args>(args)...) )
{
return (*ptr)(std::forward<Args>(args));
}
copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {}
copyable_function( copyable_function const& ) = default;
private:
std::shared_ptr<stored_input> ptr;
};
template<typename input>
copyable_function<input> make_copyable_function( input&& i ) {
return {std::forward<input>(i)};
}
which does not require you to pass the signature in, and can be slightly more efficient in a few cases, but uses more obscure techniques.
In C++14 with this can be made even more brief:
template< class F >
auto make_copyable_function( F&& f ) {
using dF=std::decay_t<F>;
auto spf = std::make_shared<dF>( std::forward<F>(f) );
return [spf](auto&&... args)->decltype(auto) {
return (*spf)( decltype(args)(args)... );
};
}
doing away with the need for the helper type entirely.
If lifetime of the closure object isn't an issue, you could pass it in a reference wrapper:
int main()
{
std::unique_ptr<int> p(new int(5));
auto f = [p = std::move(p)]{
std::cout << *p << std::endl;
};
doit(std::cref(f));
}
This obviously doesn't apply to every scenario, but it's fine for your example program.
EDIT: Taking a glance at N3797 (C++14 working draft) § 20.9.11.2.1 [func.wrap.func.con] p7, the CopyConstructible
requirement is still there. I wonder if there's a technical reason that can't be loosened to MoveConstructible
, or if the committee just didn't get around to it?
EDIT: Answering my own question: std::function
is CopyConstructible
, so the wrapped functor needs to be CopyConstructible
as well.
If you know you aren't actually going to copy your function object then you can just wrap it in a type that makes the compiler think it's copyable:
struct ThrowOnCopy {
ThrowOnCopy() = default;
ThrowOnCopy(const ThrowOnCopy&) { throw std::logic_error("Oops!"); }
ThrowOnCopy(ThrowOnCopy&&) = default;
ThrowOnCopy& operator=(ThrowOnCopy&&) = default;
};
template<typename T>
struct FakeCopyable : ThrowOnCopy
{
FakeCopyable(T&& t) : target(std::forward<T>(t)) { }
FakeCopyable(FakeCopyable&&) = default;
FakeCopyable(const FakeCopyable& other)
: ThrowOnCopy(other), // this will throw
target(std::move(const_cast<T&>(other.target))) // never reached
{ }
template<typename... Args>
auto operator()(Args&&... a)
{ return target(std::forward<Args>(a)...); }
T target;
};
template<typename T>
FakeCopyable<T>
fake_copyable(T&& t)
{ return { std::forward<T>(t) }; }
// ...
doit( fake_copyable([p = std::move(p)] () { std::cout << *p << std::endl; }) );
The function template fake_copyable
creates a wrapper which is CopyConstructible
according to the compiler (and <type_traits>
) but cannot be copied at run-time.
If you store a FakeCopyable<X>
in a std::function
and then end up copying the std::function
you will get a std::logic_error
thrown, but if you only move the std::function
everything will work OK.
The target(std::move(const_cast<T&>(other.target)))
looks worrying, but that initializer will never run, because the base class initializer will throw first. So the worrying const_cast
never really happens, it just keeps the compiler happy.