2015-06-14 43 views
2

對於具有表達式模板的類,在重載運算符的返回類型演繹期間,我偶然發現了以下錯誤。下面的例子說明了錯誤:decltype,重載運算符的遞歸類型演繹

template < typename T > 
struct A_Number { 
    T x; 
}; 


// TAG1 
template < typename T > 
A_Number<T> 
operator+(const A_Number<T> &a, const A_Number<T> &b) 
{ 
    return {a.x + b.x}; 
} 

// TAG2 
template < typename T, typename S > 
A_Number<T> 
operator+(const A_Number<T> &a, const S &b) 
{ 
    return {a.x + b}; 
} 

// TAG3 
template < typename T, typename S > 
auto 
operator+(const S &b, const A_Number<T> &a) -> decltype(a + b) 
//              ^^^^^ 
{ 
    return a + b; 
} 

int 
main(void) 
{ 
    auto x1 = A_Number<int>{1}; 
    auto x2 = A_Number<int>{1}; 

    auto res1 = x1 + 1; // instantiates TAG2 

    auto res2 = 1 + x1; // instantiates TAG3, TAG2 

    auto res3 = x1 + x2; // error, tries to match TAG3 
    return EXIT_SUCCESS; 
} 

當試圖相剋編譯此++ - 5或鐺++,我得到這個錯誤

fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) 
operator+(const S &b, const A_Number<T> &a) -> decltype(a + b) 

顯然,編譯器嘗試匹配版本TAG3,雖然有更好的匹配(TAG1)可用。當試圖匹配時,他們試圖推導出似乎導致TAG3遞歸實例化的返回類型。爲什麼返回類型扣除沒有看到其他(更好的匹配)重載?即使另一個重載的模板函數具有更好的匹配簽名,推導返回類型是否正確?

有趣的是,這個錯誤在稀薄的空氣中蒸發,省略當返回類型完全與c++14編譯,就像這樣:

// TAG3 
template < typename T, typename S > 
auto 
operator+(const S &b, const A_Number<T> &a) // C++14 
{ 
    return a + b; 
} 

誠然,這是一個學術問題,因爲解決方法是可能的。但任何人都可以闡明這種行爲是標準符合還是編譯器錯誤?

回答

0

這實際上是編譯器部分的正確行爲。重載決策有兩個步驟,從[over.match]:

— First, a subset of the candidate functions (those that have the proper number of arguments and meet certain other conditions) is selected to form a set of viable functions (13.3.2).
— Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed to match each argument to the corresponding parameter of each viable function.

x1 + x2候選人功能是:

// TAG1 
template < typename T > 
A_Number<T> 
operator+(const A_Number<T> &a, const A_Number<T> &b); 

// TAG2 
template < typename T, typename S > 
A_Number<T> 
operator+(const A_Number<T> &a, const S &b); 

// TAG3 
template < typename T, typename S > 
auto operator+(const S &b, const A_Number<T> &a) -> decltype(a + b) 

這樣就OK了,我們需要確定返回類型是什麼TAG3第一,然後我們選擇最佳的可行候選人。現在,在這裏,operator+是一個從屬名稱,因爲它取決於兩個模板參數。因此,根據[temp.dep.res]:

In resolving dependent names, names from the following sources are considered:
— Declarations that are visible at the point of definition of the template.
— Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.

因此,要解決的TAG3返回類型,我們需要做a+b查找和重載解析。這給了我們同樣的三位候選人(前兩個人找到了通常的方式,TAG3不再那麼有助於通過ADL找到),所以我們周圍和周圍都走了。

你的解決方案是:

  • 在C++ 14,落尾返回類型TAG3。這將停止旋轉木馬,我們將從我們的三個可行的選擇開始,其中TAG1是最專業的,因此它將被選爲最佳可行候選人。
  • 如果SA_Number<T>,則防止TAG3被實例化。例如,我們可以爲A_Number創建類型特點:

    template <typename T> 
    struct is_A_Number : std::false_type { }; 
    
    template <typename T> 
    struct is_A_Number<A_Number<T>> : std::true_type { }; 
    
    // TAG3 
    template < typename T, typename S, 
          typename = typename std::enable_if<!is_A_Number<S>::value>::type> 
    auto operator+(const S &b, const A_Number<T> &a) -> decltype(a + b); 
    

這將在C++ 11編譯。