Search code examples
c++11lambdastd-function

How is std::function constructed for a lambda


I am a bit confused by how std::function is constructed at a given lambda. The constructor of std::function is listed here. Which one is actually used to capture a lambda? Is it template< class F > function( F f );? Looks like I can't construct std::function with a lambda that captures non-copy-constructible objects. Why is this necessary for lambda capture?

// fu is an object of type std::future 
std::function f = [future=std::move(fu)]() {...}  // compile error

// foo is an object of type int
std::function f = [foo=std::move(foo)]() {...} // compile ok

Solution

  • The short answer is that the standard states only copyable function objects can be stored in a std::function. This is unsatisfactory: why?

    std::function is a copyable type.

    The standard states that when copied, it also copies its contents.

    "But", you say, "I never copy it. Why should it need be copied?" An instance of std::function records how to copy its contents even if it never does so. It typically uses a technique known as type erasure.

    Here is a toy example:

    struct invoke_later {
      struct i_impl {
        virtual ~i_impl() {}
        virtual void invoke() const = 0;
        virtual std::unique_ptr<i_impl> clone() const = 0;
      };
      template<class T>
      struct impl:i_impl {
        T t;
        ~impl() = default;
        void invoke() const override {
          t();
        }
        impl(T&& tin):t(std::move(tin)) {}
        impl(T const& tin):t(tin) {}
        virtual std::unique_ptr<i_impl> clone() const {
          return std::make_unique<impl>(t);
        };
      };
      std::unique_ptr<i_impl> pimpl;
    
      template<class T,
        // SFINAE suppress using this ctor instead of copy/move ctors:
        std::enable_if_t< !std::is_same<std::decay_t<T>, invoke_later>{}, int>* =0
      >
      invoke_later( T&& t ):
        pimpl( std::make_unique<impl<std::decay_t<T>>( std::forward<T>(t) ) )
      {}
    
      invoke_later(invoke_later&&)=default;
      invoke_later(invoke_later const&o):
        pimpl(o.pimpl?o.pimpl->clone():std::unique_ptr<i_impl>{})
      {}
    
      ~invoke_later() = default;
    
      // assignment goes here
    
      void operator() const {
        pimpl->invoke();
      }
      explicit operator bool() const { return !!pimpl; }
    };
    

    the above is a toy example of a std::function<void()>.

    The operation of copying is stored in the ->clone() method of the pimpl. It must be compiled even if you never call it.

    The writers of the std specification where aware of the above technique, and knew its limitations, and wanted to permit std::functions to be implemented simply using it. In addition, they wanted simple operations on the std::function to behave in predictable ways: with non-copyable contents, what should copying the std::function do?

    Note you can get around this issue by wrapping your state in a shared_ptr. Then copyies of your std::function will simply store shared references to your state, instead of copies.

    template<class F>
    auto shared_state( F&& f ) {
      return [pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f))]
        (auto&&... args)->decltype(auto) {
          return (*pf)(decltype(args)(args)...);
        };
    }
    

    now:

    std::function<Sig> f = shared_state([future=std::move(fu)]() {...});
    

    will compile and work.

    An alternative approach is to make a non-copyable std::function and use that instead of std::function.

    Finally, whe working with future, a shared_future is a copyable future type and may be cheaper than doing a shared_state:

    std::function<void()> f = [fu=fu.share()]{ /* code */ };