恶搞一下std::forward函数

前言

关于 std::forward 的用法在之前的文章 《C++11中std::move和std::forward到底干了啥》已经总结过了,它被称为完美转发函数,用于函数模板中完成参数转发任务,当形参为左值引用时把它转发成左值,而在形参成为右值引用时把它转发成右值,依靠了引用折叠规则和 std::remove_reference 模板。

前段时间看到std::forward的源代码时突然有发现有些疑问,后来弄明白了决定换个花样试一试,不过在“恶搞”这个函数之前,先来看一看使用模板的规则,我们以模板函数为例,看看模板是怎么用的。

函数模板

template<class T>
T Add(T a, T b)
{
    return a + b;
}

这是一个非常简单的模板函数,直接传入参数就可以调用这个函数做加法运算,就像下面这样:

#include <iostream>

template<class T>
T Add(T a, T b)
{
    return a + b;
}

int main(void)
{
    std::cout << Add(2020, 2) << std::endl;
    std::cout << Add(3.0, 2.1) << std::endl;
    std::cout << Add(std::string("happy"), std::string(" holiday")) << std::endl;

    return 0;
}

函数的运行结果如下:

2022
5.1
happyholiday

我们在调用模板函数时虽然没有指定模板 T 的类型,但是编译器会自动推导,分别生成以下三个函数:

int Add(int a, int b)
{
    return a + b;
}

double Add(double a, double b)
{
    return a + b;
}

std::string Add(std::string a, std::string b)
{
    return a + b;
}

但是当我们采用以下的方式调用函数的时候就会出现编译错误

std::cout << Add(3, 2.1) << std::endl;
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testt.cpp --std=c++11
testt.cpp: In function ‘int main()’:
testt.cpp:13:28: error: no matching function for call to ‘Add(int, double)’
     std::cout << Add(3, 2.1) << std::endl;
                            ^
testt.cpp:5:3: note: candidate: template<class T> T Add(T, T)
 T Add(T a, T b)
   ^
testt.cpp:5:3: note:   template argument deduction/substitution failed:
testt.cpp:13:28: note:   deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)
     std::cout << Add(3, 2.1) << std::endl;
                            ^

编译器给出的错误很明显,那就是没有匹配 Add(int, double) 的函数生成,这个模板只提供了一个类型参数,遇到这种情况应该怎么办呢?我们知道 int 可以隐式转换成 double 类型,那就让它默认生成一个类型为 double 的模板函数 Add(int, double) 就可以了,所以这种情况下把调用函数写成下面这样就可以成功编译了:

std::cout << Add<double>(3, 2.1) << std::endl;

通过这个例子我们发现有些情况下,这个模板函数的参数类型必须显式传递,接下来我们再来熟悉一下 std::forward 函数。

forwawrd 函数定义

先来复习一下函数的定义:

  /**
   *  @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
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
            " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }

以上两个模板函数就是用来实现完美转发左值引用和右值引用的,那么你可以试试,当调用第一个函数的时候能不能推导出 _Tp 是什么类型,我之前的疑惑也在这里,这个函数的参数 typename std::remove_reference<_Tp>::type& __t 和模板参数类型 _Tp 看起来关系很密切,但好像又没关系,因为虽然知道参数类型 typename std::remove_reference<_Tp>::type& __t 是个左值引用,但是你并不知道 _Tp 是什么类型,它还是需要显式来指定的,我们接下来试一试和我们想的一不一样。

forwawrd 完美转发

直接拿一个之前写过的完美转发例子吧,代码如下:

#include <iostream>
#include <utility>

void Print(int& val)
{
    std::cout << "lvalue refrence: val=" << val << std::endl;
}

void Print(int&& val)
{
    std::cout << "rvalue refrence: val=" << val << std::endl;
}

template<typename T>
void TPrint(T&& t)
{
    return Print(std::forward<T>(t));
}

int main()
{
    int date = 2022;
    TPrint(date);
    TPrint(501);

    return 0;
}

编译运行之后的结果如下:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
lvalue refrence: val=2022
rvalue refrence: val=501

通过结果我们发现 std::forward 函数在拥有万能引用参数的模板函数中实现了完美转发,左值转发后调用了参数为左值引用的函数,右值转发后调用了参数为右值引用的函数,这时如果我们把调用 std::forward 的地方改一下,去掉指定的参数类型 T,写成 return Print(std::forward(t));,然后编译看看会发生什么

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
testf.cpp: In instantiation of ‘void TPrint(T&&) [with T = int&]’:
testf.cpp:23:16:   required from here
testf.cpp:17:30: error: no matching function for call to ‘forward(int&)’
     return Print(std::forward(t));
                              ^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,
                 from /usr/include/c++/5/bits/stl_algobase.h:64,
                 from /usr/include/c++/5/bits/char_traits.h:39,
                 from /usr/include/c++/5/ios:40,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from testf.cpp:1:
/usr/include/c++/5/bits/move.h:76:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&)
     forward(typename std::remove_reference<_Tp>::type& __t) noexcept
     ^
/usr/include/c++/5/bits/move.h:76:5: note:   template argument deduction/substitution failed:
testf.cpp:17:30: note:   couldn't deduce template parameter ‘_Tp’
     return Print(std::forward(t));
                              ^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,
                 from /usr/include/c++/5/bits/stl_algobase.h:64,
                 from /usr/include/c++/5/bits/char_traits.h:39,
                 from /usr/include/c++/5/ios:40,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from testf.cpp:1:
/usr/include/c++/5/bits/move.h:87:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&&)
     forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
     ^
/usr/include/c++/5/bits/move.h:87:5: note:   template argument deduction/substitution failed:
testf.cpp:17:30: note:   couldn't deduce template parameter ‘_Tp’
     return Print(std::forward(t));
                              ^
testf.cpp:17:33: error: return-statement with a value, in function returning 'void' [-fpermissive]
     return Print(std::forward(t));
                                 ^
testf.cpp: In instantiation of ‘void TPrint(T&&) [with T = int]’:
testf.cpp:24:15:   required from here
testf.cpp:17:30: error: no matching function for call to ‘forward(int&)’
     return Print(std::forward(t));
                              ^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,
                 from /usr/include/c++/5/bits/stl_algobase.h:64,
                 from /usr/include/c++/5/bits/char_traits.h:39,
                 from /usr/include/c++/5/ios:40,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from testf.cpp:1:
/usr/include/c++/5/bits/move.h:76:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&)
     forward(typename std::remove_reference<_Tp>::type& __t) noexcept
     ^
/usr/include/c++/5/bits/move.h:76:5: note:   template argument deduction/substitution failed:
testf.cpp:17:30: note:   couldn't deduce template parameter ‘_Tp’
     return Print(std::forward(t));
                              ^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,
                 from /usr/include/c++/5/bits/stl_algobase.h:64,
                 from /usr/include/c++/5/bits/char_traits.h:39,
                 from /usr/include/c++/5/ios:40,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from testf.cpp:1:
/usr/include/c++/5/bits/move.h:87:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&&)
     forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
     ^
/usr/include/c++/5/bits/move.h:87:5: note:   template argument deduction/substitution failed:
testf.cpp:17:30: note:   couldn't deduce template parameter ‘_Tp’
     return Print(std::forward(t));
                              ^
testf.cpp:17:33: error: return-statement with a value, in function returning 'void' [-fpermissive]
     return Print(std::forward(t));

这次出了一个很长的编译错误,看来还是需要指定类型的,既然是需要指定的,那我们指定成其他的有没有问题呢?比如写成下面这样:

return Print(std::forward<float>(t));

编译运行结果如下,都变成了右值引用:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
rvalue refrence: val=2022
rvalue refrence: val=501

再改成下面这样:

return Print(std::forward<T&>(t));

编译运行结果如下,都变成了左值引用:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
lvalue refrence: val=2022
lvalue refrence: val=501

完美转发失效

上面的这两个例子能说明完美转发失效了吗?这倒也不能说明,第一个例子全都转发成了右值引用,第二个例子全部转发成了左值引用,和我们指定的类型是一致的,也算实现了完美转发,只不过通过这些例子更加深入的理解了完美转发的含义,就是能保证转化成指定的类型,如果指定的类型是个万能引用,就会根据原始类型来完成转发,本次探索之旅到此也就结束了,解答疑惑是个有趣的事情。

总结

  • std::forward 的本质还是进行强制类型转换,它会把传入的参数转发成指定的类型
  • 完美转发其实是可以脱离左值右值概念的,这也是对完美转发更加深入的理解吧

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

每一点付出终有回报,每一滴汗水从不会白流,可能你看不见也摸不着,但其实它已经悄然声息的改变了你,改变了你周围的点点滴滴~

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在C++11中,有两个重要的函数std::forwardstd::move。 std::forward函数std::forward函数是用来解决转发参数类型的问题的。当我们在编写一个函数模板时,如果需要将参数转发给另一个函数时,我们需要使用std::forward函数来将参数正确地转发过去。std::forward函数的定义如下: template <class T> constexpr T&& forward(typename std::remove_reference<T>::type& x) noexcept; template <class T> constexpr T&& forward(typename std::remove_reference<T>::type&& x) noexcept; std::forward函数的作用是将一个参数转发给另一个函数,同时保留该参数的原始类型(左值或右值)。当我们需要将一个参数转发给另一个函数时,我们通常需要使用std::forward函数来实现类型转发。例如: template <typename T> void foo(T&& arg) { bar(std::forward<T>(arg)); } std::move函数std::move函数用于将一个对象的值转移到另一个对象中,同时将原始对象的值置为未定义状态。std::move函数的定义如下: template <class T> typename std::remove_reference<T>::type&& move(T&& x) noexcept; std::move函数的作用是将一个对象的值转移到另一个对象中,同时保留该对象的原始类型(左值或右值)。当我们需要将一个对象的值转移到另一个对象时,我们通常需要使用std::move函数来实现。例如: std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = std::move(v1); // v1现在是未定义状态,v2的值为{1, 2, 3}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlbertS

常来“玩”啊~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值