好久没更新,不是没学习,是不想写到CSDN而已,都写在本地了。这次是因为我觉得我对于元编程的理解还可以了,使用上还是比较少,那就得练了。
文章目录
- 1. 类型推导工具
- 2. type traits
- 2.1 基本例子
- 2.2 其他traits
- 3. SFINAE
- 3.1 void_t
- 3.2 SFINAE
- 3.3 enable_if
1. 类型推导工具
类型和值的转换
template<class T,T v>
struct integral_constant{
static const T value = v;
typedef T value_type;
typedef integral_constant type;
};
T是类型,V是值。
常用的就是bool_constant,值和类型对应起来了。
template<bool,B>
using bool_constant = integral_constant<bool, B>;
typedef bool_constant<true>true_type;//<true_type能够拿到值
typedef bool_constant<false>false_type;
用途呢?值是不能用来重载的,但是类型是可以的。很多时候编译期拿到的都是值,比如is_same_v,用它可以转换成类型。
2. type traits
2.1 基本例子
判断:is_array,is_enum,is_function,is_reference,is_const,has_virtual_destructor等等
修改:decay,make_signed,remove_const,remove_cv,remove_reference等等
判断的例子:is_reference这个可以自己实现,有的实现不了。
template<class T>
struct is_reference:public false_type{};
template<class T>
struct is_reference<T&>:public true_type{};
template<class T>
struct is_refernce<T&&>:public true_type{};
template<class T>
inline constexpr bool is_reference_v = is_reference<T>::value;
修改的例子:remove_const
template<class T>
struct remove_const{
typedef T type;
};
template<class T>
struct remove_const<const T>{
typedef T type;
};
template<class T>
using remove_const_t = typename remove_const<T>::type
有个坑,const string &,或者const int &这种结构,remove_const后结果都还是自己,因为他们最外层是ref,即便用is_const得到的都是假,要像洋葱一样一层一层剥,最外层是referencce,再一层才是const。如果需要得到string &,需要先去掉reference,再去掉const,再加上reference。这个的主要原因是const string&应该写成string const&,我们习惯反着写。
gpt给的例子
#include <bits/stdc++.h>
#include <iostream>
#include <type_traits>
template <typename T>
void remove_const_example() {
std::cout << std::is_reference<T>::value << std::endl;
std::cout << std::is_const<T>::value << " -> " // 输出是否是 const 类型
<< std::is_const<typename std::remove_const<T>::type>::value
<< std::endl; // 输出移除 const 后的类型是否是 const
}
int main() {
remove_const_example<const int&>();
// remove_const_example<const int>(); // 输出: 1 -> 0
// remove_const_example<int>(); // 输出: 0 -> 0
// remove_const_example<const double>(); // 输出: 1 -> 0
return 0;
}
2.2 其他traits
吴老师举了几个他常用的例子
is_pod的traits不建议用了,最新的c++已经没有了,因为这一般不是我们想要的,用下面两个替换掉了
-
is_standard_layout:内存使用标准布局,其实就是没有虚函数、没有继承、没有static函数这些。
-
is_trivial:默认构造、复制、析构是不是不需要做任何特殊处理。分为下面两种
- is_trivially_default_constructible:默认构造不需要执行任何动作
- is_trivially_copyable:复制时可以简单复制内存块,不需要做特殊处理。这个还分为下面四种
- is_trivially_copy_constructible:拷贝构造可以简单复制内存块
- is_trivially_copy_assignable:拷贝赋值可以复制简单内存块
- is_trivially_move_constructible:移动构造可以复制简单内存块
- is_trivially_mvoe_assignable:移动赋值可以复制简单内存块
- is_trivially_destructible:析构不需要执行任何操作
3. SFINAE
3.1 void_t
如果变参模板中能够正常匹配就去掉全部类型,只剩void,如果不能,就直接失败(SFINAE)。
template<typename...>
using void_t=void;
3.2 SFINAE
用void_t实现has_reserve
#include <bits/stdc++.h>
template <typename T, typename C= void>
struct has_reserve : std::false_type {
has_reserve() { std::cout << "not" << std::endl; }
};
template <typename T>
struct has_reserve<T, std::void_t<decltype(std::declval<T&>().reserve(1U))>>
: std::true_type {
has_reserve() { std::cout << "has" << std::endl; }
};
struct A {};
struct B {
void reserve(unsigned int) {}
};
int main() {
has_reserve<A> a;
has_reserve<B> b;
}
我之前为什么一直没理解SFINAE,其实主要是没理解默认参数的特化。有几个核心点
- 为什么主模板要写个默认参数?
- 先说答案,不写默认的,就写个
typename = void
也是可以的(其实这个更好),原因在下面。 - 这个默认参数也不是必须是void,但是为了要用下面的
std::void_t
就只能用void,我尝试写了一个int_t,也是可以的。 - 特化版本的第二个如果能够满足里面的条件,就变成了has_reserve<T,void>。前面说到void是默认参数,或者说正常情况是没人给has_reserve传第二个参数的,也就是说第二个参数必须是void。而特化版本第二个也必须是void才能匹配上主模板。还是那句话,主模板是void不是因为自己,是因为特化需要void_t。
- 如果主模板的默认参数是int,其它不变会怎么样?
特化会依然是它的特化,但是不会被匹配到。还是那句话,没人会传入第二个参数,就是说,你写的 has_reserve<A> a;
会变成 has_reserve<A,int> a;
,怎么可能和 has_reserve<T,void>
匹配呢?
如果你非要说,我就这么写has_reserve<A,void> a;
,那好,你能匹配上。
- 特化std::void_t里面是什么呢?
这里面确实有点乱,但还不至于恶心到看不懂。std::declval<T&>()
用于在编译期构造一个对象,这个东西我搜来搜去基本都用来检查是否有某个函数了。cppreference上说哪怕它的构造不能调,也可以这么写,因为只有编译阶段生效。注意declval前面一定要加std。
3.3 enable_if
先说enable_if的实现。第一个是参数是true,type就返回T,不是,就不满足sfinae标准,匹配失败。
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
上面的扩展
#include <bits/stdc++.h>
template <typename T, typename = void>
struct has_reserve : std::false_type {
has_reserve() { std::cout << "not" << std::endl; }
};
template <typename T>
struct has_reserve<T, std::void_t<decltype(std::declval<T&>().reserve(1U))>>
: std::true_type {
has_reserve() { std::cout << "has" << std::endl; }
};
template <typename C>
std::enable_if_t<has_reserve<C>::value, void> put_data(C& container) {
std::cout << "has func" << std::endl;
}
template <typename C>
std::enable_if_t<!has_reserve<C>::value, void> put_data(C& container) {
std::cout << "not" << std::endl;
}
struct A {};
struct B {
void reserve(unsigned int) {}
};
int main() {
has_reserve<A> a;
has_reserve<B, void> b;
std::vector<int> c;
std::list<int> d;
put_data(c);
put_data(d);
}
首先,我把主模板的默认参数改掉了。这种写法相当于把enable加到了返回值上。回到enable_if的定义,如果第一个value失败了就SFINAE失败,换下一个,如果是真的就直接返回第二个参数:void。如果函数需要有返回值那就把enable_if后面的void换成其它类型,比如int。