完美转发和模板

5
当我有这样的代码时:

template<class T>
void f_(const T& arg)
{
    cout << "void f(const T& arg): Cannot modify\n";
}

template<class T>
void f_(T&& arg)
{
    cout << "void f(T&& arg): Can modify\n";
}

在主函数中我调用它:

int main()
{

    MemoryBlock block;
    f_(block);
    f_(MemoryBlock());
    return 0;
}

输出结果为:
"void f(T&& arg): 可修改\n";
"void f(T&& arg): 可修改\n";

但是当我将这段代码改为非通用代码时,也就是说,我不再使用函数模板,而是使用普通函数,

void f(const MemoryBlock&)
{
    cout << "In f(const MemoryBlock&). This version cannot modify the parameter.\n";
}

void f(MemoryBlock&&)
{
    cout << "In f(MemoryBlock&&). This version can modify the parameter.\n";
}

输出结果更加“直观”:
“在f(const MemoryBlock&)中。此版本不能修改参数。”;
“在f(MemoryBlock&&)中。此版本可以修改参数。”;
在我看来,仅通过将函数从模板更改为非模板就完全改变了rvalue引用的推断规则。 如果有人能向我解释一下,我会非常感激。

1
你误解了模板参数TMemoryBlock的映射关系。 - Walter
有趣的是,你在标题中提到了“完美转发”,但实际上你并没有进行任何转发。 - user541686
2个回答

5
当您使用T&&时,那不是一个右值引用,而是一个通用引用参数。它们的声明方式相同,但行为不同。
当您移除模板参数时,您不再处于可推导的上下文中,这实际上是一个右值引用:它们只绑定到右值,当然。
在可推导的上下文中(也就是进行类型推断时),T&&可以是一个右值引用或左值引用。通用引用可以绑定到几乎所有组合(包括constconst volatile等),在您的情况下是const T&
现在您的想法是高效并重载右值和左值,但是当推断模板参数时,通用引用重载是更好的匹配项。因此,它将被选择而不是const T&重载。
通常,您只需要保留通用引用函数并将其与std::forward<T>()配对以完美转发参数。这消除了您对const T&重载的需求,因为通用引用版本将接管。
请注意,仅因为您在可推导的上下文中看到&&,并不意味着它是一个通用引用;&&需要附加到正在推断的类型上,因此这里是一个实际上是右值引用的示例:
template<class T>
void f_(std::vector<T>&& arg) // this is an rvalue reference; not universal
{
    cout << "void f(T&& arg): Can modify\n";
}

这是一个关于这个问题的精彩讲座:https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11

2
@Artur 还可以阅读有关引用折叠规则的内容。 - vsoftco

1

T&& 可以被称为 通用/转发 引用。
引用折叠规则:

  1. A& & 变成 A&
  2. A& && 变成 A&
  3. A&& & 变成 A&
  4. A&& && 变成 A&&

template<typename T> void foo(T&&);

在这里,以下情况适用:

  1. 当对类型为 A 的左值调用 foo 时,T 解析为 A&,因此根据上述引用折叠规则,参数类型有效地变成了 A&。
  2. 当对类型为 A 的右值调用 foo 时,T 解析为 A,因此参数类型变成了 A&&。

在您的情况下:

template<class T> void f_(T&& arg);
f_(block); //case 1
f_(MemoryBlock()); //case  2

在情况1下:
T = MemoryBlock&,那么 T&& 就变成了 T& &&,得到的结果为 T&
在情况2下:
T = MemoryBlock,那么 T&& 就变成了 T&& ==>,得到的结果为 T&&
对于这两种情况。
template<class T> void f_(T&& arg)

"

因此,由于其是编译器的最佳选择,它被选中而不是

"
template<class T>
void f_(const T& arg)

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