为什么模板化的右值引用可以接受左值?

5
我看过类似以下用法的东西:
#include <iostream>
#include <functional>
using namespace std;

template<typename FN>
void Foo(FN&& Fn)
{
    Fn();
}


void b()
{
    cout << "2." << endl;
}

int main() 
{
    Foo([](){ cout << "1." << endl; });
    Foo(&b);

    auto c = []() { cout << "3." << endl; };

    Foo(c);

    std::function<void(void)> d = c;

    Foo(d);

    return 0;
}

我相当确定 'c' 是一个左值,但我可以相信有一些 lambda 类型推导的诡计。但我几乎百分之百确定,d 是一个左值。

如果函数接受一个右值,那么模板化的内容为什么会起作用,但是 d 是一个左值呢?

此外,为什么有人会这样写 Foo 的签名,而不只是:

template<typename FN>
void Foo(FN Fn)

2
它并不是真正的rvalue:由于模板类型推导和引用折叠,它被称为“通用引用”(或“转发引用”,但前者更能表达这个概念)。 - edmz
@black 正确的术语是“转发引用”(因为它可能应该使用std::forward())。“通用引用”是Scott Meyers使用的术语。 - Barry
1个回答

3

T&&的推导规则很棘手。

它们旨在使得推导出的T&&成为“转发引用”(或“通用引用”)。

首先是引用折叠。假设您有一个未知类型X。现在,X尚未被推导。

然后,如果我们检查以下类型的变量:

typedef X x0;
typedef X& x1;
typedef X const& x2;
typedef X&& x3;

如果我们将X设置为intint&int const&int&&之一,我们会得到:

X is --->  int         int&      int const&      int&&
X          int         int&      int const&      int&&
X&         int&        int&      int const&      int&
X const&   int const&  int&      int const&      int&
X&&        int&&       int&      int const&      int&&

实时例子

接下来是推断规则。如果在一个推断的上下文中将 X& 传递给 T&&,则会推断出 TX&。这导致 T&& 按照上述引用折叠规则变为 X&。对于 X const& 也会发生类似的事情。

如果将 X&& 传递给 T&&,它会推断出 TX。同时,T&& 也变成了 X&&

在这两个之间,在一个推断的上下文中,template<class T> void foo(T&&t) 是一个通用引用(现在称为转发引用)。

您可以使用 std::forward<T>(t) 恢复 t 的右/左值类别,因此称为转发引用。

这允许一个模板处理l和r值,并使用std::forward等机制,如果需要的话,可以稍微有所不同。
仅处理r值需要额外的工作:您必须使用SFINAE或另一个重载(可能带有=delete)。 仅处理l值很容易(只需用T&进行推断)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接