user328543
user328543

Reputation: 587

Nested bind expressions

This is a followup question to my previous question.

#include <functional>

int foo(void) {return 2;}

class bar {
public:
    int operator() (void) {return 3;};
    int something(int a) {return a;};
};

template <class C> auto func(C&& c) -> decltype(c()) { return c(); }

template <class C> int doit(C&& c) { return c();}

template <class C> void func_wrapper(C&& c) { func( std::bind(doit<C>, std::forward<C>(c)) ); }

int main(int argc, char* argv[])
{
    // call with a function pointer
    func(foo);
    func_wrapper(foo);  // error

    // call with a member function
    bar b;
    func(b);
    func_wrapper(b);

    // call with a bind expression
    func(std::bind(&bar::something, b, 42));
    func_wrapper(std::bind(&bar::something, b, 42)); // error

    // call with a lambda expression
    func( [](void)->int {return 42;} );
    func_wrapper( [](void)->int {return 42;} );

    return 0;
}

I'm getting a compile errors deep in the C++ headers:

functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’
functional:1137: error: conversion from ‘int’ to non-scalar type ‘std::_Bind<std::_Mem_fn<int (bar::*)(int)>(bar, int)>’ requested

func_wrapper(foo) is supposed to execute func(doit(foo)). In the real code it packages the function for a thread to execute. func would the function executed by the other thread, doit sits in between to check for unhandled exceptions and to clean up. But the additional bind in func_wrapper messes things up...

Upvotes: 15

Views: 3442

Answers (2)

Matt Yang
Matt Yang

Reputation: 659

At the beginning, please let me introduce 2 key points:

  • a: When using nested std::bind, the inner std::bind is evaluated first, and the return value will be substituted in its place while the outer std::bind is evaluated. That means std::bind(f, std::bind(g, _1))(x) executes as same as f(g(x)) does. The inner std::bind is supposed to be wrapped by std::ref if the outer std::bind wants a functor rather than a return value.

  • b: The r-value reference cannot be correctly forwarded to the function by using std::bind. And the reason has already been illustrated in detail.

So, let's look at the question. The most importance function here might be func_wrapper which is intended to perform 3 purposes:

  1. Perfect forwarding a functor to doit function template at first,
  2. then using std::bind to make doit as a closure,
  3. and letting func function template execute the functor returned by std::bind at last.

According to point b, purpose 1 cannot be fulfilled. So, let's forget perfect forwarding and doit function template has to accept a l-value reference parameter.

According to point a, purpose 2 will be performed by using std::ref.

As a result, the final version might be:

#include <functional>

int foo(void) {return 2;}

class bar {
public:
    int operator() (void) {return 3;};
    int something(int a) {return a;};
};

template <class C> auto func(C&& c) -> decltype(c()) { return c(); }

template <class C> int doit(C&/*&*/ c)    // r-value reference can't be forwarded via std::bind
{
    return c();
}

template <class C> void func_wrapper(C&& c)
{
    func(std::bind(doit<C>,
                   /* std::forward<C>(c) */ // forget pefect forwarding while using std::bind
                   std::ref(c)) // try to pass the functor itsself instead of its return value
        );
}

int main(int argc, char* argv[])
{
    // call with a function pointer
    func(foo);
    func_wrapper(foo);  // error disappears

    // call with a member function
    bar b;
    func(b);
    func_wrapper(b);

    // call with a bind expression
    func(std::bind(&bar::something, b, 42));
    func_wrapper(std::bind(&bar::something, b, 42)); // error disappears

    // call with a lambda expression
    func( [](void)->int {return 42;} );
    func_wrapper( [](void)->int {return 42;} );

    return 0;
}

But, if you really want to achieve purpose 1 and 2, how? Try this:

#include <functional>
#include <iostream>

void foo()
{
}

struct bar {
    void operator()() {}
    void dosomething() {}
};

static bar b;

template <typename Executor>
void run(Executor&& e)
{
    std::cout << "r-value reference forwarded\n";
    e();
}

