2014-11-03 68 views
24

感謝decltype作爲返回類型,C++ 11使得它非常容易引入裝飾器。例如,考慮這個類:爲什麼自動返回類型改變重載分辨率?

struct base 
{ 
    void fun(unsigned) {} 
}; 

我想與額外的功能來裝飾它,因爲我會做多次與不同類型的裝飾品,我先介紹一個decorator類,簡單地轉發一切base。在真實代碼中,這是通過std::shared_ptr完成的,以便我可以刪除裝飾並恢復「裸」對象,並且所有內容都是模板化的。

#include <utility> // std::forward 
struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

完美轉發和decltype只是美好的。在真正的代碼中,我實際上使用了一個只需要函數名稱的宏,其餘都是樣板。

然後,我可以介紹一個derived類添加功能我對象(derived不當,表示同意,但它通過繼承有助於理解是derived是-A-一種base,儘管不是)。

struct foo_t {}; 
struct derived : decorator 
{ 
    using decorator::fun; // I want "native" fun, and decorated fun. 
    void fun(const foo_t&) {} 
}; 

int main() 
{ 
    derived d; 
    d.fun(foo_t{}); 
} 

然後C++ 14來了,與返回類型推演,讓寫的東西在一個簡單的方法:取出轉發功能的decltype部分:

struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

而且然後它休息。是的,至少根據GCC和Clang的,這一點:

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

不等同於這個(和問題的關鍵不是autodecltype(auto)):

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

中的重載決策似乎是完全不同的,它最終是這樣的:

clang++-mp-3.5 -std=c++1y main.cc 
main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int' 
    return b.fun(std::forward<Args>(args)...); 
       ^~~~~~~~~~~~~~~~~~~~~~~~ 
main.cc:32:5: note: in instantiation of function template specialization 
     'decorator::fun<foo_t>' requested here 
    d.fun(foo_t{}); 
    ^
main.cc:7:20: note: passing argument to parameter here 
    void fun(unsigned) {} 
       ^

我明白了失敗的原因:我的電話(d.fun(foo_t{}))不簽名完全匹配的derived::fun,這需要const foo_t&,所以非常渴望decorator::fun踢(我們知道Args&&...是如何綁定到任何不完美匹配的極其不耐煩)。所以它將此轉發給base::fun,這不能處理foo_t

如果我改變derived::fun採取的foo_t代替const foo_t&,那麼它將按預期工作,這表明確實是這裏的問題是,有derived::fundecorator::fun之間的競爭。

然而,爲什麼這個表演與返回式扣除?更準確地說,爲什麼是委員會選擇的這種行爲?

爲了方便起見,在Coliru:

謝謝!

+0

@Nawaz:謝謝你的提示。在這裏,它沒有任何區別。 – akim 2014-11-03 08:42:26

+7

'decltype(b.fun(std :: forward (args)...))'在尾隨返回類型中* *表達式SFINAE *,而'auto'和'decltype(auto)'都不會 – 2014-11-03 08:42:43

+4

要添加到Piotr S.的評論:這也是故意的。爲了使SFINAE正常工作,返回類型必須可用,而不必查看函數體。要啓用返回的lambda表達式,返回類型不能在不查看函數體的情況下可用。作出了一個決定,使返回的lambda比SFINAE更重要。 – hvd 2014-11-03 08:47:29

回答

18

只要看看這個電話:

d.fun(foo_t{}); 

您創建一個臨時(即右值),將它作爲參數傳遞給函數。現在你覺得怎麼樣?

  • 它首先嘗試綁定到參數Arg&&,因爲它可以接受右值由於無效的返回類型推演(這又是由於foo_t不能轉換成unsigned int,因爲這b.fun(std::forward<Args>(args)...)圈出現無效表達式),如果使用decltype(expr)作爲返回類型,則此功能被拒絕,因爲在這種情況下SFINAE進入畫面。但是,如果僅使用auto,那麼SFINAE不會進入畫面,並且錯誤被歸類爲導致編譯失敗的硬錯誤。

  • 如果SFINAE在第一種情況下起作用,那麼接受foo_t const&作爲參數的第二個過載被調用。

+1

啊,我還沒有意識到C++ 11的工作原因是因爲調用'base :: fun'被拒絕,並且_then_'derived :: fun'被當作第二選擇。非常感謝!然而,'decltype(auto)'怎麼解決這個問題呢?它應該被認爲是GCC和Clang中的一個bug嗎? – akim 2014-11-03 09:15:06

+1

好的,我剛剛讀了你對這個問題的評論,並且說明了一切,非常感謝! – akim 2014-11-03 09:18:53