借助void_t和declval实现is_copy_assignable
【引例】
class ACPABL { }; class BCPABL { public: BCPABL& operator=(const BCPABL& tmpobj) //拷贝赋值运算符 { //... return *this; } }; class CCPABL { public: CCPABL& operator=(const CCPABL& tmpobj) = delete;//拷贝赋值运算符标记为delete };
- 调用
ACPABL aobj1; ACPABL aobj2; aobj2 = aobj1; //拷贝赋值 BCPABL bobj1; BCPABL bobj2; bobj2 = bobj1; //拷贝赋值 CCPABL cobj1; CCPABL cobj2; cobj2 = cobj1; //拷贝赋值,拷贝赋值运算符标记为delete,这里报错
- 使用标准库is_copy_assignable进行测试
cout << "int:" << std::is_copy_assignable<int>::value << endl; // 1 cout << "ACPABL:" << std::is_copy_assignable<ACPABL>::value << endl; // 1 cout << "BCPABL:" << std::is_copy_assignable<BCPABL>::value << endl; // 1 cout << "CCPABL:" << std::is_copy_assignable<CCPABL>::value << endl; // 1
- is_copy_assignable标准库中的源码
【使用void_t和declval实现is_copy_assignable】
//泛化版本 template <typename T, typename U = std::void_t<>> struct IsCopyAssignable : std::false_type { }; //特化版本 template <typename T> struct IsCopyAssignable<T, std::void_t< decltype( std::declval<T&>() = std::declval<const T&>() ) > > :std::true_type //a = b====> a.operator=(b); //decltype( std::declval<T&>()) = class T& //decltype( std::declval<T&>() = std::declval<const T&>() ) = class T& { };
- std::declval<T&>() = std::declval<const T&>() 解析:
- std::declval<T&>() 返回一个T&的对象,declval<const T&>()返回一个const T&对象
- 对比: CCPABL& operator=(const CCPABL& tmpobj),将CCPABL替换成T进行理解
- 调用
cout << "int:" << IsCopyAssignable<int>::value << endl; //1 cout << "ACPABL:" << IsCopyAssignable<ACPABL>::value << endl; //1 cout << "BCPABL:" << IsCopyAssignable<BCPABL>::value << endl; //1 cout << "CCPABL:" << IsCopyAssignable<CCPABL>::value << endl; //0
综合范例
- 两个vector容器,元素数量相同(10元素),但是这两个容器中的元素类型不同(比如第一个容器里边是int类型的元素,第二个容器里面是double类型的元素)
- 希望重载一下+运算符,做一下这两个容器的加法运算,加法运算的意思就是第一个容器的第一个元素与第二个容器的第一个元素相加......
【引例1】
int i = int(); //定义了一个int类型的变量,而且这种定义方式会把i的初值设置为0; i = 5; double j = double(); //定义了一个double类型的变量,而且这种定义方式会把j的初值设置为0.0; j = 13.6;
【引例2】
//考虑设计 一个类模板VecAddResult。 template<typename T, typename U> struct VecAddResult { using type = decltype( T() + U()); //把结果类型的推导交给了编译器来做 }; template<typename T,typename U> //T,U表示容器中的元素类型 std::vector< typename VecAddResult<T,U>::type > operator+(std::vector<T> const& vec1, std::vector<U> const& vec2) { //随便写几句代码 std::vector < typename VecAddResult<T, U>::type > tmpvec; return tmpvec; };
- 调用
std::vector< int > veca; std::vector< double > vecb; operator+(veca, vecb); //或者 veca + vecb ;
【引例3】
- 两个类相加,需要重载加法运算符
struct elemC { elemC operator+(const elemC& tmppar); //重载加法运算符以支持加法操作。 };
std::vector< elemC > veca; std::vector< elemC > vecb; veca + vecb;
【引例4】
- 如果给 elemC添加带参构造函数
struct elemC { elemC(int tmpvalue); //带一个参数的构造函数 elemC operator+(const elemC& tmppar); //重载加法运算符以支持加法操作。 };
- 那么decltype( T() + U())执行时创建临时对象也需要传入参数,如果没有传入参数则报错
template<typename T, typename U> struct VecAddResult { using type = decltype( T() + U()); //把结果类型的推导交给了编译器来做 };
- 利用std::declval进行改进
template<typename T, typename U> struct VecAddResult { using type = decltype(std::declval<T>() + std::declval<U>()); };
- 使用别名模板简化vector加法重载函数
template<typename T, typename U> using VecAddResult_t = typename VecAddResult<T, U>::type; template<typename T,typename U> //T,U表示容器中的元素类型 //std::vector< typename VecAddResult<T,U>::type > operator+(std::vector<T> const& vec1, std::vector<U> const& vec2) std::vector< VecAddResult_t<T, U> > operator+(std::vector<T> const& vec1, std::vector<U> const& vec2) { //随便写几句代码 std::vector < typename VecAddResult<T, U>::type > tmpvec; return tmpvec; };
【终版】
- 通过SFINAE特性检测一下两个类型的对象之间到底能不能相加。准备引入一个叫做IfCanAdd类模板,用来判断两个类型能否相加。
- 避免两个类型不能相加,错误出现在类模板中。
template<typename T, typename U> using VecAddResult_t = typename VecAddResult<T, U>::type; //泛化版本 template<typename T, typename U , typename V = std::void_t<>> //T,U,V可以省略template<typename , typename , typename = std::void_t<>> struct IfCanAdd : std::false_type { }; //特化版本 template<typename T, typename U> struct IfCanAdd<T,U, std::void_t< decltype ( std::declval<T>() + std::declval<U>() ) > > : std::true_type { }; //泛化版本的VecAddResult template<typename T, typename U, bool ifcando = IfCanAdd<T,U>::value > //如果T、U两种类型这能相加,那么实例化IfCanAdd特化版本,ifcando为true struct VecAddResult { using type = decltype(std::declval<T>() + std::declval<U>()); }; //特化版本 template<typename T, typename U> struct VecAddResult<T, U, false> //ifcando 为false即执行特化版本,即泛化版本只能执行true的情形 { }; template<typename T,typename U> //T,U表示容器中的元素类型 std::vector< VecAddResult_t<T, U> > operator+(std::vector<T> const& vec1, std::vector<U> const& vec2) { //随便写几句代码 //std::vector < typename VecAddResult<T, U>::type > tmpvec; std::vector < VecAddResult_t<T, U> > tmpvec; return tmpvec; };
- VecAddResult和IfCanAdd这两个类模板之间有一种SFINAE-friendly(SFINAE友好)关系。
- IfCanAdd特化版本中如果能相加,则实例化特化版本,为ture,此时VecAddResult的ifcando 为true,从而有type类型。
- IfCanAdd特化版本中如果不能相加,则实例化泛化版本,为false,此时直接执行VecAddResult<T, U, false>特化版本,没有type类型。
- VecAddResult中特化版本的参数ifcando 为false即执行特化版本,即泛化版本只能执行ifcando 为true的情形。
- 即IfCanAdd有true_type则VecAddResult有type,IfCanAdd有false_type则VecAddResult没有type。
- 编写模板时的原则:如果选择要实例化某个模板(比如上面的operator+,VecAddResult),则实例化时不应该失败(编译错误)。