嵌套绑定表达式

时间:2010-04-29 20:25:52

标签: c++ c++11 bind

这是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;
}

我在C ++标题中深陷了编译错误:

  

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)应该执行func(doit(foo))。在实际代码中,它打包了要执行的线程的函数。 func将由另一个线程执行该函数,它们介于两者之间以检查未处理的异常并进行清理。但func_wrapper中的额外绑定会让事情变得混乱......

2 个答案:

答案 0 :(得分:2)

现在第二次看这个,我想我对你看到的第一个错误有一个可信的解释。

在这种情况下,查看完整错误和导致它的模板实例更有帮助。例如,我的编译器(GCC 4.4)打印的错误以以下行结束:

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 (*)()’

现在看看这个自下而上,实际的错误信息似乎是正确的;编译器推断的类型 不兼容。

第一个模板实例化,func_wrapper,清楚地显示了编译器从foo中的实际参数func_wrapper(foo)中推断出的类型。我个人认为这是一个函数指针,但它实际上是一个函数引用

第二个模板实例化几乎不可读。但是稍微弄乱了std::bind,我了解到GCC的文本表示格式打印为绑定仿函数大致是:

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

将它拆开:

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

这是不兼容类型的起始位置。显然,即使c中的func_wrapper是函数引用,它也会在传递给std::bind后变成函数指针,从而导致类型不兼容。对于它的价值,std::forward在这种情况下根本不重要。

我的理由是std::bind似乎只关心价值观,而不是引用。在C / C ++中,没有函数值这样的东西;只有引用和指针。因此,当取消引用函数引用时,编译器只能有意义地为您提供函数指针。

对此唯一的控制是模板参数。您必须告诉编译器您正在处理一个函数指针从头开始才能使其工作。无论如何,这可能是你的想法。为此,请为模板参数C明确指定所需的类型:

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

或者更简洁的解决方案,明确地采用函数的地址:

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

如果我弄清楚第二个错误,我会回复你的。 :)

答案 1 :(得分:2)

首先,请让我介绍两个要点:

  • a :当使用嵌套的std :: bind时,首先计算内部std :: bind,并在外部std :: bind时将返回值替换为其位置被评估。这意味着std::bind(f, std::bind(g, _1))(x)执行与f(g(x))相同。如果外部std :: bind想要一个仿函数而不是一个返回值,那么内部std :: bind应该被std :: ref包装。

  • b :使用std :: bind无法将r值引用正确转发到该函数。 reason已经详细说明了。

所以,让我们看一下这个问题。这里最重要的功能可能是func_wrapper,它旨在实现3个目的:

  1. 首先完美地将仿函数转发到doit函数模板
  2. 然后使用std :: bind将doit作为闭包,
  3. 让func函数模板最后执行std :: bind返回的仿函数。
  4. 根据b点,目的1无法实现。所以,让我们忘记完美转发和doit函数模板必须接受l值引用参数。

    根据a点,目的2将使用std :: ref。

    执行

    因此,最终版本可能是:

    #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;
    }
    

    但是,如果你真的想达到目的1和2,怎么样?试试这个:

    #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
    }
    

    让我解释一下:

    • 要减少大量的return语句,将functor签名从int()更改为void()。
    • 2个run()函数模板用于检查原始仿函数参数是否完美转发。
    • dispatcher_traits将bool常量映射到type。
    • 你最好将dispatcher :: forward命名为与dispatcher :: dispatch不同,或者你必须使用dispatcher :: forward的签名来调用std :: bind模板。