27

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)

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • It's an issue I have been having more and more with proxies (last time it surfaced for me was implementing PIMPL in a generic way). The problem is that an object abilities is determined by its type (here `std::function`) so if you wanted an object that is only copyable if the passed object is copyable, movable if the passed object is movable, etc... it is unfortunately impossible with the same type => I guess a bitmask as another template parameter (`std::function`) would do but it may be stuffy :/ – Matthieu M. Dec 30 '13 at 16:51
  • I have my own type-erasing holder for that purpose. To the best of my knowledge there is no proposal to improve the situation, although I’m told there are comments from national bodies on the topic. – Luc Danton Dec 30 '13 at 21:37
  • Probably `std::function_ref` will be able to handle this http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0792r2.html . Otherwise use `std::cref` like one of the answers below. – alfC Oct 24 '20 at 08:51

3 Answers3

14

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
9

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.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    If the closure of the object isn't an issue, couldn't you just make it so that doit takes a const function&? – IdeaHat Dec 30 '13 at 16:33
  • 2
    @MadScienceDreams It may work with a particular implementation constructing the function object as `std::function bar{std::move(f)};` if that implementation avoids instantiating `std::function`'s copy constructor. It wouldn't be portable behavior though, since `std::function` explicitly requires the functor to be copy constructible in [func.wrap.func.con]/7. Indeed, [it fails with gcc 4.8.1, for example](http://coliru.stacked-crooked.com/a/945dea28043c5a65). – Casey Dec 30 '13 at 16:41
  • 1
    @MadScienceDreams I assume that implementations of `std::function` achieve type erasure by wrapping the passed functor with an abstract interface that includes a "virtual copy constructor". Every implementation of C++ with which I am familiar instantiates all members of a template declared virtual to populate a vtable even though the standard says that instantiation of unused virtual members is implementation-specific. Obviously that virtual copy constructor is the source of the `CopyConstructible` requirement for functors passed to `std::function`. – Casey Dec 30 '13 at 16:48
  • C++0x drafts didn't always have the `CopyConstructible` requirement, it was added by [LWG 1287](http://cplusplus.github.io/LWG/lwg-defects.html#1287). The implementation doesn't have to use any virtual functions (and GCC's implementation doesn't) but type erasure still means you can't provide a copyable wrapper if the target might not be copyable. – Jonathan Wakely Jan 17 '14 at 10:12
3

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.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521