Chapter21 类型特征扩展
C++17扩展了类型特征(标准类型函数)的通用能力并引入了一些新的类型特征。
21.1 类型特征后缀_v
自从C++17起,你可以对所有返回值的类型特征使用后缀_v
(就像你可以为所有返回类型的类型特征使用_t一样)。
例如,对于任何类型T,你现在可以写:
std::is_const_v<T> // 自从C++17起
来代替:
std::is_const<T>::value // 自从C++11起
这适用于所有返回值的类型特征。方法是为每一个标准类型特征定义一个相应的新的变量模板。例如:
namespace std {template<typename T>constexpr bool is_const_v = is_const<T>::value;}
这样一个类型特征可以也用做运行时的条件表达式:
if (std::is_signed_v<char>) {...}
然而,因为这些类型特征是在编译期求值,所以你也可以在编译期使用它们
(例如,在一个编译期if语句中):
if constexpr (std::is_signed_v<char>) {...}
另一个应用是用来支持不同的模板实例化:
// 类C<T>的主模板template<typename T, bool = std::is_pointer_v<T>>class C {...};// 指针类型的偏特化版本:template<typename T>class C<T, true> {...};
这里,类C为指针类型提供了一个偏特化版本。
后缀_v也可以用于返回非bool类型的类型特征,例如std::extent<>,
返回原生数组的某一个维度的大小:
int a[5][7];std::cout << std::extent_v<decltype(a)> << '\n'; // 打印出5std::cout << std::extent_v<decltype(a), 1> << '\n'; // 打印出7
21.2 新的类型特征
表新的类型特征列出了C++17引入的新类型特征。
| 特征 | 效果 |
|---|---|
is_aggregate<T> |
是否是聚合体类型 |
is_swappable<T> |
该类型是否能调用swap() |
is_nothrow_swappable<T> |
该类型是否能调用swap()并且该操作不会抛出异常 |
is_swappable_with<T1, T2> |
特定值类型的这两种类型是否能调用swap() |
is_nothrow_swappable_with<T1, T2> |
特定值类型的这两种类型是否能调用swap()并且该操作不会抛出异常 |
has_unique_object_representations<T> |
是否该类型的两个值相等的对象在内存中的表示也一样 |
is_invocable<T, Args...> |
该类型是否可以用 Args… 调用 |
is_nothrow_invocable<T, Args...> |
该类型是否可以用 Args… 调用,并且该操作不会抛出异常 |
is_invocable_r<RT, T, Args...> |
该类型是否可以用 Args… 调用并返回 RT 类型 |
is_nothrow_invocable_r<RT, T, Args...> |
该类型是否可以用 Args… 调用并返回 RT 类型且不会抛出异常 |
invoke_result<T, Args...> |
用 Args… 作为实参进行调用会返回的类型 |
conjunction<B...> |
对bool特征 B… 进行逻辑与运算 |
disjunction<B...> |
对bool特征 B… 进行逻辑或运算 |
negation<B> |
对bool特征B进行非运算 |
is_execution_policy<T> |
是否是执行策略类型 |
另外,is_literal_type<>和result_of<>自从C++17起被废弃。
下面的段落将详细讨论这些特征。
类型特征is_aggregate<>
std::is_aggregate<T>::value
返回 T 是否是聚合体类型:
template<typename T>struct D : std::string, std::complex<T> {std::string data;};D<float> s{{"hello"}, {4.5, 6.7}, "world"}; // 自从C++17起OKstd::cout << std::is_aggregate<decltype(s)>::value; // 输出:1(true)
类型特征is_swappable<>和is_swappable_with<>
std::is_swappable<T>::valuestd::is_nothrow_swappable<T>::valuestd::is_swappable_with<T1, T2>::valuestd::is_nothrow_swappable_with<T1, T2>::value
在以下情况下返回true:
- 类型
T的左值可以被交换,或者 - 类型
T1和T2的值类型可以交换
(使用nothrow形式时还要保证不会抛出异常)。
注意is_swappable<>或is_nothrow_swappable<>检查你是否
可以交换某个指定类型的值(检查该类型的左值)。
相反,is_swappable_with<>和is_nothrow_swappable_with<>还
会考虑值类型。也就是说:
is_swappable_v<int> // 返回true
等价于
is_swappable_with_v<int&, int&> // 返回true(和上边等价)
而:
is_swappable_with_v<int, int> // 返回false
将会返回false,因为你不能调用std::swap(42, 77)。
例如:
is_swappable_v<std::string> // 返回trueis_swappable_v<std::string&> // 返回trueis_swappable_v<std::string&&> // 返回trueis_swappable_v<void> // 返回falseis_swappable_v<void*> // 返回trueis_swappable_v<char[]> // 返回falseis_swappable_with_v<std::string, std::string> // 返回falseis_swappable_with_v<std::string&, std::string&> // 返回trueis_swappable_with_v<std::string&&, std::string&&> // 返回false
类型特征has_unique_object_representations<>
std::has_unique_object_representations<T>::value
当任意两个值相同的类型T的对象在内存中的表示都相同时返回true。
也就是说,两个相同的值在内存中总是有相同的字节序列。
有这种属性的对象可以通过对字节序列哈希来得到对象的哈希值 (不用担心某些不参与比较的位可能不同的情况)。
类型特征is_invocable<>和is_invocable_r<>
std::is_invocable<T, Args...>::valuestd::is_nothrow_invocable<T, Args...>::valuestd::is_invocable_r<Ret, T, Args...>::valuestd::is_nothrow_invocable_r<Ret, T, Args...>::value
当你能以Args...为实参调用T类型的对象并且返回值可以转换为Ret
类型(并且保证不抛出异常)时返回true。
也就是说,我们可以使用这些特征来测试我们是否可以用Args...为实参调用
(直接调用或者通过std::invoke())T类型的可调用对象并返回Ret类型的值。
例如,如下定义:
struct C {bool operator() (int) const {return true;}};
会导致下列结果:
std::is_invocable<C>::value // falsestd::is_invocable<C, int>::value // truestd::is_invocable<int*>::value // falsestd::is_invocable<int(*)()>::value // truestd::is_invocable_r<bool, C, int>::value // truestd::is_invocable_r<int, C, long>::value // truestd::is_invocable_r<void, C, int>::value // truestd::is_invocable_r<char*, C, int>::value // falsestd::is_invocable_r<long, int(*)(int)>::value // falsestd::is_invocable_r<long, int(*)(int), int>::value // truestd::is_invocable_r<long, int(*)(int), double>::value // true
类型特征invoke_result<>
std::invoke_result<T, Args...>::type
返回当使用实参Args...调用T类型的对象时会返回的类型。
也就是说,我们可以使用这个特征来获知如果使用Args...调用T类型的对象时
将会返回的类型。
这个类型特征替换了result_of<>,后者现在不应该再使用。
例如:
std::string foo(int);using T1 = std::invoke_result_t<decltype(foo), int>; // T1是std::stringstruct ABC {virtual ~ABC() = 0;void operator() (int) const {}};using T2 = typename std::invoke_result<ABC, int>::type; // T2是void
bool类型特征的逻辑操作
表组合其他类型特征的类型特征列出了对bool类型类征(几乎所有的返回bool类型值的标准类型特征) 进行逻辑组合的类型特征。
| 特征 | 效果 |
|---|---|
conjunction<B...> |
对bool特征 B… 进行逻辑 与 运算 |
disjunction<B...> |
对bool特征 B… 进行逻辑 或 运算 |
negation<B> |
对bool特征B进行 非 运算 |
它们的一大应用就是通过组合现有类型特征来定义新的类型特征。 例如,你可以轻松的定义一个检查某个类型是否是“指针”(原生指针,成员函数指针,或者空指针)的特征:
template<typename T>struct IsPtr : std::disjunction<std::is_null_pointer<T>,std::is_member_pointer<T>,std::is_pointer<T>> {};
现在我们在一个编译期if语句中使用这个新的特征:
template<typename T>void foo(T x){if constexpr(IsPtr<T>) {... // 处理是指针的情况}else {... // 处理不是指针的情况}}
作为另一个例子,我们可以定义一个检查指定类型是否是整数或者枚举但又不是bool的类型特征:
template<typename T>struct IsIntegralOrEnum : std::conjunction<std::disjunction<std::is_integral<T>,std::is_enum<T>>,std::negation<std::is_same<T, bool>>> {};
这里,类似于计算
(is_integral<T> || is_enum<T>) && !is_same<T, bool>
这么写的一个好处是std::conjunction<>和std::disjunction<>
会 短路求值 bool表达式,这意味着当 conjunction 出现第一个false或者
disjunction 出现第一个true时就会停止计算。
这节省了编译时间,甚至因为短路求值可以在某些情况下使原本无效的代码变得有效。
例如,如果像下面这样使用不完全类型:
struct X {X(int); // 从int转换而来};struct Y; // 不完全类型
下面的静态断言会失败,因为对于不完全类型is_constructible会陷入未定义行为
(尽管有些编译器接受这样的代码):
// 未定义行为static_assert(std::is_constructible<X, int>{} || std::is_constructible<Y, int>{},"can't init X or Y from int");
下面使用std::disjunction的替代版保证不会失败,
因为当is_constructible<X, int>返回true后求值就会停止:
// OK:static_assert(std::disjunction<std::is_constructible<X, int>,std::is_constructible<Y, int>>{},"can't init X or Y from int");
