本文是<functional>系列的第3篇。

引用传参

我有一个函数:

void modify(int& i)
{
++i;
}

因为参数类型是int&,所以函数能够修改传入的整数,而非其拷贝。

然后我用std::bind把它和一个int绑定起来:

int i = 1;
auto f = std::bind(modify, i);
f();
std::cout << i << std::endl;

可是i还是1,为什么呢?原来std::bind会把所有参数都拷贝,即使它是个左值引用。所以modify中修改的变量,实际上是std::bind返回的函数对象中的一个int,并非原来的i。

我们需要std::reference_wrapper:

int j = 1;
auto g = std::bind(modify, std::ref(j));
g();
std::cout << j << std::endl;

std::ref(j)返回的就是std::reference_wrapper<int>对象。

reference_wrapper

std::reference_wrapper及其辅助函数大致长成这样:

template<typename T>
class reference_wrapper
{
public:
template<typename U>
reference_wrapper(U&& x) : ptr(std::addressof(x)) { } reference_wrapper(const reference_wrapper&) noexcept = default;
reference_wrapper& operator=(const reference_wrapper& x) noexcept = default; constexpr operator T& () const noexcept { return *ptr; }
constexpr T& get() const noexcept { return *ptr; } template<typename... Args>
auto operator()(Args&&... args) const
{
return get()(std::forward<Args>(args)...);
} private:
T* ptr;
}; template<typename T>
reference_wrapper<T> ref(T& t) noexcept
{
return reference_wrapper<T>(t);
}
template<typename T>
reference_wrapper<T> ref(reference_wrapper<T> t) noexcept
{
return t;
}
template<typename T>
void ref(const T&&) = delete; template<typename T>
reference_wrapper<const T> cref(const T& t) noexcept
{
return reference_wrapper<const T>(t);
}
template<typename T>
reference_wrapper<const T> cref(reference_wrapper<T> t) noexcept
{
return reference_wrapper<const T>(t.get());
}
template<typename T>
void cref(const T&&) = delete;

可见,std::reference_wrapper不过是包装一个指针罢了。它重载了operator T&,对象可以隐式转换回原来的引用;它还重载了operator(),包装函数对象时可以直接使用函数调用运算符;调用其他成员函数时,要先用get方法获得其内部的引用。

std::reference_wrapper的意义在于:

  1. 引用不是对象,不存在引用的引用、引用的数组等,但std::reference_wrapper是,使得定义引用的容器成为可能;

  2. 模板函数无法辨别你在传入左值引用时的意图是传值还是传引用,std::ref和std::cref告诉那个模板,你要传的是引用。

实现

尽管std::reference_wrapper的简单(但是不完整的)实现可以在50行以内完成,GCC的标准库为了实现一个完美的std::reference_wrapper还是花了300多行(还不包括std::invoke),其中200多行是为了定义result_type、argument_type、first_argument_type和second_argument_type这几个在C++17中废弃、C++20中移除的成员类型。如果你是在C++20完全普及以后读到这篇文章的,就当考古来看吧!

继承成员类型

定义这些类型所用的工具是继承,一种特殊的、没有“is-a”含义的public继承。以_Maybe_unary_or_binary_function为例:

template<typename _Arg, typename _Result>
struct unary_function
{
typedef _Arg argument_type;
typedef _Result result_type;
}; template<typename _Arg1, typename _Arg2, typename _Result>
struct binary_function
{
typedef _Arg1 first_argument_type;
typedef _Arg2 second_argument_type;
typedef _Result result_type;
}; template<typename _Res, typename... _ArgTypes>
struct _Maybe_unary_or_binary_function { }; template<typename _Res, typename _T1>
struct _Maybe_unary_or_binary_function<_Res, _T1>
: std::unary_function<_T1, _Res> { }; template<typename _Res, typename _T1, typename _T2>
struct _Maybe_unary_or_binary_function<_Res, _T1, _T2>
: std::binary_function<_T1, _T2, _Res> { };

然后std::function<Res(Args...)>去继承_Maybe_unary_or_binary_function<Res, Args...>:当sizeof...(Args) == 1时继承到std::unary_function,定义argument_type;当sizeof...(Args) == 2时继承到std::binary_function,定义first_argument_type和second_argument_type;否则继承一个空的_Maybe_unary_or_binary_function,什么定义都没有。

各种模板技巧,tag dispatching、SFINAE等,面对这种需求都束手无策,只有继承管用。

成员函数特征

template<typename _Signature>
struct _Mem_fn_traits; template<typename _Res, typename _Class, typename... _ArgTypes>
struct _Mem_fn_traits_base
{
using __result_type = _Res;
using __maybe_type
= _Maybe_unary_or_binary_function<_Res, _Class*, _ArgTypes...>;
using __arity = integral_constant<size_t, sizeof...(_ArgTypes)>;
}; #define _GLIBCXX_MEM_FN_TRAITS2(_CV, _REF, _LVAL, _RVAL) \
template<typename _Res, typename _Class, typename... _ArgTypes> \
struct _Mem_fn_traits<_Res (_Class::*)(_ArgTypes...) _CV _REF> \
: _Mem_fn_traits_base<_Res, _CV _Class, _ArgTypes...> \
{ \
using __vararg = false_type; \
}; \
template<typename _Res, typename _Class, typename... _ArgTypes> \
struct _Mem_fn_traits<_Res (_Class::*)(_ArgTypes... ...) _CV _REF> \
: _Mem_fn_traits_base<_Res, _CV _Class, _ArgTypes...> \
{ \
using __vararg = true_type; \
}; #define _GLIBCXX_MEM_FN_TRAITS(_REF, _LVAL, _RVAL) \
_GLIBCXX_MEM_FN_TRAITS2( , _REF, _LVAL, _RVAL) \
_GLIBCXX_MEM_FN_TRAITS2(const , _REF, _LVAL, _RVAL) \
_GLIBCXX_MEM_FN_TRAITS2(volatile , _REF, _LVAL, _RVAL) \
_GLIBCXX_MEM_FN_TRAITS2(const volatile, _REF, _LVAL, _RVAL) _GLIBCXX_MEM_FN_TRAITS( , true_type, true_type)
_GLIBCXX_MEM_FN_TRAITS(&, true_type, false_type)
_GLIBCXX_MEM_FN_TRAITS(&&, false_type, true_type) #if __cplusplus > 201402L
_GLIBCXX_MEM_FN_TRAITS(noexcept, true_type, true_type)
_GLIBCXX_MEM_FN_TRAITS(& noexcept, true_type, false_type)
_GLIBCXX_MEM_FN_TRAITS(&& noexcept, false_type, true_type)
#endif #undef _GLIBCXX_MEM_FN_TRAITS
#undef _GLIBCXX_MEM_FN_TRAITS2

_Mem_fn_traits是成员函数类型的特征(trait)类型,定义了__result_type、__maybe_type、__arity和__vararg成员类型:__arity表示元数,__vararg指示成员函数类型是否是可变参数的(如std::printf,非变参模板)。... ...中的前三个点表示变参模板,后三个点表示可变参数,参考:What are the 6 dots in template parameter packs?

成员函数类型有const、volatile、&/&&、noexcept(C++17开始noexcept成为函数类型的一部分)4个维度,共24种,单独定义太麻烦,所以用了宏。

检测成员类型

一个类模板,当模板参数的类型定义了成员类型result_type时该类模板也定义它,否则不定义它,如何实现?我刚刚新学到一种方法,用void_t(即__void_t)。

void_t的定义出奇地简单:

template<typename...>
using void_t = void;

不就是一个void嘛,有什么用呢?请看:

template<typename _Functor, typename = __void_t<>>
struct _Maybe_get_result_type
{ }; template<typename _Functor>
struct _Maybe_get_result_type<_Functor,
__void_t<typename _Functor::result_type>>
{ typedef typename _Functor::result_type result_type; };

第二个定义是第一个定义的特化。当_Functor类型定义了result_type时,两个都正确,但是第二个更加特化,匹配到第二个,传播result_type;反之,第二个在实例化过程中发生错误,根据SFINAE,匹配到第一个,不定义result_type。

void_t的技巧,本质上还是SFINAE。

以下两个类同理:

template<typename _Tp, typename = __void_t<>>
struct _Refwrap_base_arg1
{ }; template<typename _Tp>
struct _Refwrap_base_arg1<_Tp,
__void_t<typename _Tp::argument_type>>
{
typedef typename _Tp::argument_type argument_type;
}; template<typename _Tp, typename = __void_t<>>
struct _Refwrap_base_arg2
{ }; template<typename _Tp>
struct _Refwrap_base_arg2<_Tp,
__void_t<typename _Tp::first_argument_type,
typename _Tp::second_argument_type>>
{
typedef typename _Tp::first_argument_type first_argument_type;
typedef typename _Tp::second_argument_type second_argument_type;
};

分类讨论

#if __cpp_noexcept_function_type
#define _GLIBCXX_NOEXCEPT_PARM , bool _NE
#define _GLIBCXX_NOEXCEPT_QUAL noexcept (_NE)
#else
#define _GLIBCXX_NOEXCEPT_PARM
#define _GLIBCXX_NOEXCEPT_QUAL
#endif /**
* Base class for any function object that has a weak result type, as
* defined in 20.8.2 [func.require] of C++11.
*/
template<typename _Functor>
struct _Weak_result_type_impl
: _Maybe_get_result_type<_Functor>
{ }; /// Retrieve the result type for a function type.
template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
struct _Weak_result_type_impl<_Res(_ArgTypes...) _GLIBCXX_NOEXCEPT_QUAL>
{ typedef _Res result_type; }; /// Retrieve the result type for a varargs function type.
template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
struct _Weak_result_type_impl<_Res(_ArgTypes......) _GLIBCXX_NOEXCEPT_QUAL>
{ typedef _Res result_type; }; /// Retrieve the result type for a function pointer.
template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
struct _Weak_result_type_impl<_Res(*)(_ArgTypes...) _GLIBCXX_NOEXCEPT_QUAL>
{ typedef _Res result_type; }; /// Retrieve the result type for a varargs function pointer.
template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
struct
_Weak_result_type_impl<_Res(*)(_ArgTypes......) _GLIBCXX_NOEXCEPT_QUAL>
{ typedef _Res result_type; }; // Let _Weak_result_type_impl perform the real work.
template<typename _Functor,
bool = is_member_function_pointer<_Functor>::value>
struct _Weak_result_type_memfun
: _Weak_result_type_impl<_Functor>
{ }; // A pointer to member function has a weak result type.
template<typename _MemFunPtr>
struct _Weak_result_type_memfun<_MemFunPtr, true>
{
using result_type = typename _Mem_fn_traits<_MemFunPtr>::__result_type;
}; // A pointer to data member doesn't have a weak result type.
template<typename _Func, typename _Class>
struct _Weak_result_type_memfun<_Func _Class::*, false>
{ }; /**
* Strip top-level cv-qualifiers from the function object and let
* _Weak_result_type_memfun perform the real work.
*/
template<typename _Functor>
struct _Weak_result_type
: _Weak_result_type_memfun<typename remove_cv<_Functor>::type>
{ }; /**
* Derives from unary_function or binary_function when it
* can. Specializations handle all of the easy cases. The primary
* template determines what to do with a class type, which may
* derive from both unary_function and binary_function.
*/
template<typename _Tp>
struct _Reference_wrapper_base
: _Weak_result_type<_Tp>, _Refwrap_base_arg1<_Tp>, _Refwrap_base_arg2<_Tp>
{ }; // - a function type (unary)
template<typename _Res, typename _T1 _GLIBCXX_NOEXCEPT_PARM>
struct _Reference_wrapper_base<_Res(_T1) _GLIBCXX_NOEXCEPT_QUAL>
: unary_function<_T1, _Res>
{ }; template<typename _Res, typename _T1>
struct _Reference_wrapper_base<_Res(_T1) const>
: unary_function<_T1, _Res>
{ }; template<typename _Res, typename _T1>
struct _Reference_wrapper_base<_Res(_T1) volatile>
: unary_function<_T1, _Res>
{ }; template<typename _Res, typename _T1>
struct _Reference_wrapper_base<_Res(_T1) const volatile>
: unary_function<_T1, _Res>
{ }; // - a function type (binary)
template<typename _Res, typename _T1, typename _T2 _GLIBCXX_NOEXCEPT_PARM>
struct _Reference_wrapper_base<_Res(_T1, _T2) _GLIBCXX_NOEXCEPT_QUAL>
: binary_function<_T1, _T2, _Res>
{ }; template<typename _Res, typename _T1, typename _T2>
struct _Reference_wrapper_base<_Res(_T1, _T2) const>
: binary_function<_T1, _T2, _Res>
{ }; template<typename _Res, typename _T1, typename _T2>
struct _Reference_wrapper_base<_Res(_T1, _T2) volatile>
: binary_function<_T1, _T2, _Res>
{ }; template<typename _Res, typename _T1, typename _T2>
struct _Reference_wrapper_base<_Res(_T1, _T2) const volatile>
: binary_function<_T1, _T2, _Res>
{ }; // - a function pointer type (unary)
template<typename _Res, typename _T1 _GLIBCXX_NOEXCEPT_PARM>
struct _Reference_wrapper_base<_Res(*)(_T1) _GLIBCXX_NOEXCEPT_QUAL>
: unary_function<_T1, _Res>
{ }; // - a function pointer type (binary)
template<typename _Res, typename _T1, typename _T2 _GLIBCXX_NOEXCEPT_PARM>
struct _Reference_wrapper_base<_Res(*)(_T1, _T2) _GLIBCXX_NOEXCEPT_QUAL>
: binary_function<_T1, _T2, _Res>
{ }; template<typename _Tp, bool = is_member_function_pointer<_Tp>::value>
struct _Reference_wrapper_base_memfun
: _Reference_wrapper_base<_Tp>
{ }; template<typename _MemFunPtr>
struct _Reference_wrapper_base_memfun<_MemFunPtr, true>
: _Mem_fn_traits<_MemFunPtr>::__maybe_type
{
using result_type = typename _Mem_fn_traits<_MemFunPtr>::__result_type;
};

不说了,看图:

我的感受:

大功告成

template<typename _Tp>
class reference_wrapper
: public _Reference_wrapper_base_memfun<typename remove_cv<_Tp>::type>
{
_Tp* _M_data; public:
typedef _Tp type; reference_wrapper(_Tp& __indata) noexcept
: _M_data(std::__addressof(__indata))
{ } reference_wrapper(_Tp&&) = delete; reference_wrapper(const reference_wrapper&) = default; reference_wrapper&
operator=(const reference_wrapper&) = default; operator _Tp&() const noexcept
{ return this->get(); } _Tp&
get() const noexcept
{ return *_M_data; } template<typename... _Args>
typename result_of<_Tp&(_Args&&...)>::type
operator()(_Args&&... __args) const
{
return std::__invoke(get(), std::forward<_Args>(__args)...);
}
}; template<typename _Tp>
inline reference_wrapper<_Tp>
ref(_Tp& __t) noexcept
{ return reference_wrapper<_Tp>(__t); } template<typename _Tp>
inline reference_wrapper<const _Tp>
cref(const _Tp& __t) noexcept
{ return reference_wrapper<const _Tp>(__t); } template<typename _Tp>
void ref(const _Tp&&) = delete; template<typename _Tp>
void cref(const _Tp&&) = delete; template<typename _Tp>
inline reference_wrapper<_Tp>
ref(reference_wrapper<_Tp> __t) noexcept
{ return __t; } template<typename _Tp>
inline reference_wrapper<const _Tp>
cref(reference_wrapper<_Tp> __t) noexcept
{ return { __t.get() }; }

最后组装一下就好啦!

引用传参与reference_wrapper的更多相关文章

  1. C++二级指针和指针引用传参

    前提 一级指针和引用 已经清晰一级指针和引用. 可参考:指针和引用与及指针常量和常量指针 或查阅其他资料. 一级指针和二级指针 个人觉得文字描述比较难读懂,直接看代码运行结果分析好些,如果想看文字分析 ...

  2. Js 赋值传值和引用传址

    赋值传值和引用传址 在JavaScript中基本数据类型都是赋值传值,复合数据类型都是引用传址(传地址) 基本数据类型的变量名和数据是直接存在"快速内存"(栈内存)中,而复合数据类 ...

  3. Go 到底有没有引用传参(对比 C++ )

    Go 到底有没有引用传参(对比 C++ ) C++ 中三种参数传递方式 值传递: 最常见的一种传参方式,函数的形参是实参的拷贝,函数中改变形参不会影响到函数外部的形参.一般是函数内部修改参数而又不希望 ...

  4. js学习笔记<拷贝传值,引用传址和匿名函数>

    拷贝传值:把一个变量的值拷贝一份,传给了另外一个变量拷贝传值中,两个变量之间没有任何联系,修改其中一个一个变量的值,原来的变量不变. 例: var arr1 = ["张三",24, ...

  5. Python中的引用传参

    Python中函数参数是引用传递(注意不是值传递).对于不可变类型,因变量不能修改,所以运算不会影响到变量自身:而对于可变类型来说,函数体中的运算有可能会更改传入的参数变量. 引用传参一: >& ...

  6. [ 随手记6 ] C/C++ 形参、实参、按值传参、指针传参、引用传参

    个人原创: 1. 形参:形式上的参数,一般多在函数声明.函数定义的参数上: 2. 实参:实体参数,有实际的值,在运算上被循环使用的值: 3. 按值传参:按值,就是把实际的值传给函数内部: 4. 指针传 ...

  7. &符号 (弃用引用传参了,不要用!!)

    写法一 $age = function grow($age) { $age += ; return $age; } echo grow($age) echo $age 写法二 $age = funct ...

  8. Java中的new关键字和引用传参

    先看看Java中如何使用new关键字创建一个对象的. [java] view plain copy public class Student { public String name; public  ...

  9. c++拷贝构造函数引用传参

    看一道C++面试题: 给出下述代码,分析编译运行的结果,并提供3个选项: A.编译错误  B.编译成功,运行时程序崩溃  C.编译运行正常,输出10 class A { private: int va ...

随机推荐

  1. 使用sklearn做单机特征工程(Performing Feature Engineering Using sklearn)

    本文转载自使用sklearn做单机特征工程 目录 目录 特征工程是什么 数据预处理 1 无量纲化 11 标准化 12 区间缩放法 13 标准化与归一化的区别 2 对定量特征二值化 3 对定性特征哑编码 ...

  2. MySQL数据库参数调优方法

    怎么配置MySQL服务器,但考虑到服务器硬件配置的不同,具体应用的差别,那些文章的做法只能作为初步设置参考,我们需要根据自己的情况进行配置优化,好的做法是MySQL服务器稳定运行了一段时间后运行,根据 ...

  3. 一个完整的机器学习项目在Python中演练(四)

    大家往往会选择一本数据科学相关书籍或者完成一门在线课程来学习和掌握机器学习.但是,实际情况往往d是,学完之后反而并不清楚这些技术怎样才能被用在实际的项目流程中.就像你的脑海中已经有了一块块" ...

  4. 添加windows开机自启动项

    windows系统下我们最常用的是禁用启动项,但如果程序不在自启动列表里面,如何添加程序启动呢. 其实也很简单,首先找到windows启动路径C:\Users\NL\AppData\Roaming\M ...

  5. python框架-Django创建项目

    创建项目 django-admin startproject douban//创建project cd douban python manage.py startapp books//创建app项目 ...

  6. coding++:mybatis 嵌套查询子查询column传多个参数描述

    mybatis 嵌套查询子查询column传多个参数如下: 2.代码示例 备注:注意,相同颜色的单词都是有关联的 <resultMap id="blogResult" typ ...

  7. Spring Framework之IoC容器

    Spring IoC 概述 问题 1.什么是依赖倒置? 2.什么是控制反转? 3.什么是依赖注入? 4.它们之间的关系是怎样的? 5.优点有哪些? 依赖倒置原则 (Dependency Inversi ...

  8. 学习 MyBatis 的一点小总结 —— 底层源码初步分析

    目录 MyBatis 如何获取数据库源? MyBatis 如何获取 sql 语句? MyBatis 如何执行 sql 语句? MyBatis 如何实现不同类型数据之间的转换? 在过去程序员使用 JDB ...

  9. Activiti网关--排他网关

    排他网关 1.什么是排他网关 排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策. 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为 true 则执行该分 ...

  10. java对象clone

    java克隆 为什么需要克隆 我们在很多时候需要使用一个对象去记录另外一个对象的当前状态,对象中可能会有很多属性,如果我们一个一个去设置,不仅不方便,而且效率很低,我们看一个初学者可能遇到的问题 cl ...