template <typename Executor>
void run(Executor& e)
{
    std::cout << "l-value reference forwarded\n";
    e();
}

template <typename Executor>
auto func(Executor&& e) -> decltype(e())
{
    return e();
}

template <bool b>
struct dispatcher_traits {
    enum { value = b };
};

template <typename Executor, bool is_lvalue_reference>
class dispatcher {
private:
    static void dispatch(Executor& e, dispatcher_traits<true>)
    {
        run(e);
    }

    static void dispatch(Executor& e, dispatcher_traits<false>)
    {
        run(std::ref(e));
    }

public:
    static void forward(Executor& e)
    {
        dispatch(e, dispatcher_traits<is_lvalue_reference>());
    }
};

template <typename Executor>
void func_wrapper(Executor&& e)
{
    typedef dispatcher<Executor,
                       std::is_lvalue_reference<Executor>::value>
        dispatcher_type;

    func(std::bind(&dispatcher_type::forward, std::ref(e)));
}

int main()
{
    func_wrapper(foo);   // l-value
    func_wrapper(b);  // l-value
    func_wrapper(bar());  // r-value
    func_wrapper(std::bind(&bar::dosomething, &b));  // r-value
    func_wrapper([](){});  // r-value
}

Let me explain some points:

  • To reduce lots of return statements, changing functor signature from int() to void().
  • The 2 run() function templates are used to check whether the original functor parameter is perfect forwarded or not.
  • dispatcher_traits is going to map bool constant to type.
  • You'd better name dispatcher::forward to differ from dispatcher::dispatch or you have to invoke std::bind template with dispatcher::forward's signature.

Upvotes: 3

St&#233;phan Kochen
St&#233;phan Kochen

Reputation: 19943

Looking at this the second time now, and I think I have a plausable explanation for the first error you are seeing.

In this case, it's more helpful to look at the complete error and the template instantiations that lead up to it. The error printed by my compiler (GCC 4.4), for example, ends with the following lines:

test.cpp:12:   instantiated from ‘decltype (c()) func(C&&) [with C = std::_Bind<int (*(int (*)()))(int (&)())>]’
test.cpp:16:   instantiated from ‘void func_wrapper(C&&) [with C = int (&)()]’
test.cpp:22:   instantiated from here
/usr/include/c++/4.4/tr1_impl/functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’

Now looking at this bottom-up, the actual error message seems correct; the types the compiler has deduced are incompatible.

The first template instantiation, at func_wrapper, clearly shows what type the compiler has deduced from the actual parameter foo in func_wrapper(foo). I personally expected this to be a function pointer, but it is in fact a function reference.

The second template instantiation is hardly readable. But messing around with std::bind a bit, I learned that the format of the textual representation GCC prints for a bind functor is roughly:

std::_Bind<RETURN-TYPE (*(BOUND-VALUE-TYPES))(TARGET-PARAMETER-TYPES)>

So tearing it apart:

std::_Bind<int (*(int (*)()))(int (&)())>
// Return type: int
// Bound value types: int (*)()
// Target parameter types: int (&)()

This is where the incompatible types start. Apparently, even though c in func_wrapper is a function reference, it turns into a function pointer once passed to std::bind, resulting in the type incompatibility. For what it's worth, std::forward doesn't matter at all in this case.

My reasoning here is that std::bind only seems to care about values, and not references. In C/C++, there's no such thing as a function value; there's only references and pointers. So when the function reference is dereferenced, the compiler can only meaningfully give you a function pointer.

The only control you have over this is your template parameters. You will have to tell the compiler that you're dealing with a function pointer from the start to make this work. It's probably what you had in mind anyways. To do that, explicitly specify the type you want for the template parameter C:

func_wrapper<int (*)()>(foo);

Or the more brief solution, explicitly take the function's address:

func_wrapper(&foo); // with C = int (*)()

I'll get back to you if I ever figure out the second error. :)

Upvotes: 2

Related Questions