为什么 C++ 不能在对 Foo<T>::Foo(T&&) 的调用中推导出 T?

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&没有用户定义的推导指南?

And*_*dyG 6

这里的问题是,由于是模板化的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

  • @Wyck真正的原因很简单:标准*专门关闭*综合推导指南中的完美转发语义以防止出现意外。 (3认同)
  • @Wyck:我已经更新了帖子以更多地关注原因。 (2认同)

wal*_*nut 5

我认为这里出现混淆是因为关于转发参考的综合扣除指南有一个特定的例外。

确实,从构造函数生成的用于类模板参数推导的候选函数和从用户定义的推导指南生成的候选函数看起来完全相同,即:

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&&总是一个右值引用。

  • @jtbandes 这似乎是由于美国国家机构的评论而发生了变化,请参阅论文 [p0512r0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/ p0512r0.pdf)。但我找不到评论。我猜测其基本原理是,如果您编写一个采用右值引用的构造函数,那么无论您指定“Foo&lt;int&gt;(...)”还是仅指定“Foo(...)”,您通常都会期望它以相同的方式工作。 `,转发引用则不是这种情况(它可以推导出 `Foo&lt;int&amp;&gt;`。 (3认同)
  • 感谢您清晰简洁的回答。你知道**为什么**在转发引用的规则中类模板参数有一个例外吗? (2认同)