Dependent type or argument in decltype in function definition fails to compile when declared without decltype
我一直在尝试在解析为与声明相同类型的定义中使用推断的返回类型。这工作:
1 2 3 4 5 6 7 | template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(int)) Cls<T>::f() { return 0; } |
但是如果我把定义改为与
1 2 | template <typename T> decltype(sizeof(T)) Cls<T>::f() { return 0; } |
GCC的错误(clang几乎相同):
1 2 3 4 5 6 | 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(); ^ |
同样的问题也出现在函数参数类型上:
1 2 3 4 5 6 7 | template <typename> struct Cls { static void f(std::size_t); }; template <typename T> void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead |
更奇怪的是,如果声明和定义匹配,并且都使用
1 2 3 4 5 6 7 8 9 10 11 | #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())>{},""); |
用另一个例子更新。这不是依赖类型,但仍然失败。
1 2 3 4 5 6 7 | template <int I> struct Cls { static int f(); }; template <int I> decltype(I) Cls::f() { return I; } |
如果我在定义和声明中同时使用
更新2:类似的例子。如果
1 2 3 4 5 6 7 8 | 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:M.M.中的另一个失败示例是非模板类的模板化成员函数。
1 2 3 4 5 6 7 | struct S { template <int N> int f(); }; template <int N> decltype(N) S::f() {} |
为什么声明和定义只与依赖类型不一致是非法的?即使类型本身与上面的
型
因为当涉及模板参数时,
在这篇文章中,我使用C++标准草案N33 37。
§ 7.1.6.2 [dcl.type.simpl]
? 4
The type denoted by decltype(e) is defined as follows:
— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e)
is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded func-
tions, the program is ill-formed;— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
— otherwise, decltype(e) is the type of e.
号
这就解释了什么是
§ 14.4 [temp.type]
? 2
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). [ Note: however,
it may be aliased, e.g., by a typedef-name. — end note ]
号
在clang llvm源中,文件
1 2 3 4 5 6 7 8 | 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()), |
重要的短语开头是"因此是decltype…"。它再次澄清了形势。
在clang sources 3.9版文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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收集并从一个特殊的集合中选择那些独特的依赖类型
为什么编译器如此愚蠢以至于它看不到
此外,在AndyG+14(N496,〈7.1.64.[DCL,SPEC.AUTO)12/13)中发现了一个与EDCOX1〔15〕、EDCOX1〔16〕相似的不明显的情况。
§ 7.1.6.4 [dcl.spec.auto]
? 13
Redeclarations or specializations of a function or function template with a declared return type that uses a
placeholder type shall also use that placeholder, not a deduced type. [ Example:
1
2
3
4
5 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
号C++ 17、文档号> N45的变化
自2016年3月起,N4582标准草案的变更(得益于Bogdan)概括了以下声明:
§ 17.4 (old § 14.4) [temp.type]
? 2
If an expression e is type-dependent (17.6.2.2), decltype(e) denotes a unique dependent type. Two such
decltype-specifiers refer to the same type only if their expressions are equivalent (17.5.6.1). [ Note: however,
such a type may be aliased, e.g., by a typedef-name. — end note ]
号
这一变化导致了另一个描述类型相关表达式的部分,这种表达式在我们的特定情况下看起来很奇怪。
§ 17.6.2.2 [temp.dep.expr] (old § 14.6.2.2)
? 4
Expressions of the following forms are never type-dependent (because the type of the expression cannot be
dependent):
1
2
3 ...
sizeof ( type-id )
...
号
还有一些关于值相关表达式的章节,其中,如果
我用clang 5,gcc 8
1 2 3 4 5 6 7 8 9 | template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(sizeof(T))) Cls<T>::f() { return 0; } |
给出了同样的错误,即使