Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding functions with unique_ptr arguments to std::function<void()>

Tags:

c++

c++11

I'm trying to make the following code work:

#include <cstdio>
#include <functional>
#include <string>
#include <memory>

using namespace std;

class Foo {
public:
    Foo(): m_str("foo") { }

    void f1(string s1, string s2, unique_ptr<Foo> p)
        {
            printf("1: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
        }

    void f2(string s1, string s2, Foo* p)
        {
            printf("2: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
        }

    const char* str() const { return m_str.c_str(); }

private:
    string m_str;
};

int main()
{
    string arg1 = "arg1";
    string arg2 = "arg2";
    Foo s;
    unique_ptr<Foo> ptr(new Foo);


    //function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
    function<void()> f(bind(&Foo::f2, &s, arg1, arg2, ptr.release()));

    f();
}

Calling f() bound to Foo::f2 (last parameter is a raw pointer) works fine, but binding it to Foo::f1 causes compilation error:

test.cpp: In function ‘int main()’:
test.cpp:36:70: error: no matching function for call to ‘std::function<void()>::function(std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type)’
     function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
                                                                      ^
test.cpp:36:70: note: candidates are:
In file included from test.cpp:2:0:
/usr/include/c++/4.8.2/functional:2251:2: note: template<class _Functor, class> std::function<_Res(_ArgTypes ...)>::function(_Functor)
  function(_Functor);
  ^
/usr/include/c++/4.8.2/functional:2251:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.8.2/functional:2226:7: note: std::function<_Res(_ArgTypes ...)>::function(std::function<_Res(_ArgTypes ...)>&&) [with _Res = void; _ArgTypes = {}]
       function(function&& __x) : _Function_base()
       ^
/usr/include/c++/4.8.2/functional:2226:7: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘std::function<void()>&&’
/usr/include/c++/4.8.2/functional:2429:5: note: std::function<_Res(_ArgTypes ...)>::function(const std::function<_Res(_ArgTypes ...)>&) [with _Res = void; _ArgTypes = {}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/include/c++/4.8.2/functional:2429:5: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘const std::function<void()>&’
/usr/include/c++/4.8.2/functional:2206:7: note: std::function<_Res(_ArgTypes ...)>::function(std::nullptr_t) [with _Res = void; _ArgTypes = {}; std::nullptr_t = std::nullptr_t]
       function(nullptr_t) noexcept
       ^
/usr/include/c++/4.8.2/functional:2206:7: note:   no known conversion for argument 1 from ‘std::_Bind_helper<false, void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>), Foo*, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::unique_ptr<Foo, std::default_delete<Foo> > >::type {aka std::_Bind<std::_Mem_fn<void (Foo::*)(std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>(Foo*, std::basic_string<char>, std::basic_string<char>, std::unique_ptr<Foo>)>}’ to ‘std::nullptr_t’
/usr/include/c++/4.8.2/functional:2199:7: note: std::function<_Res(_ArgTypes ...)>::function() [with _Res = void; _ArgTypes = {}]
       function() noexcept
       ^
/usr/include/c++/4.8.2/functional:2199:7: note:   candidate expects 0 arguments, 1 provided

What am I doing wrong?

I'm using gcc 4.8.2 and -std=c++0x (-std=c++11 fails too) flags.

like image 516
rzan Avatar asked Nov 28 '13 14:11

rzan


2 Answers

The issues with bind described in the other answers (as of this writing) are not what the compiler is complaining about in the question. The problem is that std::function must be CopyConstructible, which requires its argument (which will be stored by the function) also be CopyConstructible.

From the standard [20.9.11.2 Class template function]

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 (20.9.11.2) for argument types ArgTypes and return type R . The copy constructor and destructor of A shall not throw exceptions...

Consider this example that doesn't even include bind in it:

#include <functional>
#include <memory>

using namespace std;

struct NonCopyableFunctor {
  NonCopyableFunctor(){}
  NonCopyableFunctor(const NonCopyableFunctor &) = delete;
  NonCopyableFunctor(NonCopyableFunctor &&){}
  void operator()(){}
};

int main() 
{
  NonCopyableFunctor fun;
  function<void()> vfun(move(fun)); // even though I move here,
  // it still complains about a copy going on elsewhere.
}

Here's the output from clang:

[orm@localhost ~]$ clang++ -std=c++11 bound_unique.cc 
In file included from bound_unique.cc:1:
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:1911:10: error: call to deleted constructor of 'NonCopyableFunctor'
            new _Functor(*__source._M_access<_Functor*>());
                ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:1946:8: note: in instantiation of member function
      'std::_Function_base::_Base_manager<NonCopyableFunctor>::_M_clone' requested here
              _M_clone(__dest, __source, _Local_storage());
              ^
/usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2/../../../../include/c++/4.8.2/functional:2453:33: note: in instantiation of member function
      'std::_Function_base::_Base_manager<NonCopyableFunctor>::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
bound_unique.cc:16:20: note: in instantiation of function template specialization 'std::function<void ()>::function<NonCopyableFunctor, void>' requested here
  function<void()> vfun(move(fun));
                   ^
bound_unique.cc:8:3: note: function has been explicitly marked deleted here
  NonCopyableFunctor(const NonCopyableFunctor &) = delete;
  ^
1 error generated.

Note that if you bind a unique_ptr, the resulting bind object will be non-copyable. Bind will still compile anyway.

like image 152
orm Avatar answered Oct 01 '22 11:10

orm


1) The following code won't compile

 function<void()> f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr)));
 // just define f, not even call it

because function requires the callable object is copy-constructible but when bind takes a non-copyable argument like unique_ptr the returned functor will be non-copyable, as mentioned in other answers.

2) So just don't use function for bind. However the following code won't compile either

 auto f(bind(&Foo::f1, &s, arg1, arg2, std::move(ptr))); // a
 f();                                                    // b

because at step (a) bind stores what you give to it as an lvalue (except for reference_wrapper), and passes it to the internal functor at step (b). Therefore it requires the bound arguments are copyable for those parameters are passed by value, not references.

3) Then try using the raw pointer. However the following code won't compile either

 auto f(bind(&Foo::f1, &s, arg1, arg2, ptr.release()));
 f();

A similar reason like (2), the functor stores an int*, and try to convert it to the parameter type unique_ptr<int> when called. But the constructor unique_ptr(pointer p) is explicit.


To compile it, you need a function like this

void f3(string s1, string s2, unique_ptr<Foo>& p)
//                                           ^ use reference; add const if you need
{
    printf("3: %s %s %s\n", s1.c_str(), s2.c_str(), p->str());
}

auto f(bind(&Foo::f3, &s, arg1, arg2, std::move(ptr)));
f();

Note that f could be called several times, and the parameters p reference the same unique_ptr that is stored in the returned object of bind.

like image 32
neuront Avatar answered Oct 01 '22 11:10

neuront