【问题标题】:Why does a perfect forwarding function have to be templated?为什么一个完美的转发功能必须模板化?
【发布时间】:2012-01-11 02:02:22
【问题描述】:

为什么下面的代码有效:

template<typename T1>
void foo(T1 &&arg) { bar(std::forward<T1>(arg)); }

std::string str = "Hello World";
foo(str); // Valid even though str is an lvalue
foo(std::string("Hello World")); // Valid because literal is rvalue

但不是:

void foo(std::string &&arg) { bar(std::forward<std::string>(arg)); }

std::string str = "Hello World";
foo(str); // Invalid, str is not convertible to an rvalue
foo(std::string("Hello World")); // Valid

为什么示例 2 中的左值没有以与示例 1 中相同的方式解析?

另外,为什么标准认为需要在 std::forward 中提供参数类型而不是简单地推导它很重要?简单地向前呼叫就是表明意图,不管是什么类型。

如果这不是标准的东西,只是我的编译器,我正在使用 msvc10,这将解释糟糕的 C++11 支持。

谢谢

编辑 1:将文字“Hello World”更改为 std::string("Hello World") 以生成右值。

【问题讨论】:

  • 酒吧发生了什么?编译并不意味着它必须工作。我认为应该分别是void foo(T1 &amp;arg)void foo(std::string &amp;arg)
  • "Hello World" 不是右值,它是类型为const char[12] 的左值。
  • @AJG85 在 bar 中发生的事情并不重要。 && 表示右值引用。
  • @GMan 为什么“Hello World”是左值?它是不可分配的。我错过了什么鬼鬼祟祟的东西吗?如果我将其更改为 std::string("Hello World") 意味着什么?那么它肯定是一个右值。
  • @AJG85 你刚才描述的是std::move

标签: c++ c++11 perfect-forwarding


【解决方案1】:

首先,read this 全面了解转发。 (是的,我将大部分答案委托给其他地方。)

总而言之,转发意味着左值保持左值,右值保持右值。你不能用一种类型来做到这一点,所以你需要两个。因此,对于每个转发的参数,您需要该参数的两个版本,这需要函数总共有 2N 个组合。您可以对函数的所有组合进行编码,但如果您使用模板,则会根据需要为您生成各种组合。


如果您尝试优化副本和移动,例如:

struct foo
{
    foo(const T& pX, const U& pY, const V& pZ) :
    x(pX),
    y(pY),
    z(pZ)
    {}

    foo(T&& pX, const U& pY, const V& pZ) :
    x(std::move(pX)),
    y(pY),
    z(pZ)
    {}

    // etc.? :(

    T x;
    U y;
    V z;
};

那么你应该停下来这样做:

struct foo
{
    // these are either copy-constructed or move-constructed,
    // but after that they're all yours to move to wherever
    // (that is, either: copy->move, or move->move)
    foo(T pX, U pY, V pZ) :
    x(std::move(pX)),
    y(std::move(pY)),
    z(std::move(pZ))
    {}

    T x;
    U y;
    V z;
};

你只需要一个构造函数。 指南:如果您需要自己的数据副本,请在参数列表中制作该副本;这使得决定是复制还是向上移动到调用者和编译器。

【讨论】:

  • 所以第一个示例有效的原因是因为 T1 实际上被解析为 void foo(const std::string &amp; &amp;&amp;arg) 的等价物,使用参考扣除规则将其减少为 void foo(const std::string &amp;arg)?它在示例 2 中失败,因为字符串没有左值重载?是否有任何定义模板函数的最佳实践,以便至少相对明显地应该是什么类型?理想情况下,我正在寻找一种在避免 2^N 重载的同时明确类型的好方法。
  • @Mranz:没错。嗯,你要干什么?转发和模板是用来转发的,你不需要知道类型。
  • 假设我有一个构造函数,它接受一个向量,如Class(.. some args ..., vector&lt;int&gt; items)。理想情况下,我希望允许该参数使用移动语义或作为右值来避免复制。为了支持这一点,我要么必须模板化项目,要么创建一个右值重载。如果我将其模板化,实际类型会在定义中丢失,而实际参数必须由我的类的消费者通过读取头文件或一些注释来推断。
  • 感谢您的信息。这很有意义。
  • 为什么不使用 x(std::forward(pX)) 等? std::forward 是干什么用的?
猜你喜欢
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
相关资源
最近更新 更多