在之前的 文章 中,谈到了对于 std::bind 的一些 VC++ 上的实现细节,作为之前的延续,也顺便调查了一下常用的 std::forward 的实现机制.
std::forward 是在模板编程中常用的一个函数,主要用于将当前函数的参数原封不动地传入内部的函数(完美转发),就像 std::bind
实现中的那样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
template<class _Ty> class reference_wrapper : public _Weak_types<_Ty>::type { // stand-in for an assignable reference public: static_assert(is_object<_Ty>::value || is_function<_Ty>::value, "reference_wrapper<T> requires T to be an object type " "or a function type."); typedef _Ty type; reference_wrapper(_Ty& _Val) _NOEXCEPT : _Ptr(_STD addressof(_Val)) { // construct } operator _Ty&() const _NOEXCEPT { // return reference return (*_Ptr); } _Ty& get() const _NOEXCEPT { // return reference return (*_Ptr); } template<class... _Types> auto operator()(_Types&&... _Args) const -> decltype(_STD invoke(get(), _STD forward<_Types>(_Args)...)) { // invoke object/function return (_STD invoke(get(), _STD forward<_Types>(_Args)...)); } reference_wrapper(_Ty&&) = delete; private: _Ty *_Ptr; }; // TEMPLATE FUNCTIONS ref AND cref template<class _Ty> inline reference_wrapper<_Ty> ref(_Ty& _Val) _NOEXCEPT { // create reference_wrapper<_Ty> object return (reference_wrapper<_Ty>(_Val)); } |
而 std::forward 的定义则是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<class _Ty> inline constexpr _Ty&& forward( typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT { // forward an lvalue as either an lvalue or an rvalue return (static_cast<_Ty&&>(_Arg)); } template<class _Ty> inline constexpr _Ty&& forward( typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT { // forward an rvalue as an rvalue static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call"); return (static_cast<_Ty&&>(_Arg)); } |
在实际使用中因为上面的定义,就需要显式指定模板参数,于是下面的代码尝试利用模板推导,来免去指定模板参数的麻烦.
1 2 3 4 5 6 7 8 9 |
namespace std { template<class _Ty, class = typename enable_if<is_lvalue_reference<_Ty>::value>::type> constexpr auto myForward(_Ty _Arg) ->decltype(static_cast<_Ty&&>(_Arg)) { return static_cast<_Ty&&>(_Arg); } } |
上面的forward会根据参数的类型,来获取Ty,进行一系列模板操作.虽然实际上forward并应该不限定输入的参数必须是左值(也就是说代码写错了),不过通过以下的测试,发现了一些有趣的东西.
1 2 3 4 5 6 7 8 9 10 |
void modify(int& value) { value += 1; } void mid(int& val) { modify(std::myForward(val)); //语法错误,推导类型_Ty为int modify(std::myForward(static_cast<int&&>(1))); //语法错误,推导类型_Ty为int } |
可以发现,无论输入的是左值,还是右值,推导出的类型都是int,并不是期望的int&,或int&&,自然 std::enable_if 会永远匹配失败.
也就是说,实际上推导的规则使得 myForward 永远无法得到想要的结果.
让我们重新参考一下 C++文档 中对于模板参数推导的描述,可以整理出以下规则:
-
- 对 std::initializer_list<T>(初始化列表,比如你用大括号来初始化 std::vector<T> 之类的) 进行参数推导时,会对大括号中每个单独的值进行推导(移除修饰符之类的),当每个值的推导结果都相同时,视为匹配成功,如下所示:
1234567891011template<class T> void func(std::initializer_list<T>){}void test(){const int a = 1;volatile int b = 2;thread_local int c = 3;func({ 1,2,3 }); //每个参数推导出的都是int,成功func({ a,b,c }); //会移除const等修饰符,推导出int,成功func({ 1,2,1.0f }); //有一个是float,失败(并不会将其他的转换成float)}
*注:C++17后还可以推导出大括号里有几个值,好厉害好厉害….x - 对于 可变参数列表 的情况,则会对里面每个参数单独进行推导,最终的结果合并在一起就是可变参数列表的结果(参考 std::forward 的定义)
- 模板参数可以从 函数,函数指针(成员函数也可以哦) 的参数列表中的类型 进行类型推导,推导过程会对那个函数的所有重载进行检查,当有且仅有一个推导成功的重载时,推导成功,如下所示:
1234567891011namespace Test3{template<class T>void func(T(*)(T)) {}int x(char w) { return 0; }int x(int w) { return 0; }int x(float w) { return 0; }void test(){func(x);}}
*注:进一步说,对于函数还可以推导出成员函数所属类的哦:
12345678910111213namespace Test4{template<class T, class U>T* Fx(void(T::*)(U)) { return new T(); }class QwQ{public:void TvT(int i){}};void test(){Fx(&QwQ::TvT);}} - 对于单个参数的推导,则会按照以下的规则进行推导
1234567891011121314/** 如果待推导的不是一个引用类型( void(T x) )* 当传入数组时,会推导成对应的指针类型* int[] -> int** 当传入函数时,会推导为那种函数的指针* void(int) -> void(*)(int)* 当传入一个带有修饰符(const等)的类型时,推导结果会移除其最顶层的修饰符* const int -> int* 当待推导的类型包含修饰符时,最顶层的修饰符会被忽略( void(const T x) )* 当待推导的时一个引用类型时,会推导出其引用到的类型( void(T& x) )* int&(传入) -> int(推导结果)* 当待推导的类型是一个右值的时候,就可以参考完美转发了( void(T&& x) )**/
- 对 std::initializer_list<T>(初始化列表,比如你用大括号来初始化 std::vector<T> 之类的) 进行参数推导时,会对大括号中每个单独的值进行推导(移除修饰符之类的),当每个值的推导结果都相同时,视为匹配成功,如下所示:
从上面的规则中可以发现,上面 myForward,是无法推导出想要的结果的.(不过换成_Ty&& Arg后似乎可以用了…之后再测试下具体效果有没有区别….~)
*注:在 模板推导的文档 中,也详细说明了所有可以推导场合的列表,可以去看以下
*注2:封面依然是f7(eiki)的画(曾经做过手机封面~~(喜欢w