decltype
时,
是否允许在 a 中使用传统的前导返回类型语法
声明:
decltype(expr) foo();
然后使用 C++11 尾随返回类型 定义中的语法?
auto foo() -> decltype(expr) { /*...*/ }
我认为答案是肯定的,因为 C++11 8.3.5p1 (前导返回类型)和 8.3.5p2(尾随返回类型)似乎都产生相同的最终类型 描述,无论返回类型出现在哪一侧,以及 7.1.6.2p4 (
decltype
) 似乎没有任何可以
改变这一点。此外,9.3p9 中的注释显示了一个示例
使用 typedef 声明成员,但解释说无法定义它
使用 typedef,意味着它们不必使用完全相同的
语法约定。
但是,我有一个例子,Clang 和 GCC 都不认为这是 允许,尽管 MSVC 确实 (神螺栓链接):
// The class must be a template for the problem to happen.
template <typename T>
struct Class {
// This has to be inside the class for the problem to happen.
int dataMember;
// All is fine if I use trailing return type here.
//auto method() -> decltype(dataMember);
// But it fails with leading return type in the declaration.
decltype(dataMember) method();
};
// Definition uses trailing return type.
template <typename T>
auto Class<T>::method() -> decltype(dataMember) {
return 3;
}
Clang 16.0.0 说:
<source>:16:16: error: return type of out-of-line definition of 'Class::method' differs from that in the declaration
auto Class<T>::method() -> decltype(dataMember) {
^
<source>:11:24: note: previous declaration is here
decltype(dataMember) method();
~~~~~~~~~~~~~~~~~~~~ ^
1 error generated.
Compiler returned: 1
GCC 13.1 说,内容更丰富,但令人怀疑:
<source>:16:6: error: no declaration matches 'decltype (((Class<T>*)this)->Class<T>::dataMember) Class<T>::method()'
16 | auto Class<T>::method() -> decltype(dataMember) {
| ^~~~~~~~
<source>:11:24: note: candidate is: 'decltype (Class<T>::dataMember) Class<T>::method()'
11 | decltype(dataMember) method();
| ^~~~~~
<source>:3:8: note: 'struct Class<T>' defined here
3 | struct Class {
| ^~~~~
Compiler returned: 1
MSVC 19 对这段代码很满意,即使我添加了实例化的代码
Class
并调用 method
(尽管随后会发出警告
选择内联该方法,这有点奇怪)。
我倾向于相信这两个签名(其中一个带有领先 返回类型和带有尾随返回类型的类型)实际上应该是 在 C++ 中是等价的,而且我还可以使用前导 声明中的返回类型和尾随返回类型 定义,尽管这两个编译器反对,因为对于 他们,只有当该类是模板并且
dataMember
在其中声明(建议编译器可能都
有类似的错误)。
我是否忽略了一些不允许这样做的规定?
(切线:为什么我想要声明和定义有所不同 对此?通常我不会,但这是代码的输出 转换工具。声明是在原始代码中,其中 通常使用前导返回类型,我只想最少地使用它 在生成定义时更改,以及尾随返回类型 语法在这种情况下非常方便,因为所有类成员都是 在那里的范围内。)
gcc 和 clang 都没有实现 CWG2065。
这意味着由于
dataMember
“涉及模板参数”(clang 称之为“实例化相关”),由于它是当前实例化的成员,因此 decltype(dataMember)
是“唯一的依赖类型”。
[temp.type]p4(DR2065 未更改措辞):
如果表达式 e 涉及模板参数,则
edecltype(
表示唯一的依赖类型。两个这样的 decltype-specifiers 仅当它们的 expressions 相等时才指代相同的类型 ([temp.over.link])。)
如果包含表达式的两个函数定义满足单一定义规则,则两个涉及模板参数的表达式被视为等价,[...]
当 id 表达式 ([expr.prim.id]) 既不是类成员访问语法 ([expr.ref]) 的一部分,也不是一元
运算符 ([expr.unary) 的无括号操作数时.op]) 用于当前类为 X ([expr.prim.this]) 的情况,如果名称查找 ([basic.lookup]) 将 id-expression 中的名称解析为非静态非类型某个类&
的成员,并且如果 id-expression 可能被求值或C
是C
或X
的基类,则 id-expression 会转换为类成员访问表达式 ([expr.ref]) 使用X
作为(*this)
.
运算符左侧的后缀表达式。
因此,在您的声明
decltype(dataMember) method();
中,dataMember
与Class::dataMember
指的是同一事物,因为没有this
(因此没有当前类),但在您的定义template <typename T> auto Class<T>::method() -> decltype(dataMember)
中,dataMember
是 (*this).dataMember
。尽管它们具有相同的标记,但由于 ODR,这些表达式并不等效。
如果您确保没有
(*this).
转换,您可以看到这种情况:
template <typename T>
struct Class {
int dataMember;
decltype(std::type_identity_t<Class<T>>::dataMember) method();
};
template <typename T>
auto Class<T>::method() -> decltype(std::type_identity_t<Class<T>>::dataMember) {
return 3;
}
如果 gcc 和 clang 实现 CWG2065,
decltype(dataMember)
将不再是依赖类型,而只是 int
,所以这可以工作。
如果您要使
dataMember
的类型依赖,您会遇到同样的问题:
template<typename T>
struct Class {
T dataMember;
decltype(dataMember) method();
};
template <typename T>
auto Class<T>::method() -> decltype(dataMember) {
return 3;
} // Error: return type is different