考虑这个简单的独立代码:
template<typename T>
void foo();
void bar() {
int i;
auto l = [&i]() -> decltype(auto) {
decltype(auto) x = i;
foo<decltype(x)>();
foo<decltype(i)>();
return static_cast<decltype(i)>(i);
};
l();
foo<decltype(l())>();
}
GCC 生成以下内容:
bar():
sub rsp, 8
call void foo<int&>()
call void foo<int>()
add rsp, 8
jmp void foo<int>()
Clang 生成以下内容:
bar(): # @bar()
push rax
call void foo<int>()
call void foo<int>()
pop rax
jmp void foo<int>() # TAILCALL
MSVC 生成以下内容:
void bar(void) PROC ; bar, COMDAT
$LN8:
sub rsp, 40 ; 00000028H
call void foo<int &>(void) ; foo<int &>
call void foo<int &>(void) ; foo<int &>
add rsp, 40 ; 00000028H
jmp void foo<int &>(void) ; foo<int &>
void bar(void) ENDP
似乎三个编译器都不同意。哪一个是正确的,C++ 标准的哪一部分证实了它?
在我看来,
decltype(i)
应该永远是int
,而不是int&
,无论是否被捕获。
decltype(auto)
在 [dcl.type.auto.deduct]/4: 中指定
如果 占位符类型说明符 的形式为 type-constraintopt
,则decltype(auto)
应单独作为占位符。T
推导的类型按照 [dcl.type.decltype] 中的描述确定,就好像 E 是T
的操作数一样。decltype
这意味着
decltype(auto) x = i
相当于 decltype(i) x = i
。关于 decltype(i)
,[dcl.type.decltype]/1.3 指出:
- 否则,如果 E 是不带括号的 id-expression 或不带括号的类成员访问 ([expr.ref]),则
是由 E 命名的实体的类型。如果没有这样的实体,则程序是格式错误的;decltype(E)
因此,
decltype(i)
是由i
命名的实体类型,即int
。因此,x
属于 int
类型,因此,decltype(x)
也是 int
。因此,Clang 在这种情况下是正确的。 GCC 中的这个错误已在 GCC 14 中修复(Patrick)。
但是,此修复似乎会导致 GCC 中的另一个错误。如果我们将
i
的类型更改为 int&
,则以下代码演示了此问题 (Godbolt):
template <typename T> void foo();
void bar(int &i) {
auto l = [&i]() -> decltype(auto) {
decltype(auto) x = i;
foo<decltype(x)>(); // [Clang] int& [GCC 14] int [GCC 13] int&
foo<decltype(i)>(); // [Clang] int& [GCC 14] int& [GCC 13] int&
return i;
};
l();
foo<decltype(l())>(); // [Clang] int& [GCC 14] int [GCC 13] int&
}
按照与上面相同的推理,Clang 和 GCC 13 在这种情况下是正确的。