2010-04-29 132 views
13

這是對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(富)應該執行FUNC(DOIT(富) )。在真正的代碼中,它封裝了要執行的線程的函數。函數會由另一個線程執行的函數,doit坐在中間檢查未處理的異常並清理。但在func_wrapper附加綁定食堂的事情了......

+0

也許刪除C++標記?這是直的C++ 0x – Anycorn 2010-04-29 20:33:33

+10

保留這兩個標籤。 C++ 0x也是C++。 – jalf 2010-04-29 22:44:22

+0

給PC-Lint一個旋風(來自www.gimpel.com)。它比Visual Studio提供更好更詳細的錯誤消息非常好。但是,它非常昂貴。 – 2010-05-14 10:55:52

回答

2

現在這個第二次看,我覺得我對你所看到的第一個錯誤plausable解釋。

在這種情況下,它更有助於看完整的錯誤,並導致了它的模板實例。通過我的編譯器(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 (&)() 

這是不兼容類型開始。顯然,即使在cfunc_wrapper是一個函數的參考,它變成一次傳遞給std::bind函數指針,造成類型不兼容。在這種情況下,std::forward完全不重要。

我在這裏的理由是,std::bind似乎只關心價值,而不是引用。在C/C++中,函數的值不存在;只有引用和指針。所以當函數引用被取消引用時,編譯器只能有意義地給你一個函數指針。

你有過這種情況的唯一控制是你的模板參數。您將不得不告訴編譯器,您正在處理從開始開始的函數指針以完成此工作。無論如何,這可能就是你想到的。要做到這一點,明確指定要用於模板參數C類型:

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

或者更簡單的解決方案,明確地把函數的地址:

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

我會彷彿回到了你我曾經弄清楚第二個錯誤。 :)

1

在開始的時候,請讓我介紹2關鍵點:

  • 一個:當使用嵌套的std ::綁定,內部的std ::綁定首先計算,並且返回在評估外部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. 完美轉發函子起初大一函數模板,
  2. 然後使用std ::綁定,使DOIT爲關閉,
  3. 並讓func函數模板最後執行std :: bind返回的函子。

根據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,怎麼樣?試試這個:

讓我解釋一下幾點:

  • 爲了減少大量return語句的,從int改變仿簽名()爲void()。
  • 2 run()函數模板用於檢查原始仿函數參數是否完美轉發。
  • dispatcher_traits將要將bool常量映射到類型。
  • 您最好將dispatcher :: forward命名爲dispatcher :: dispatch,或者您必須使用dispatcher :: forward的簽名來調用std :: bind模板。