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


9

给定以下模板结构:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

这可以编译,并T推导为int

auto f = Foo(2);

但这无法编译:https : //godbolt.org/z/hAA9TE

int x = 2;
auto f = Foo(x);

/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
    auto f = Foo(x);
             ^

<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
    Foo(T&&) {}
    ^
*/

但是,Foo<int&>(x)被接受。

但是,当我添加一个看似多余的用户定义的演绎指南时,它会起作用:

template<typename T>
Foo(T&&) -> Foo<T>;

为什么没有用户定义的推导指南就无法T推论int&



这个问题似乎与模板类型有关,例如Foo<T<A>>
jtbandes

Answers:


5

我认为这里之所以会引起混淆,是因为综合推论指南中有关转发参考的特定例外。

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

template<typename T>
auto f(T&&) -> Foo<T>;

但是对于从构造函数生成的T&&引用,是一个简单的右值引用,而在用户定义的情况下,它是一个转发引用。这由C ++ 17标准的[temp.deduct.call] / 3(草稿N4659,强调我的)指定:

转发参考是一个rvalue参照CV-不合格模板参数不代表一类模板的模板参数(类模板参数推导过程中([over.match.class.deduct]))。

因此,从类构造函数综合的候选对象将不会T像从转发引用中推断出那样(它可以推断T为左值引用,因此T&&也是左值引用),而只会推断T为非引用,因此T&&始终右值引用。


1
感谢您简洁明了的回答。您知道为什么转发参考规则中的类模板参数有例外吗?
jtbandes

2
@jtbandes这似乎是由于美国国家机构的评论而改变的,请参见论文p0512r0。我找不到评论。我的理由的猜测是,如果你写一个构造函数取右值引用,你通常期望它的工作是否指定了同样的方式Foo<int>(...),或只是Foo(...),这是不符合转发引用的情况下(这可以推断Foo<int&>代替。
核桃

6

这里的问题是,由于是在上模板化的T,因此在构造函数中,Foo(T&&)我们执行类型推导。我们总是有一个r值参考。也就是说,的构造函数Foo实际上如下所示:

Foo(int&&)

Foo(2)之所以有效,2是因为prvalue。

Foo(x)并非因为x是无法绑定到的左值int&&。您可以std::move(x)将其转换为适当的类型(demo

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);

这是因为该标准指示该(虚构的)模板方法具有与类(Just T)相同的模板参数,后跟任何模板参数作为构造函数(在这种情况下为无;未对构造函数进行模板化)。然后,函数参数的类型与构造函数中的参数相同。在我们的例子中,实例化之后Foo<int>,构造函数看起来像Foo(int&&)rvalue-reference,换句话说。因此,add_rvalue_reference_t上面的用法。

显然,这是行不通的。

当您添加“冗余”扣除指南时:

template<typename T>
Foo(T&&) -> Foo<T>;

你让编译器来区分的是,尽管连接到任何类型的引用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);

(出于类模板参数推导的目的,我们对构造函数的调用被重定向到helper函数,因此Foo(x)变为MakeFoo(x))。

这允许U&&变得int&T变得简单int


模板的第二层似乎没有必要;它提供什么价值?您能否提供一些文档的链接,以阐明为什么在此处始终将T &&视为右值引用?
jtbandes

1
但是,如果尚未推导出T,“任何可转换为T的类型”是什么意思?
jtbandes

1
您很快就会提供一种解决方法,但是您能否将重点更多地放在解释上,除非您以某种方式对其进行修改,否则该解释将不起作用?为什么它不能按原样工作?“ x是一个不能绑定到的左值int&&”,但是一个不理解的人会困惑,Foo<int&>(x)可以工作,但是没有自动找出答案-我想我们都希望对原因有更深入的了解。
Wyck)

2
@Wyck:我更新了帖子,以更加关注其原因。
AndyG

3
@Wyck真正的原因很简单:该标准专门关闭了合成演绎指南中的完善转发语义,以防止出现意外情况。
LF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.