[英]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.