[英]Dependent type or argument in decltype in function definition fails to compile when declared without decltype
我一直在使用推定的返回類型定義來解析為與聲明相同的類型。 這有效:
template <typename>
struct Cls {
static std::size_t f();
};
template <typename T>
decltype(sizeof(int)) Cls<T>::f() { return 0; }
但是如果我通過將sizeof(int)
替換為sizeof(T)
來將定義更改為應該等效的內容, 它將失敗
template <typename T>
decltype(sizeof(T)) Cls<T>::f() { return 0; }
gcc的錯誤(叮當聲幾乎相同):
error: prototype for ‘decltype (sizeof (T)) Cls<T>::f()’ does not match any in class ‘Cls<T>’
decltype(sizeof(T)) Cls<T>::f() { return 0; }
^~~~~~
so.cpp:4:24: error: candidate is: static std::size_t Cls<T>::f()
static std::size_t f();
^
函數參數類型也會出現相同的問題:
template <typename>
struct Cls {
static void f(std::size_t);
};
template <typename T>
void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead
奇怪的是,如果聲明和定義匹配並且都使用decltype(sizeof(T))
則編譯成功,並且我可以static_assert
聲明返回類型為size_t
。 以下將成功編譯:
#include <type_traits>
template <typename T>
struct Cls {
static decltype(sizeof(T)) f();
};
template <typename T>
decltype(sizeof(T)) Cls<T>::f() { return 0; }
static_assert(std::is_same<std::size_t, decltype(Cls<int>::f())>{}, "");
用另一個示例進行更新。 這不是從屬類型,但仍然會失敗。
template <int I>
struct Cls {
static int f();
};
template <int I>
decltype(I) Cls<I>::f() { return I; }
如果在定義和聲明中都使用decltype(I)
,則可以使用;如果在定義和聲明中都使用int
,則可以使用,但是使兩者不同會失敗。
更新2:一個類似的示例。 如果將Cls
更改為不是類模板,則編譯成功。
template <typename>
struct Cls {
static int f();
using Integer = decltype(Cls::f());
};
template <typename T>
typename Cls<T>::Integer Cls<T>::f() { return I; }
更新3:另一個來自MM的失敗示例非模板類的模板成員函數。
struct S {
template <int N>
int f();
};
template <int N>
decltype(N) S::f() {}
為什么聲明和定義僅與從屬類型不同是非法的? 即使類型本身與上面的template <int I>
不相關,它為什么也會受到影響?
因為當涉及到模板參數時, decltype
根據標准返回不依賴於語言的類型,請參見下文。 如果沒有模板參數,則解析為明顯的size_t
。 因此,在這種情況下,您必須選擇聲明和定義都具有獨立的表達式(例如size_t/decltype(sizeof(int))
)作為返回類型,或者兩者都具有從屬表達式(例如decltype(sizeof(T))
) ,如果它們的表達式等效,則解析為唯一的依賴類型,並視為等效(請參見下文)。
在這篇文章中,我正在使用C ++標准草案N3337 。
第7.1.6.2節[dcl.type.simpl]
¶4
用decltype(e)表示的類型定義如下:—如果e是未括號化的id表達式或未括號化的類成員訪問(5.2.5),則decltype(e)是e命名的實體的類型。 如果沒有這樣的實體,或者如果e命名了一組重載函數,則程序格式錯誤;
—否則,如果e為x值,則decltype(e)為T &&,其中T為e的類型;
—否則,如果e為左值,則decltype(e)為T&,其中T為e的類型;
—否則, decltype(e)是e的類型 。
這解釋了什么是decltype(sizeof(int))
。 但是對於decltype(sizeof(T))
還有另一節說明了它是什么。
第14.4節[臨時類型]
¶2
如果表達式e包含模板參數,則decltype(e)表示唯一的從屬類型 。 兩個這樣的decltype-specifier僅在它們的表達式相等時才引用同一類型(14.5.6.1)。 [注意:但是,可以使用typedef-name作為別名。 —尾注]
在Clang LLVM源3.9版的文件lib/AST/Type.cpp
DecltypeType::DecltypeType(Expr *E, QualType underlyingType, QualType can)
// C++11 [temp.type]p2: "If an expression e involves a template parameter,
// decltype(e) denotes a unique dependent type." Hence a decltype type is
// type-dependent even if its expression is only instantiation-dependent.
: Type(Decltype, can, E->isInstantiationDependent(),
E->isInstantiationDependent(),
E->getType()->isVariablyModifiedType(),
E->containsUnexpandedParameterPack()),
重要的短語開頭為“因此有一個十進制類型...”。 它再次澄清了這種情況。
同樣在Clang源版本3.9中的文件lib/AST/ASTContext.cpp
QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const {
DecltypeType *dt;
// C++11 [temp.type]p2:
// If an expression e involves a template parameter, decltype(e) denotes a
// unique dependent type. Two such decltype-specifiers refer to the same
// type only if their expressions are equivalent (14.5.6.1).
if (e->isInstantiationDependent()) {
llvm::FoldingSetNodeID ID;
DependentDecltypeType::Profile(ID, *this, e);
void *InsertPos = nullptr;
DependentDecltypeType *Canon
= DependentDecltypeTypes.FindNodeOrInsertPos(ID, InsertPos);
if (!Canon) {
// Build a new, canonical typeof(expr) type.
Canon = new (*this, TypeAlignment) DependentDecltypeType(*this, e);
DependentDecltypeTypes.InsertNode(Canon, InsertPos);
}
dt = new (*this, TypeAlignment)
DecltypeType(e, UnderlyingType, QualType((DecltypeType *)Canon, 0));
} else {
dt = new (*this, TypeAlignment)
DecltypeType(e, UnderlyingType, getCanonicalType(UnderlyingType));
}
Types.push_back(dt);
return QualType(dt, 0);
}
因此,您會看到Clang收集並從特殊集合中選擇了那些獨特的decltype
相關類型。
為什么編譯器如此愚蠢,以至於看不到decltype
的表達式是始終為size_t
sizeof(T)
? 是的,這對於人類讀者來說是顯而易見的。 但是,當您設計和實現正式的語法和語義規則時,尤其是對於諸如C ++這樣的復雜語言,則必須將問題歸類並為其定義規則,而不是僅僅針對每個特定問題提出一個規則。您將無法使用您的語言/編譯器設計的方式。 此處沒有相同的規則,那就是:如果decltype
具有不需要任何模板參數解析的函數調用表達式-將decltype
解析為函數的返回類型。 不僅如此,您還需要處理很多情況,以至於想出了一個更通用的規則,例如上述標准( 14.4[2]
)中引用的規則。
此外,AndyG在C ++-14(N4296,第7.1.6.4節[dcl.spec.auto] 12/13)中發現了與auto
, decltype(auto)
類似的非顯而易見的情況:
§7.1.6.4 [dcl.spec.auto]
¶13
具有使用占位符類型的已聲明返回類型的函數或函數模板的重新聲明或特化也應使用該占位符,而不是推導類型。 [示例:
auto f(); auto f() { return 42; } // return type is int auto f(); // OK int f(); // error, cannot be overloaded with auto f() decltype(auto) f(); // error, auto and decltype(auto) don't match
自2016年3月起對標准N4582草案進行了更改(感謝bogdan),概括了以下說法:
§17.4(舊的§14.4)[臨時類型]
¶2
如果表達式e是依賴類型的(17.6.2.2) ,則decltype(e)表示唯一的依賴類型。 兩個這樣的decltype-specifier僅在它們的表達式相等時才引用同一類型(17.5.6.1)。 [注意:但是,可以使用例如typedef-name作為別名的類型。 —尾注]
此更改導致另一部分描述了類型依賴表達式,該表達式對於我們的特定情況看起來很奇怪。
§17.6.2.2 [temp.dep.expr](舊的§14.6.2.2)
¶4
以下形式的表達式從不依賴類型(因為表達式的類型不能依賴):
... sizeof ( type-id ) ...
還有一些關於值相關表達式的部分,其中如果type-id
相關,則sizeof
可以取決於值。 與值相關的表達式和decltype
之間沒有關系。 經過一番思考,我沒有找到decltype(sizeof(T))
一定不能或不能解析為size_t
任何原因。 而且我認為這是編譯器開發人員沒有非常注意的標准(可能被許多其他更改淹沒,也許沒有認為它可能會是標准)中的一個相當偷偷摸摸的更改(“將模板參數”涉及“類型相關”)。實際改變一些東西,只是簡單的配方改進)。 更改確實有意義,因為sizeof(T)
不依賴於類型,而是依賴於值。 decltype(e)
的操作數是未評估的操作數,即不在乎值,僅在乎類型。 這就是為什么decltype
僅在e
與類型相關時才返回唯一類型。 sizeof(e)
可能僅取決於值。
我嘗試使用clang 5,gcc 8 -std=c++1z
的相同結果:錯誤。 我進一步嘗試了以下代碼:
template <typename>
struct Cls {
static std::size_t f();
};
template <typename T>
decltype(sizeof(sizeof(T))) Cls<T>::f() {
return 0;
}
給出了相同的錯誤,即使sizeof(sizeof(T))
既不依賴類型也不依賴於值(請參見此文章 )。 這給了我一個理由來假設編譯器正在按照C ++-11/14標准的舊方式工作(例如,“涉及模板參數”),就像上面clang 3.9源代碼中的代碼片段一樣(我可以驗證最新的開發者clang 5.0具有相同的內容,沒有在標准中找到與此新更改相關的任何內容,但與類型無關。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.