简体   繁体   中英

c++ std::bind universal reference can't compile

#include <functional>
#include <iostream>
#include <type_traits>

using cb_t = std::function<void ()>;

template<typename CB_T, std::enable_if_t<std::is_constructible<cb_t, CB_T>::value, bool> = true>
void
on_my_write(int a, int b, CB_T&& cb) {
    std::cout << "on_my_write:" << a << ", " << b << std::endl;
}
 

template<typename CB_T,  std::enable_if_t<std::is_constructible<cb_t, CB_T>::value, bool> = true>
void foo(CB_T&& cb) {
    auto b = std::bind(&on_my_write<CB_T>, std::placeholders::_1, std::placeholders::_2, std::forward<CB_T>(cb));
    b(1, 2);
}

int main() {
  
    foo([]{});

    return 0;
}

compile this with command line

g++ ./try1.cpp -std=c++17

gave me:

./try1.cpp: In instantiation of ‘void foo(CB_T&&) [with CB_T = main()::<lambda()>; typename >std::enable_if<std::is_constructible<std::function<void()>, CB_T>::value, bool>::type <anonymous> = 1]’:

./try1.cpp:39:13:   required from here
./try1.cpp:34:6: error: no match for call to ‘(std::_Bind<void (*(std::_Placeholder<1>, std::_Placeholder<2>, main()::<lambda()>))(int, int, main()::<lambda()>&&)>) (int, int)’
     b(1, 2);
     ~^~~~~~
In file included from ./try1.cpp:19:0:
/usr/include/c++/7/functional:547:2: note: candidate: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int, int, main()::<lambda()>&&); _Bound_args = {std::_Placeholder<1>, std::_Placeholder<2>, main()::<lambda()>}]
  operator()(_Args&&... __args)
  ^~~~~~~~
/usr/include/c++/7/functional:547:2: note:   template argument deduction/substitution failed:
/usr/include/c++/7/functional:558:2: note: candidate: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int, int, main()::<lambda()>&&); _Bound_args = {std::_Placeholder<1>, std::_Placeholder<2>, main()::<lambda()>}]
  operator()(_Args&&... __args) const
  ^~~~~~~~
/usr/include/c++/7/functional:558:2: note:   template argument deduction/substitution failed:
/usr/include/c++/7/functional:576:2: note: candidate: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int, int, main()::<lambda()>&&); _Bound_args = {std::_Placeholder<1>, std::_Placeholder<2>, main()::<lambda()>}]
  operator()(_Args&&... __args) volatile
  ^~~~~~~~
/usr/include/c++/7/functional:576:2: note:   template argument deduction/substitution failed:
/usr/include/c++/7/functional:588:2: note: candidate: template<class ... _Args, class _Result> _Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) const volatile [with _Args = {_Args ...}; _Result = _Result; _Functor = void (*)(int, int, main()::<lambda()>&&); _Bound_args = {std::_Placeholder<1>, std::_Placeholder<2>, main()::<lambda()>}]
  operator()(_Args&&... __args) const volatile
  ^~~~~~~~
/usr/include/c++/7/functional:588:2: note:   template argument deduction/substitution failed:

But if I change on_my_write(int a, int b, CB_T&& cb) to on_my_write(int a, int b, const CB_T& cb) , then it will compile and run successfully.

I really don't understand why?

Could anybody explain this to me? thanks a lot!

I've tried several hours and can't figure it out.

Yes, this is slightly tricky. std::bind is of course not magical, it has to store the bound parameters somehow and it stores them as values by default, unless requested with std::ref argument.

Meaning that in the call std::bind(...,std::forward<CB_T>(cb)) , the cb is used to construct the bound member variable properly using the perfect forwarding.

But, the calling is not done with perfect forwarding sadly, the argument that bind will pass to on_my_write is always an l-value. It is not impossible to create std::bind version that would forward it and I do not know exactly why is it this way, but a good reason might be that such forwarding would make calling the bound functor multiple times dangerous.

So, since std::bind always passes the argument as l-value, you have a couple of options:

  1. Remove perfect forwarding and use on_my_write(int a, int b, const CB_T& cb) as you did.
  2. Remove perfect forwarding and move anyway, use on_my_write(int a, int b, CB_T& cb) and use std::move(cb) inside to consume it. Be careful, this is exactly the case in which the new functor should be called just once.
  3. Keep perfect forwarding but do not use it for this call which can be achieved by manually adding a reference:
     std::bind(&on_my_write<CB_T&>, std::placeholders::_1, std::placeholders::_2, std::forward<CB_T>(cb));
    This will work thanks to reference collapsing rules and on_my_write(int a, int b, CB_T& cb) will be called always. You can still use 2. and move anyway.
  4. Choose me Forget std::bind , use lambda:
     template<typename CB_T, std::enable_if_t<std::is_constructible<cb_t, CB_T>::value, bool> = true> void foo(CB_T&& cb) { auto b = [&cb](auto a,auto b){return on_my_write(a,b,std::forward<CB_T>(cb));}; b(1, 2); }
    This will forward cb properly, an extra advantage of this solution is that you do not have to deduce on_my_write manually.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM