2015-06-09 47 views
4

據我所知,如何將std :: bind作爲通用引用類型傳遞?

  1. std::bind完全向前既它包裝可調用對象和參數到該調用對象;
  2. std::bind返回對象本身是可移動和/或可複製的,這取決於可調用對象及其參數是可移動和/或可複製的;
  3. a std::bind返回對象可能是嵌套的,在這種情況下,外部返回對象是可移動和/或可複製的,就像綁定其他可調用對象時一樣。

因此,我期望下面的代碼片段編譯好。相反,代碼會在main()的最後兩條語句中生成編譯器錯誤。

#include <functional> 

template<typename HandlerType> 
void call_handler(HandlerType&& handler) 
{ 
    handler(); 
} 

template<typename HandlerType> 
void do_something(HandlerType&& handler) 
{ 
    auto f = std::bind(
    &call_handler<HandlerType&>, 
    std::forward<HandlerType>(handler)); 
    f(); 
} 

int main() 
{ 
    auto a = [&]() {}; 
    do_something(a); 
    do_something(std::move(a)); 

    auto b = std::bind([&]() {}); 
    do_something(b);    // <- compiler error! 
    do_something(std::move(b)); // <- compiler error! 
} 

兩個問題行中的每一個都會在沒有另一個的情況下發生錯誤。爲了消除所有的錯誤,我必須註釋掉這兩行。

這裏的一個樣本誤差,從G ++ 4.9.2在Cygwin中,在調用f()do_something()

(4 of 103): error: no match for call to ‘(std::_Bind<void (*(std::_Bind<main()::<lambda()>()>))(std::_Bind<main()::<lambda()>()>&)>)()’ 

下面是從Visual Studio 2013的樣品誤差,在同一行:

1>C:\Program Files (x86)\Microsoft Visual Studio12.0\VC\include\functional(1149): error C2664: 'void (HandlerType)' : cannot convert argument 1 from 'void' to 'std::_Bind<false,void,main::<lambda_2b8ed726b4f655ffe5747e5b66152230>,> ' 

發生了什麼事?我誤解了std::bind

具體地,如何能我

  1. 綁定一個可調用對象?和
  2. 通過那std::bind返回對象到一個函數採取通用引用?和
  3. 巢那std::bind返回對象在另一個std::bind

我的目標是底層的可調用對象及其參數被完美轉發。

編輯:爲了澄清,我想通過引用傳遞包裝的可調用對象,按價值計算它的參數,沒有,所以std::ref不會幫助,至少,不是一個完整的解決方案。其原因是,我真正的代碼更加複雜,包括使fstd::bind返回的對象跨線程邊界,並且兩個abstd::bind返回對象可以走出去的範圍在原來的線程之前call_handler電話f(),所以ab需要複製或移入f,不能僅僅是引用。這就是說,我的問題特別是關於std::bind和完美的轉發,並且爲了提出一個好問題,我已經提取了一些不需要的東西來重現我提到的特定編譯器錯誤。

+2

的std ::綁定的值取參數,可以使用std ::裁判通過引用 –

+0

@Dieter我編輯我的問題,以顯示我特別希望複製或移動,不使用引用。不過謝謝你。 –

回答

5

您的假設1錯了,bind始終將綁定參數作爲左值傳遞給它正在包裝的可調用對象。爲了證明這一點,內do_something改變bind表達以下

auto f = std::bind(
    &call_handler<decltype(handler)>, 
    std::forward<HandlerType>(handler)); 

下面的行會失敗,編譯

do_something(std::move(a)); 

因爲decltype(handler)是右值引用,但bind會嘗試調用call_handler與您在main中通過它的綁定lambda表達式的左值引用。


現在在你的例子的後半部分出了什麼問題。 bind對嵌套的bind表達式有特殊處理,它將識別和評估。然而,在你的例子中,你不希望發生這種情況。相反,您希望將嵌套bind按原樣轉發到call_handler,然後調用它。

Boost提供了boost::protect,它允許您屏蔽嵌套bind的實際類型,從而防止其通過外部bind進行評估。

不幸的是,沒有std::protect等價物,但write it yourself並不難。

template<typename T> 
struct protect_wrapper : T 
{ 
    protect_wrapper(const T& t) : T(t) 
    {} 

    protect_wrapper(T&& t) : T(std::move(t)) 
    {} 
}; 

template<typename T> 
std::enable_if_t<!std::is_bind_expression<std::decay_t<T>>::value, 
       T&& 
       > 
protect(T&& t) 
{ 
    return std::forward<T>(t); 
} 

template<typename T> 
std::enable_if_t<std::is_bind_expression<std::decay_t<T>>::value, 
       protect_wrapper<std::decay_t<T>> 
       > 
protect(T&& t) 
{ 
    return protect_wrapper<std::decay_t<T>>(std::forward<T>(t)); 
} 

只是包裝自己內心bind表達protect,你的代碼將編譯。

auto b = protect(std::bind([&]() {})); 
do_something(b); 
do_something(std::move(b)); 

Live demo

+0

謝謝!如果我理解正確,'bind'完全轉發可調用參數,但對於通過引用傳遞給可調用對象的任何參數,這些參數作爲左值引用傳遞,並且永遠不會右值引用 - 無論參數是作爲左值還是作爲左值綁定或右值。 –

+0

@Craig否,'bind' * always *將綁定參數作爲左值傳遞,* never從不作爲rvalues,而不管可調用函數中的相應參數類型是否是左值/右值/轉發引用。 [Here's](https://stackoverflow.com/a/30084685/241631)我的另一個答案顯示,在將綁定參數傳遞給可調用對象時,綁定參數使綁定參數「移動」的醜陋。 – Praetorian

+0

謝謝,你幫了我很多。 [這是一個答案](http://stackoverflow.com/a/28509110/1094609)顯示了一個類似的解決方法,你鏈接到一個獲得'綁定'傳遞參數作爲右值。總的來說,我現在更好的理解爲什麼Scott Meyers建議我們更喜歡lambdas到'std :: bind'。 –

相關問題