使用 decltype 的前导返回类型方法声明是否与尾随返回类型定义兼容?

问题描述 投票:0回答:1

使用

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
在其中声明(建议编译器可能都 有类似的错误)。

我是否忽略了一些不允许这样做的规定?

(切线:为什么我想要声明和定义有所不同 对此?通常我不会,但这是代码的输出 转换工具。声明是在原始代码中,其中 通常使用前导返回类型,我只想最少地使用它 在生成定义时更改,以及尾随返回类型 语法在这种情况下非常方便,因为所有类成员都是 在那里的范围内。)

c++ templates language-lawyer decltype trailing-return-type
1个回答
0
投票

gcc 和 clang 都没有实现 CWG2065

这意味着由于

dataMember
“涉及模板参数”(clang 称之为“实例化相关”),由于它是当前实例化的成员,因此
decltype(dataMember)
是“唯一的依赖类型”。

[temp.type]p4(DR2065 未更改措辞):

如果表达式 e 涉及模板参数,则

decltype(
e
)
表示唯一的依赖类型。两个这样的 decltype-specifiers 仅当它们的 expressions 相等时才指代相同的类型 ([temp.over.link])。

[临时链接]p5

如果包含表达式的两个函数定义满足单一定义规则,则两个涉及模板参数的表达式被视为等价,[...]

[class.mfct.non.static]p2:

id 表达式 ([expr.prim.id]) 既不是类成员访问语法 ([expr.ref]) 的一部分,也不是一元

&
运算符 ([expr.unary) 的无括号操作数时.op]) 用于当前类为 X ([expr.prim.this]) 的情况,如果名称查找 ([basic.lookup]) 将 id-expression 中的名称解析为非静态非类型某个类
C
的成员,并且如果 id-expression 可能被求值或
C
X
X
的基类,则 id-expression 会转换为类成员访问表达式 ([expr.ref]) 使用
(*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
© www.soinside.com 2019 - 2024. All rights reserved.