Loading

移动语义完美转发

移动语义完美转发

测试代码和输出

#include <iostream>

template <typename T> void judge(const T &&t) {
  std::cout << "&&:\t" << t << std::endl;
}

template <typename T> void judge(const T &t) {
  std::cout << "&:\t" << t << std::endl;
}

template <typename T> void f(T &&t) {
  judge(std::forward<T>(t));
  judge(t);
  judge(std::move(t));
}

int main() {
  int x = 10;

  f(x);

  std::cout << std::endl;

  f(11);

  return 0;
}


output:

&:      10
&:      10
&&:     10

&&:     11
&:      11
&&:     11

可以看到 std::forward 能完美转发,什么都不带的话是左值,而 std::move 永远是一个 rvalue


分析

首先老生常谈,在 f 当中,参数 t 一定是一个 lvalue。但在所谓 universal reference 下有不同意义 :

  1. f(11) 中,t 是一个指向 rvaluelvalue
  2. f(x) 中,t 是一个指向 lvaluelvalue

关于 universal reference

这是 Scott Meyers 创造的术语。看下例:

int &&z1 = x;   // Error
auto &&z2 = x;  // z2: int &

1: 行处报错:

Rvalue reference to type 'int' cannot bind to lvalue of type 'int' clang(lvalue_to_rvalue_ref)

2: 行处正确,但编译器显示 z2: int &,也就是说 z2 在这里是个 lvalue reference

注意:univeral reference出现的前提时在推演的时候,原文:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

个人不太理解为什么要把模板推导引用和右值引用以相同符号表示,虽然确实说得通,但初见很容易误导:

wow, great answer! All seems clearer now. I must say I'm new to learning C++ by myself, so there are a bit new unfamiliar terms to me in your discussion, specifically, what does the double ampersand '&&' mean right after 'T'? I thought thats only used for the logical AND.

其中 std::move

definition: ( from the GNU ISO C++ Library )

/**
 *  @brief  Convert a value to an rvalue.
 *  @param  __t  A thing of arbitrary type.
 *  @return The parameter cast to an rvalue-reference to allow moving it.
 */
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ 
    return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); 
}

其中 std::forward

definition: ( from the GNU ISO C++ Library )

/**
 *  @brief  Forward an lvalue.
 *  @return The parameter cast to the specified type.
 *
 *  This function is used to implement "perfect forwarding".
 */
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ 
    return static_cast<_Tp&&>(__t); 
}

/**
 *  @brief  Forward an rvalue.
 *  @return The parameter cast to the specified type.
 *
 *  This function is used to implement "perfect forwarding".
 */
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
    // this assert seems redundant, I'm not sure
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue reference type");
    return static_cast<_Tp&&>(__t);
}

关于: 引用模板参数推导

据称 c++ 编译器将自动推导到一个

  1. 合法的 legal
  2. 最少推导的 with the least amount of deduction

类型

首先稍微测试一下( type_name() 抄自此回答)。

template <typename T> void g0(T t) { //
  std::cout << "type of T: " << type_name<T>()
            << ", decltype(t): " << type_name<decltype(t)>() << std::endl;
}
template <typename T> void g1(T &t) {
  std::cout << "type of T: " << type_name<T>()
            << ", decltype(t): " << type_name<decltype(t)>() << std::endl;
}
template <typename T> void g2(T &&t) {
  std::cout << "type of T: " << type_name<T>()
            << ", decltype(t): " << type_name<decltype(t)>() << std::endl;
}

int main() {
  int x = 10;
  int &y = x;
  int &&z = 12;

  std::cout << std::endl;

  std::cout << type_name<int>() << std::endl
            << type_name<int &>() << std::endl
            << type_name<int &&>() << std::endl
            << std::endl;

  g0(1);
  g0(x);
  g0(y);
  g0(z);
  g0(std::move(x));

  std::cout << std::endl;

  // g1(1); Error: no match function
  g1(x);
  g1(y);
  g1(z);
  // g1(std::move(x)); Error: no match function

  std::cout << std::endl;

  g2(1);
  g2(x);
  g2(y);
  g2(z);
  g2(std::move(x));

  return 0;
}

output:

int
int &
int &&

type of T: int, decltype(t): int
type of T: int, decltype(t): int
type of T: int, decltype(t): int
type of T: int, decltype(t): int
type of T: int, decltype(t): int

type of T: int, decltype(t): int &
type of T: int, decltype(t): int &
type of T: int, decltype(t): int &

type of T: int, decltype(t): int &&
type of T: int &, decltype(t): int &
type of T: int &, decltype(t): int &
type of T: int &, decltype(t): int &
type of T: int, decltype(t): int &&

没有一个 T 推导成了 int &&

与我原本猜想不符的地方包括

  1. g0(int &) 中,T 会不会被推导为 int &
  2. g0(int &&) 中,T 会不会被推导为 int &&
  3. g2(int &&) 中,T 会不会被推导为 int && 以便引用折叠

说实话,这 T 的推导结果疑似被编译器优化过。不过总的来说还是很合理的

关于: std::forward<T> 的模板参数

开了 -std=c++17 也不能自动推导,怎么会事呢

关于: && & && 引用折叠

编译器禁止你写 & && 这样的代码(声明到引用的引用,这没有意义),因而这种写法就被编译器拿来表示另一种含义,即引用折叠( Reference collapsing )

Reference collapsing
It is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference:

注意:与 universal reference 一样,这同样只发生在类型操作中。

(说是类型操作,实际上还是需要在类型推导中。下面这样的代码仍然会报错:)

typedef int &&&&&B; // 'Tp' declared as a reference to a reference
                    // *clang(illegal_decl_reference_to_reference)*

using B = int &&&&&;// Type name declared as a reference to a reference
                    // *clang(illegal_decl_reference_to_reference)*

所以你大概只能像这样去手动操作引用折叠

template <typename T> struct A1 { using Type = T &; };
template <typename T> struct A2 { using Type = T &&; };

int main() {
  int x = 10;

  A1<A1<int>::Type>::Type y = x;  // Right
  A2<A2<int>::Type>::Type z = 12; // Right
  A1<A2<int>::Type>::Type zz = x; // Right
  // int &&&zz = x; Error: 'zz' declared as a reference to a
  // reference *clang(illegal_decl_reference_to_reference)*

  return 0;
}

关于: std::remove_reference

definition: ( from the GNU ISO C++ Library )

/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type; };

根据前面的测试T 可能被推导为 lvalue reference,因而需要 remove 一下

关于: 参数的 const 修饰符

template <typename T> void w0(T &&t) { //
  std::cout << t << std::endl;
}

template <typename T> void w1(const T &&t) { //
  std::cout << t << std::endl;
}

int main() {
  auto &&_x = 12;
  auto &&x = _x;

  int y = 13;

  w0(std::forward<int>(_x));
  w0(std::forward<int>(x));
  w0(y);

  w1(std::forward<int>(_x));
  w1(std::forward<int>(x));
  // w1(y); // Candidate function [with T = int] not viable: expects an rvalue
  // for 1st argument

  return 0;
}

w1 处的 const 使得 && 不再是 universal reference,而是 rvalue reference见此问答

posted @ 2022-05-16 11:35  人中之人  阅读(199)  评论(0)    收藏  举报