How to boost::bind a template member function which takes a universal reference as a parameter

306 Views Asked by At

I've been trying to use boost::bind to post a call to a member function onto an io_strand but have been getting errors. I have manged to create a simplistic equivalent example of what I am trying to do and have seen the same error in the following context:

I have the following class containing the doThings() member function I want to call:

class base
{
public:
  int x = 1;

  template<typename B>
  void doThings(B&& b)
  {}
};

There is then a subclass of this (to accurately represent the scenario I am encountering my error - I don't think it makes a difference)

class child : public base
{
  int y = 2;
};

I have the following code trying to make the boost::bind call:

template<typename A>
void getInt(child c, A&& a)
{
  boost::bind((void(child::*)(A)) &child::doThings, &c, std::forward<A>(a))();
}

And then this is being called as follows:

int main()
{
  child c = child();
  getInt(c, 7);
}

When I compile the above code I get the following error:

error: no matches converting function ‘doThings’ to type ‘void (class child::*)(int)’


If I change the function signature of doThings() to take a regular B type rather than a universal reference i.e. B rather than B&& then it compiles a runs with no issues.
I suspect my issue is something to with the cast I'm doing in getInt():

(void(child::*)(A))

But I don't know what I would need to change it to. A&& wouldn't work in this context as I believe it would represent an r-value reference in that context. The compilation error I get when I try it seems to confirm this:

error: cannot bind ‘int’ lvalue to ‘int&&’

For completeness: if I don't attempt to perform a cast then I get the following compilation error:

error: no matching function for call to ‘bind(unresolved overloaded function type, child*, int)’

Could someone please enlighten me on what I would need to do in order to make my boost::bind call valid in this scenario please?

I am using C++11

1

There are 1 best solutions below

1
Vittorio Romeo On BEST ANSWER

I would suggest against using boost::bind, as lambda expressions can be used to cleanly bind arguments (avoiding many pitfalls of bind explained in this talk by STL).


I'm assuming that you want to:

  • Capture a by move if an rvalue-reference is passed to getInt.

  • Capture a by reference if an lvalue-reference is passed to getInt.

I'm also assuming that:

  • A is not int in your real code, otherwise perfect-forwarding would not make sense.

  • You want to avoid unnecessary copies of a or that A could be a move-only type.

  • You only have access to C++11 (and not to newer standards).

If you need to "perfectly-capture" a (i.e. capture-by-move if A is an rvalue-reference, capture-by-reference if A is an lvalue-reference), you need some sort of wrapper.

Unfortunately this is non-trivial, even though it gets better in C++14 and C++17. Here's an example of how the final syntax could look:

template<typename A>
void getInt(child c, A&& a)
{
    // `a_wrapper` stores an `A&` or an `A` depending on the value category
    // of `a`. Copying it should not copy `a` - it should conditionally move 
    // it depending on the original value category of `a`.
    auto a_wrapper = forward_capture_wrapper(std::forward<A>(a));

    // `forward_like` retains information about `a`'s value category so that
    // it can be used in the body of the lambda to forward the reference/value
    // stored inside `a_wrapper`.
    //                          vvvvvvvvvvvvvvv
    [&a, a_wrapper]{ c.doThings(forward_like<A>(a_wrapper.get()); }();
    //                                          ^^^^^^^^^^^^^^^
    // `a_wrapper.get()` returns a reference that can then be moved or passed
    // to `c.doThings`.
}

As you can see, you need a template function called forward_capture_wrapper that deals with "perfect-capture". You can find information on how to implement that in these resources:

By combining the resources above you should be able to implement a "perfect capture wrapper" in C++11.

You also need a forward_like helper function that preserves the original value category of the a argument. You can find an implementation of that: