【问题标题】:Understanding of the implementation of std::forward since C++11了解C++11以来std::forward的实现
【发布时间】:2019-08-03 11:24:41
【问题描述】:

constexprnoexcept 被忽略了,因为它们似乎与了解 std::forward 的行为方式无关。)

基于我对 Scott Meyers 的“Effective Modern C++”的理解, C++14 中std::move 的示例实现如下

template<typename T>
decltype(auto) move(T&& param) {
  return static_cast<remove_reference_t<T>&&>(param);
}

鉴于对转发(或“通用”)引用的解释,我认为这个实现对我来说非常清楚:

  • 参数paramT&amp;&amp; 类型,即右值引用或左值引用(无论参数的类型是什么),取决于调用者中参数是右值还是左值;换句话说,param 可以绑定到右值和左值(即 anything);这是有意的,因为move 应该将 anything 转换为右值。
  • decltype(auto) 只是根据实际的return 语句表达返回类型的简洁方式。
  • 返回的对象是同一个对象param,被转换为一个右值引用(&amp;&amp;),无论T是什么类型,一旦其推导的引用被剥离(推导在T&amp;&amp;上完成,不在⋯&lt;T&gt;&amp;&amp;)。

简而言之,我对在move的实现中使用转发/通用引用的理解如下:

  • 转发/通用引用T&amp;&amp; 用于参数,因为它旨在绑定到任何东西;
  • 返回类型是一个右值引用,因为move 旨在将任何东西转换为右值。

很高兴知道我的理解到目前为止是否正确。

另一方面,std::forward 在 C++14 中的示例实现如下

template<typename T>
T&& forward(remove_reference_t<T>& param) {
  return static_cast<T&&>(param);
}

我的理解如下:

  • T&amp;&amp;,返回类型, 必须是转发/通用引用,因为我们希望 forward 通过右值引用或左值引用返回,因此在此处的返回类型上进行类型推导(与发生的情况不同对于move,类型推导发生在参数端)是对传递给forward的模板类型参数的右值引用;
  • 因为T 编码了实际参数的左值/右值特性,该参数绑定了作为参数传递给forward 的调用者参数,T 本身可能导致actual_type&amp;actual_type,因此T&amp;&amp; 可以是左值引用或右值引用。
  • param 的类型是对T 的任何类型的左值引用,一旦其推导引用被剥离。 Actually in std::forward type deduction is disabled on purpose, requiring that the template type argument be passed explicitly.

我的疑惑如下。

  • forward 的两个实例(实际上,每个调用它的类型都有两个)仅在返回类型上有所不同(传递右值时的右值引用,传递左值时的左值引用),因为在两者中案例param 是对非const 无引用T 的左值引用。返回类型不是不计入重载决议的东西吗? (也许我在这里不正确地使用了“过载”。)
  • 既然param的类型是非const的左值引用到无引用T,并且因为左值引用必须是to-const才能绑定到右值,@987654365怎么能@绑定到右值?

作为一个附带问题:

  • 可以将decltype(auto) 用作返回类型,就像move 一样?

【问题讨论】:

  • std::forward 的第二个重载是forward(remove_reference_t&lt;T&gt;&amp;&amp; param),而remove_reference 不会删除 cv-qualifiers
  • @PiotrSkotnicki,现在我看到std::forward 实际上有两个重载。我想知道为什么迈耶斯的书中只提到了前者……
  • 你忘了constexprnoexcept
  • @Deduplicator,这两个家伙是了解std::forward 工作原理的必要条件吗?
  • @EnricoMariaDeAngelis 仅当您也想了解它们影响的部分时。或者至少不想给人留下错误的印象,也不想解释把他们排除在外。

标签: c++ move rvalue-reference perfect-forwarding forwarding-reference


【解决方案1】:

forward本质上是一种在完美转发中保存价值类别的机制。

考虑一个简单的函数,它尝试透明地调用f 函数,尊重值类别。

template <class T>
decltype(auto) g(T&& arg)
{
    return f(arg);
}

这里的问题是表达式arg总是一个左值,不管arg是否是右值引用类型。这就是forward 派上用场的地方:

template <class T>
decltype(auto) g(T&& arg)
{
    return f(forward<T>(arg));
}

考虑std::forward的参考实现:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept
{
    return static_cast<T&&>(t);
}

template <class T>
constexpr T&& forward(remove_reference_t<T>&& t) noexcept
{
    static_assert(!std::is_lvalue_reference_v<T>);
    return static_cast<T&&>(t);
}

(这里可以使用decltype(auto),因为推导出的类型永远是T&amp;&amp;。)

在以下所有情况下,都会调用第一个重载,因为表达式 arg 表示一个变量,因此是一个左值:

  • 如果g 使用非常量左值调用,则T 被推断为非常量左值引用类型。 T&amp;&amp;T 相同,forward&lt;T&gt;(arg) 是非常量左值表达式。因此,f 使用非常量左值表达式调用。

  • 如果 g 使用 const 左值调用,则 T 被推断为 const 左值引用类型。 T&amp;&amp;T 相同,forward&lt;T&gt;(arg) 是一个 const 左值表达式。因此,f 使用 const 左值表达式调用。

  • 如果使用右值调用 g,则将 T 推断为非引用类型。 T&amp;&amp; 是右值引用类型,forward&lt;T&gt;(arg) 是右值表达式。因此,f 使用右值表达式调用。

在所有情况下,都尊重价值类别。

在正常的完美转发中不使用第二个重载。有关其用法,请参阅What is the purpose of std::forward()'s rvalue reference overload?

【讨论】:

  • 可能值得包括(提醒)T 有三种不同的可能性。 (此外,由于问题提到了它,因此可以解释关于返回类型的混淆。)
  • 我还没有接受答案,因为我还没有理解(我的错)。
  • @EnricoMariaDeAngelis 如果您准确指出您不理解的部分,也许我可以帮助您。
  • @L.F.,我基本上混淆了forward&lt;T&gt; 参数的右值/左值性与其调用者的参数的右值/左值性。事实上,不管g 的参数是右值还是左值,forward&lt;T&gt; 的参数都是左值(arg),因此永远不应该调用 'forward' 的第二个重载(不在三个示例中)你至少提到了,因为在所有这些forward&lt;T&gt; 的参数中都有一个名称,arg),我错了吗?
  • @EnricoMariaDeAngelis 没错,完美转发中没有使用第二个重载。
猜你喜欢
  • 2014-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多