jtb*_*des 9 c++ templates language-lawyer
给定以下模板结构:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Run Code Online (Sandbox Code Playgroud)
这编译,并T推导出为int:
auto f = Foo(2);
Run Code Online (Sandbox Code Playgroud)
但这不能编译:https : //godbolt.org/z/hAA9TE
template<typename T>
struct Foo {
Foo(T&&) {}
};
Run Code Online (Sandbox Code Playgroud)
然而,Foo<int&>(x)被接受。
但是当我添加一个看似多余的用户定义推导指南时,它起作用了:
template<typename T>
Foo(T&&) -> Foo<T>;
Run Code Online (Sandbox Code Playgroud)
为什么不能T推导出int&没有用户定义的推导指南?
这里的问题是,由于类是模板化的T,在构造函数中Foo(T&&)我们没有执行类型推导;我们总是有一个 r 值参考。也就是说, for 的构造函数Foo实际上是这样的:
Foo(int&&)
Run Code Online (Sandbox Code Playgroud)
Foo(2)有效,因为2是一个纯右值。
Foo(x)不会因为x是一个不能绑定到的左值int&&。您可以std::move(x)将其转换为适当的类型(演示)
Foo<int&>(x)工作得很好,因为构造函数变成Foo(int&)了引用折叠规则;最初它Foo((int&)&&)会Foo(int&)按照标准崩溃。
关于你的“冗余”推导指南:最初有一个代码的默认模板推导指南,它基本上像一个辅助函数,如下所示:
template<typename T>
struct Foo {
Foo(T&&) {}
};
template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
return Foo<T>(std::move(value));
}
//...
auto f = MakeFoo(x);
Run Code Online (Sandbox Code Playgroud)
这是因为标准规定这个(虚构的)模板方法具有与类( Just T)相同的模板参数,后跟作为构造函数的任何模板参数(在这种情况下没有;构造函数不是模板化的)。然后,函数参数的类型与构造函数中的类型相同。在我们的例子中,在实例化之后Foo<int>,构造函数看起来像Foo(int&&),换句话说就是一个右值引用。因此使用add_rvalue_reference_t上面的。
显然这行不通。
当您添加“冗余”扣除指南时:
template<typename T>
Foo(T&&) -> Foo<T>;
Run Code Online (Sandbox Code Playgroud)
你让编译器来区分的是,尽管连接到任何类型的引用T在构造函数(int&,const int&或int&&等等),你打算推断类是没有参考(仅仅是类型T)。这是因为我们突然被执行类型推断。
现在我们生成另一个(虚构的)辅助函数,如下所示:
template<class U>
Foo<U> MakeFoo(U&& u)
{
return Foo<U>(std::forward<U>(u));
}
// ...
auto f = MakeFoo(x);
Run Code Online (Sandbox Code Playgroud)
(出于类模板参数推导的目的,我们对构造函数的调用被重定向到辅助函数,因此Foo(x)变为MakeFoo(x))。
这允许U&&成为int&和T变得简单int
我认为这里出现混淆是因为关于转发参考的综合扣除指南有一个特定的例外。
确实,从构造函数生成的用于类模板参数推导的候选函数和从用户定义的推导指南生成的候选函数看起来完全相同,即:
template<typename T>
auto f(T&&) -> Foo<T>;
Run Code Online (Sandbox Code Playgroud)
但是对于从构造函数生成的,T&&是一个简单的右值引用,而在用户定义的情况下它是一个转发引用。这是由C++17 标准(草案 N4659,强调我的)的[temp.deduct.call]/3指定的:
甲转发参考是一个rvalue参照CV-不合格模板参数不代表一类模板的模板参数(类模板参数推导过程中([over.match.class.deduct]))。
因此,从类构造函数合成的候选者不会T像从转发引用(可以推导出T为左值引用,因此T&&也是左值引用)一样推断,而只会推断T为非引用,所以T&&总是一个右值引用。