標籤:

C++為什麼函數參數個數不同還能出錯啊?

template&
struct Trait{
using Type = typename T::Value;
};

template&
typename Trait&::Type f(const T t,const U u){
return t.v;
}

template&
void f(const Vv) {}

int main(){
f&(1);
}

這也能報錯?

x.cpp: In instantiation of 『struct Trait&』:
x.cpp:8:1: required by substitution of 『template& typename Trait&::Type f(const T, const U) [with T = int; U = int]』
x.cpp:18:15: required from here
x.cpp:3:29: error: 『int』 is not a class, struct, or union type
typedef typename T::Value Type;
^


如果代碼里寫的是

f&(1);

第一步:得知調用的是 f 這個函數,於是把名字相同的函數 / 模板全部作為候選,共兩個候選:

// 名字叫 f,有效
template&
void f(const Vv);

// 名字叫 f, 有效
template&
typename Trait&::Type f(const T t,const U u);

第二步:代入顯示指定的模板參數 + 檢查代入有效性:

首先是代入:

// 代入完成,還剩一個參數,在下一步中推導
template&
void f(const V v);

// 代入完成
typename Trait&::Type f(const int t, const int u);

然後是有效性檢查:

// 沒問題,有效
template&
void f(const V v);

// 需要實例化 Trait& 才能知道返回值是否有效
typename Trait&::Type f(const int t, const int u);

於是專門為了檢查後者加入了「實例化 Trait&」的步驟:

// 實例化出錯,int 里不存在 Value
struct Trait {
using Type = typename int::Value;
};

這就是 GCC 的邏輯,也是符合標準的。此處 Trait 的實例化,並不是 f 本身的實例化,而是 f 實例化過程中的副作用。所以 Trait 實例化出錯不適用 SFINAE,直接報錯是合理合法的。

---

Clang 不報錯的依據在於標準中有這樣的描述:

If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

在重載決議過程中,如果無需實例化類模板就可以判定正確的函數,是否一定要做實例化未說明

注意加粗部分。標準允許在某些情況下跳過實例化,但是不要求。

p.s. 直至今日,這一條款仍然因為措辭含糊不清而受到爭議:C++ Standard Core Language

Active Issues

---

所以我認為這不是編譯器 Bug,而是代碼利用了未說明的行為。


因為實例化Trait&不在immediate context所以SFINAE不適用。

看標準是怎麼說的,注意加粗部分:

14.8.2p8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is
one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note:
If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution
process. — end note ] Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types
and expressions can result in side effects such as the instantiation of class template specializations and/or
function template specializations, the generation of implicitly-defined functions, etc. Such side effects are
not in the 「immediate context」 and can result in the program being ill-formed. — end note ]

所以實例化Trait&失敗是hard error。確實如果寫成下面這樣是可以編譯通過的:

template&
typename T::Value f(const T t,const U u){
return t.v;
}

template&
void f(const Vv) {}

int main(){
f&(1);
}

那clang為啥過了呢。。。根據爆棧這個帖子C++ inconsistency between gcc and clang。clang的人覺得我可以不實例化Trait就確定你要的是哪個overload那我就不真的實例化Trait於是就沒有hard error了,根據的是這條:

14.7.1p7 If the overload resolution process can determine the correct function to call without instantiating a class
template definition, it is unspecified whether that instantiation actually takes place.

其實根據這條gcc的做法沒有錯,這個行為是unspecified。

另外,判斷參數個數能不能對的上是發生在模版函數的模版參數類型推導之後的,按照標準應該先找到所有函數名對的上的函數包括模版函數,然後再看哪個可以用哪個最好。所以不能說明明參數數量都不對還要考慮它。

=== update ===

好吧關於最後一段多說兩句,function overload resolution是這樣搞的:

1. 先把所有函數名match的都收集起來,只看函數名。這個是candidate set

2. 把參數數量對不上的刪掉,參數多的多出的部分必須有默認參數,少的必需是...參數(e.g. printf)。這個是viable set

3. 在viable set裡面找墜吼的那個。

具體這個case裡面,由於第一步會做模版函數的模版參數類型推導,然後導掛了,就沒有後面的事情了,參數數量對不上這種根本還沒來得及考慮。


gcc bug,正常的情況下,應該是去找

template&
void
f(const Vv){
}

然後變為T = int, U = int, V = int 也就是由f的參數推導出V的類型,類似於f&(1); 但是GCC去匹配了第二個,然後就沒有向下走了,並沒有由參數1去找第三個模板參數。

若是把代碼改為f&(1)的話,GCC也就過了。


看起來像是GCC的bug。

GCC在lookup order上出問題是它一貫的傳統。


VC++2013毫無壓力


答案已經被打臉。

/********************************/

我的感覺是bug,又不是沒有viable candidate不應該掛在這一步

話說這種問題上爆棧網問比較好吧


你這個好詭異啊:

f&

會實例化一份:

Trait&

然後Trait裡面會要求有:

int::Value

這是什麼鬼?


標準答案參見 @邱昊宇和 @Wang Shuai 的回答。

下面的答案是錯誤的。

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

只提供了兩個模板參數必然實例化第二個函數模板:

返回值的Trait沒有對int做特化,int類型本身也沒有Value子類型

函數本身需要兩個參數,只提供了一個

錯的這麼明顯,編譯的log也說的挺清楚了,不明白有什麼可問的…


VS2015裡面這麼寫

#include&

using namespace std;

template&
struct Trait{
typedef typename T::Value Type;
};

template&
typename Trait&::Type
f(const T t,const U u){
cout&<&<"in template&"&<&
void
f(const Vv){
cout&<&<"in template&"&<&(1);
}

會輸出

E: est&>cl /EHsc test.cpp
用於 x86 的 Microsoft (R) C/C++ 優化編譯器 19.00.23026 版
版權所有(C) Microsoft Corporation。保留所有權利。

test.cpp
Microsoft (R) Incremental Linker Version 14.00.23026.0
Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe
test.obj

E: est&>test.exe
in template&

E: est&>

哪位大大能解釋下為啥會這樣子嗎〒_〒


推薦閱讀:

依賴C++的情況下該如何選擇做GUI界面的框架?
C語言和C++ C#的區別在什麼地方?
如何評價 Christopher Kohlhoff 實驗性的 C++ Network 標準庫 ?
C++ 怎麼生成 4096 個函數?
C 與 C++ 的真正區別在哪裡?

TAG:C |