受到这个问题的启发
有了这个代码
#include <string>
template<class T>
struct A {
template <typename U> using NewA = A<U>;
constexpr A(T const& t){}
constexpr auto f() const {
return NewA{"bye"};
}
};
A(const char*) -> A<std::string>;
int main() {
A{"hello"}.f();
}
Run Code Online (Sandbox Code Playgroud)
GCC 13.1 生成大量无用代码(最显着的是调用 std::string 构造函数/析构函数以及其他一些东西)
main:
sub rsp, 72
mov edx, OFFSET FLAT:.LC1+5
mov esi, OFFSET FLAT:.LC1
lea rax, [rsp+16]
mov rdi, rsp
mov QWORD PTR [rsp], rax
call void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.0]
lea rax, [rsp+48]
mov edx, OFFSET FLAT:.LC2+3
mov esi, OFFSET FLAT:.LC2
lea rdi, [rsp+32]
mov QWORD PTR [rsp+32], rax
call void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.0]
lea rdi, [rsp+32]
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose()
mov rdi, rsp
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose()
xor eax, eax
add rsp, 72
ret
Run Code Online (Sandbox Code Playgroud)
如果我将此行替换return NewA{"bye"};为return ::A{"bye"};(根据我的观点,这应该是完全相同的)
#include <string>
template<class T>
struct A {
template <typename U> using NewA = A<U>;
constexpr A(T const& t){}
constexpr auto f() const {
return ::A{"bye"};
}
};
A(const char*) -> A<std::string>;
int main() {
A{"hello"}.f();
}
Run Code Online (Sandbox Code Playgroud)
编译器能够将所有内容优化为一个异或
main:
xor eax, eax
ret
Run Code Online (Sandbox Code Playgroud)
这是某种“早期版本错误”吗?Clang 甚至还无法编译此代码(不支持通过别名的 CTAD)。
UPD: 看起来至少 GCC 10.1可以完美优化一切
您的两个变体都return ::A{"bye"};使return NewA{"bye"};您的程序成为 IFNDR(格式错误,无需诊断)。(不过请参阅底部的注释)
CTAD 考虑的推导指南是从执行 CTAD 的实例化上下文中可到达的推导指南。
所以在A{"hello"}.f();你的用户声明的推导导致的隐式实例化中总是会考虑的。
然而, 和NewA{"bye"}都是::A{"bye"}非依赖的。对于模板定义中的非依赖构造,还有一个附加要求,即它在紧随模板定义之后的假设实例化中的解释与其在模板专业化的任何实际实例化中的解释没有不同。如果不满足,则该程序是 IFNDR。(参见[temp.res.general]/6.6)。
A在您的情况下,在用户声明的推导指南的定义之后立即假设的实例化是不可到达的,因此它将推断出A<char[4]>与A<std::string>实际实例化不同的解释。
该规则允许编译器在定义非依赖构造时立即执行所有推导、重载解析等,我猜这就是 GCC 在这里为变体所做的事情::A{"bye"}(这显然是非依赖的),但是不适用于NewA{"bye"}变体(这有点不太清楚,请参阅此答案的末尾)。选择A<char[4]>而不是A<std::string>有一个较少的std::string临时构造,这可能会使编译器的优化变得更加简单。
为了优化所有内容,它需要决定内联所有std::string构造函数/析构函数调用以及它们调用的所有内容,并且必须识别匹配operator new/operator delete调用,以便用堆栈内存替换它们(除非 SSO 适用)。(通常对这些分配函数的调用是可观察的,因为您可以在程序中的任何位置替换它们,甚至在运行时通过动态链接也是如此。但是,如果编译器可以提供例如堆栈内存,则允许编译器匹配这些调用并忽略它们。)
编辑:最初我声称这NewA{"bye"}是依赖的,但再想一想,两者::A{"bye"}似乎NewA{"bye"}都是非依赖的,所以可能两者都是 IFNDR。目前似乎没有明确指定推导类类型的占位符的依赖性,请参阅CWG 问题 2600,但根据提议的解决方案,您的变体确实是非依赖性的,因此 IFNDR。