Binding functions with unique_ptr arguments to std

2020-02-12 04:43发布

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.

标签: c++ c++11
3条回答
老娘就宠你
2楼-- · 2020-02-12 04:48

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.

查看更多
Animai°情兽
3楼-- · 2020-02-12 04:49

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.

查看更多
Melony?
4楼-- · 2020-02-12 04:51

Hmm it really seems that std::bind has troubles when dealing with r-value references. One alternative would be to use a lambda function:

function<void()> f([&]() { s.f1(arg1,arg2,std::move(ptr)); });

In order for this to work you also have to change the signature of f1 such that it accepts the unique_ptr as an r-value reference:

void f1(string s1, string s2, unique_ptr<Foo>&& p)

(Even if std::bind could handle r-value references, you would still have to do this because std::unique_ptr doesn't have a copy constructor, only the move constructor is accessible!)

Note however that your construct is rather dangerous (also if std::bind would work): if you call f() twice, you will end up with a runtime exception.

查看更多
登录 后发表回答