【问题标题】:Nested bind expressions嵌套绑定表达式
【发布时间】:2010-04-29 20:25:51
【问题描述】:

这是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 (&amp;)()’ from expression of type ‘int (*)()’
functional:1137: error: conversion from ‘int’ to non-scalar type ‘std::_Bind&lt;std::_Mem_fn&lt;int (bar::*)(int)&gt;(bar, int)&gt;’ requested

func_wrapper(foo) 应该执行 func(doit(foo))。在实际代码中,它打包函数以供线程执行。 func 将由另一个线程执行的函数, doit 位于两者之间以检查未处理的异常并进行清理。但是 func_wrapper 中的附加绑定搞砸了……

【问题讨论】:

  • 也许删除 C++ 标签?这是直接的 C++0x
  • 保留两个标签。 C++0x 也是 C++。
  • 试一试 PC-Lint(来自 www.gimpel.com)。它非常擅长提供比 Visual Studio 更好、更详细的错误消息。但是,它相当昂贵。

标签: c++ c++11 bind


【解决方案1】:

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

在这种情况下,查看完整的错误以及导致该错误的模板实例化会更有帮助。例如,我的编译器 (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,清楚地显示了编译器从func_wrapper(foo) 中的实际参数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 (&)()

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

我的理由是std::bind 似乎只关心值,而不关心引用。在 C/C++ 中,没有函数值之类的东西。只有引用和指针。所以当函数引用被解引用时,编译器只能有意义地给你一个函数指针。

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

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

或者更简洁的解决方案,显式获取函数的地址:

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

如果我发现第二个错误,我会尽快与您联系。 :)

【讨论】:

    【解决方案2】:

    一开始,请让我介绍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 返回的函子。

    根据 b 点,目的 1 无法实现。所以,让我们忘记完美转发,doit 函数模板必须接受左值引用参数。

    根据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
    }
    

    让我解释几点:

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

    【讨论】:

      猜你喜欢
      • 2013-12-18
      • 1970-01-01
      • 2018-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多