2017-09-10 24 views
2

當試圖編譯此,令人驚訝,它提供了一個錯誤,因爲lambda函數的參數auto已經解決,std::string,編譯器在撥打test時,不知道如何將std::string轉換爲intWidget函數的std ::功能和通用lambda表達式重載:的std :: string優於INT

但是,我不知道爲什麼,編譯器已經choosen第二invok函數,而不是第一個,當第一個會成功的:

#include <string> 
#include <functional> 

struct Widget {}; 

bool test(int); 
bool test(Widget); 

void invok(std::function<bool(int)>);   // #1 
void invok(std::function<bool(std::string)>); // #2 

int main() 
{ 
    // error: unresolved overloaded function type 
    // invok(test);    

    // still error: no known conversion from std::string to 
    // int or Widget 
    invok([](auto&& x) {  
     return test(std::forward<decltype(x)>(x)); 
    }); 

} 

這個例子已經從a C++ proposal複製。

+3

啊,SFINAE-不友好的通用lambdas。 –

+1

我贊同這個問題。 – Barry

回答

3

編譯器沒有選擇#2。它試圖決定它是否可以選擇#2。

要做到這一點,它會問「這個通用lambda可以轉換爲std::function<bool(std::string)>」嗎?

std::function的轉換構造函數說「僅當它可用std::string右值調用,結果類型可轉換爲bool」。

編譯器嘗試,推導出autostd::string,代入函數調用操作符的簽名...成功!糟糕,返回類型是auto,它需要一個實際類型來回答「可轉換」問題。所以它實例化函數調用操作符模板的主體來確定返回類型。

Ouch。畢竟,該機構對std::string無效。隨後出現嚴重錯誤和爆炸。

3

您的超載仍然不明確(有點),但是一直沿着確定代碼在嘗試兩種可能性(傳遞int或字符串)時遇到硬錯誤的方式。

若要查看歧義,請在您的lambda中添加一個->bool,以便它不必編譯正文以確定返回值。

拉姆達的主體不在某個區域內,因此取代失敗不會導致錯誤。相反,你會遇到一個很大的錯誤。

簡單的解決方法是讓你的lambda明確地取int。

如果你想有一個通用的解決方案:

#define RETURNS(...) \ 
    noexcept(noexcept(__VA_ARGS__)) \ 
    -> decltype(__VA_ARGS__) \ 
    { return __VA_ARGS__; } 

#define OVERLOADS_OF(...) \ 
    [](auto&&...args) \ 
    RETURNS(__VA_ARGS__(decltype(args)(args)...)) 

然後

invok(OVERLOADS_OF(test)); 

做(至少接近)正確的事情。

此宏將失敗從lambda主體移動到尾隨返回類型。並且失敗(該字符串不能傳遞給測試)現在發生在替換失敗不會導致錯誤(SFINAE)的上下文中。所以一切正常。

什麼是SFINAE友好的準確規則,什麼是不需要閱讀標準。然而,經驗法則認爲,編譯器不必將函數體中的錯誤視爲替換錯誤,並且訪問未定義類的內容是一個嚴重錯誤。第一,因爲它似乎是一個合理的地方畫線,並使編譯器作家更容易;第二,因爲另一種方法是瘋狂或ODR錯誤誘餌。

在實踐中,標準SFINAE規則更加神祕,最後我檢查了C++ 14,在模板類部分特化期間忽略了SFINAE:每個編譯器都支持它。但也許我誤解了它。無論如何,我使用的經驗法則與標準文本一樣有用。兩者都不會是完美的。

+0

您可以通過關於什麼是「SFINAE友好區域」的說明改善您的答案嗎? –

+0

@ Peregring-lk增加了經驗法則。 – Yakk

相關問題