类型萃取的设计与实现
概述
- 类型萃取(
type traits)可以理解成编译期函数
|
1 2 |
普通函数:值 → 值 类型萃取:类型 → 类型或编译期常量 |
|
1 2 3 |
std::is_pointer_v<int*> // 类型 → bool std::remove_reference_t<int&> // 类型 → 类型 std::conditional_t<true, int, double> // 条件和类型 → 类型 |
- 它们主要通过:
- 类模板
- 偏特化
using类型成员static constexpr成员
类型萃取的三种主要形式
| 类型 | 输入输出 | 例子 |
预测型 trait |
类型 → bool |
is_same、is_pointer |
转换型 trait |
类型 → 类型 | remove_reference、decay |
属性型 trait |
类型 → 值或元数据 | sizeof、iterator_traits |
- 典型使用形式
|
1 2 |
typename Trait<T>::type // 取出类型 Trait<T>::value // 取出值 |
|
1 2 3 4 |
// 现代简写 Trait_t<T> // C++14 常见:取出 type Trait_v<T> // C++17 常见:取出 value |
|
1 2 3 4 5 |
typename std::remove_reference<T>::type // C++11 std::remove_reference_t<T> // C++14 std::is_pointer<T>::value // C++11 std::is_pointer_v<T> // C++17 |
std::integral_constant
std::integral_constant是很多标准类型萃取的基础
|
1 2 |
std::integral_constant<int, 42> std::integral_constant<bool, true> |
- 它把一个编译期数值同时包装成
- 一个类型
- 一个静态常量
- 一个可以构造的空对象
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
template<class T, T Value> struct IntegralConstant { static constexpr T value = Value; using value_type = T; using type = IntegralConstant<T, Value>; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } }; |
|
1 2 3 4 5 6 7 |
using Answer = IntegralConstant<int, 42>; static_assert(Answer::value == 42); Answer answer; static_assert(answer == 42); static_assert(answer() == 42); |
- 之所以需要把一个值变成类型,是因为类型可以:
- 参与函数重载
- 作为模板参数
- 作为基类
- 被偏特化匹配
true_type、false_type 和 bool_constant
- 标准库定义:
|
1 2 3 4 5 |
using true_type = std::integral_constant<bool, true>; using false_type = std::integral_constant<bool, false>; |
C++17又提供
|
1 2 3 |
template<bool Value> using bool_constant = std::integral_constant<bool, Value>; |
|
1 2 3 4 5 |
// 它们不只是保存 true 和 false,而且是两个不同的类型 static_assert( !std::is_same_v<std::true_type, std::false_type> ); |
- 于是可以写出两个重载
|
1 2 3 4 5 6 |
void process(std::true_type); void process(std::false_type); // 然后用 trait 的结果选择重载 process(std::is_integral<int>{}); // 这里构造出的对象类型是 std::true_type |
实现 is_same
- 目标
|
1 2 |
IsSame<int, int>::value // true IsSame<int, double>::value // false |
- 实现
|
1 2 3 4 5 6 7 8 9 10 11 |
#include <type_traits> template<class T1, class T2> struct IsSame : std::false_type { }; template<class T> struct IsSame<T, T> : std::true_type { }; |
- 一般情况
- 默认认为两个类型不同
|
1 2 |
template<class T1, class T2> struct IsSame : std::false_type {}; |
- 偏特化
- 只有两个参数是完全相同的类型时才匹配
|
1 2 |
template<class T> struct IsSame<T, T> : std::true_type {}; |
- 使用
|
1 2 3 |
static_assert(IsSame<int, int>::value); static_assert(!IsSame<int, const int>::value); static_assert(!IsSame<int, int&>::value); |
|
1 2 3 4 5 6 7 |
// 还可以添加 C++17 风格变量模板 template<class T1, class T2> inline constexpr bool IsSameV = IsSame<T1, T2>::value; static_assert(IsSameV<int, int>); |
为什么要继承 true_type 和 false_type
- 也可以写成
|
1 2 3 4 5 |
template<class T1, class T2> struct IsSame { static constexpr bool value = false; }; |
- 但继承
std::true_type/std::false_type可以自动获得
|
1 2 3 4 |
IsSame<T1, T2>::value IsSame<T1, T2>::value_type IsSame<T1, T2>::type IsSame<T1, T2>{}() // 得到 bool |
- 而且
trait本身可以作为tag使用
|
1 2 3 4 5 6 7 8 |
void implementation(std::true_type); void implementation(std::false_type); template<class T> void function(T value) { implementation(IsSame<T, int>{}); } |
- 所以预测型
trait通常写成
|
1 2 3 4 5 |
template<class T> struct SomeTrait : std::false_type {}; template<class T> struct SomeTrait<某种模式<T>> : std::true_type {}; |
实现 remove_reference
- 目标
|
1 2 3 4 |
RemoveReferenceT<int>::type // int RemoveReferenceT<int&>::type // int RemoveReferenceT<int&&>::type // int RemoveReferenceT<const int&>::type // const int |
- 实现
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
template<class T> struct RemoveReferenceT { using type = T; }; template<class T> struct RemoveReferenceT<T&> { using type = T; }; template<class T> struct RemoveReferenceT<T&&> { using type = T; }; |
|
1 2 3 4 5 |
// 别名模板 template<class T> using RemoveReference = typename RemoveReferenceT<T>::type; |
- 使用
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <type_traits> static_assert( std::is_same_v<RemoveReference<int>, int> ); static_assert( std::is_same_v<RemoveReference<int&>, int> ); static_assert( std::is_same_v<RemoveReference<int&&>, int> ); static_assert( std::is_same_v< RemoveReference<const int&>, const int > ); |
- 注意:
remove_reference只删除引用,不删除const
为什么需要三个版本
- 主模板:
- 处理普通类型
|
1 2 3 4 5 |
template<class T> struct RemoveReferenceT { using type = T; }; |
- 偏特化
- 专门匹配左值引用
|
1 2 3 4 5 |
template<class T> struct RemoveReferenceT<T&> { using type = T; }; |
|
1 2 3 4 5 6 7 |
RemoveReferenceT<const int&> // 匹配 T& 偏特化时 T& = const int& T = const int 最终 type = const int |
- 另一个偏特化
- 专门处理右值引用
|
1 2 3 4 5 |
template<class T> struct RemoveReferenceT<T&&> { using type = T; }; |
remove_cv、remove_cvref 与 decay
- 容易混淆的四个
trait
输入 T |
remove_reference_t<T> |
remove_cvref_t<T> |
decay_t<T> |
const int& |
const int |
int |
int |
int&& |
int |
int |
int |
const int[3]& |
const int[3] |
int[3] |
const int* |
| 函数引用 | 函数类型 | 函数类型 | 函数指针 |
C++20的
|
1 2 3 4 5 6 7 |
std::remove_cvref_t<T> // 近似等价 std::remove_cv_t< std::remove_reference_t<T> > |
- 下面这样通常没有效果
- 因为引用类型自身没有顶层
const;const修饰的是它引用的int
- 因为引用类型自身没有顶层
|
1 |
std::remove_cv_t<const int&> |
- 所以
- 只想获得对象基础类型:常用
remove_cvref_t - 想模拟按值传参:使用
decay_t
- 只想获得对象基础类型:常用
实现 conditional
std::conditional是编译期的类型选择
|
1 |
std::conditional_t<Condition, ThenType, ElseType> |
- 简化实现
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
template<bool Condition, class Then, class Else> struct Conditional { using type = Then; }; template<class Then, class Else> struct Conditional<false, Then, Else> { using type = Else; }; template<bool Condition, class Then, class Else> using ConditionalT = typename Conditional<Condition, Then, Else>::type; |
- 使用
- 主模板处理
true,false偏特化覆盖另一种情况
- 主模板处理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static_assert( std::is_same_v< ConditionalT<true, int, double>, int > ); static_assert( std::is_same_v< ConditionalT<false, int, double>, double > ); |
conditional 不是普通运行期 if
|
1 2 3 4 5 6 |
using Type = std::conditional_t< sizeof(void*) == 8, std::uint64_t, std::uint32_t >; |
- 在
64位平台上- 最终程序中没有运行期分支
|
1 |
Type == std::uint64_t |
- 要注意:传给
conditional的三个模板参数必须首先能够被写出来
|
1 2 3 4 5 6 7 8 |
// 下面可能仍然失败 template<class T> using Result = std::conditional_t< condition<T>, typename T::value_type, int >; |
|
1 2 3 4 |
// 即使条件是 false,编译器在构造模板实参时仍可能需要处理 typename T::value_type // 如果 T 没有该类型,就会失败 |
- 要延迟求值,需要传递“trait 包装器”,最后才访问
::type
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<class T> struct ValueTypeOf { using type = typename T::value_type; }; template<class T> struct Identity { using type = T; }; // 先选择 ValueTypeOf<T> 或 Identity<T> // 再访问被选中类型的 ::type |
C++20标准库提供了对应的
|
1 2 |
std::type_identity<T> std::type_identity_t<T> |
trait可以描述外部类型
trait的重要价值是:- 不需要修改目标类型,也能给它附加编译期信息
- 例如协议消息
|
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 |
#include <cstddef> #include <cstdint> struct Login {}; struct Logout {}; enum class Command : std::uint16_t { login = 1, logout = 2 }; template<class Message> struct MessageTraits; // 没有默认实现 template<> struct MessageTraits<Login> { static constexpr Command command = Command::login; static constexpr std::size_t max_size = 256; }; template<> struct MessageTraits<Logout> { static constexpr Command command = Command::logout; static constexpr std::size_t max_size = 64; }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 泛型代码 template<class Message> void send_message(const Message& message) { constexpr auto command = MessageTraits<Message>::command; constexpr auto max_size = MessageTraits<Message>::max_size; // 编码并发送 } // 比在泛型函数里写下面的实现更容易扩展,也把“类型与协议元数据的关系”集中在一个位置 if constexpr (std::is_same_v<Message, Login>) { // ... } else if constexpr (...) { // ... } |
- 但
trait不能保证- 消息一定正确序列化
max_size与真实编码结果一致- 网络字节序已经转换
- 异步发送期间对象仍然存活
累加 trait
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
template<class T> struct AccumulationTraits { using accumulator_type = T; static constexpr accumulator_type zero() { return {}; } }; template<> struct AccumulationTraits<char> { using accumulator_type = int; static constexpr accumulator_type zero() { return 0; } }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
template<class Iterator> auto sum_values(Iterator first, Iterator last) { using value_type = typename std::iterator_traits<Iterator>::value_type; using traits = AccumulationTraits<value_type>; using accumulator_type = typename traits::accumulator_type; accumulator_type result = traits::zero(); while (first != last) { result += *first; ++first; } return result; } |
trait 与 tag 的关系
- 常见流程是
|
1 2 3 4 5 6 7 8 9 |
输入类型 ↓ trait 提取属性 ↓ 得到一个 tag 类型 ↓ 把 tag 对象传给重载函数 ↓ 重载解析选择实现 |
- 例如
iterator_traits是traititerator_category是提取出的tag类型category{}是tag对象advance_impl()通过重载选择不同算法
|
1 2 3 4 |
using category = typename std::iterator_traits<Iterator>::iterator_category; advance_impl(iterator, n, category{}); |
Tag Dispatch
概述
- 核心只有一句话
- 把编译期信息包装成不同的类型,然后利用函数重载选择不同实现
- 它通常由三部分组成
- 它不是运行期分支,通常不会生成类似
if的运行期判断
- 它不是运行期分支,通常不会生成类似
|
1 2 3 4 |
输入类型 → trait 提取分类 → 构造 tag 对象 → 重载解析选择实现 |
最小完整示例:布尔 Tag
|
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 |
#include <iostream> #include <type_traits> template<class T> void describe_impl(const T&, std::true_type) { std::cout << "pointer type\n"; } template<class T> void describe_impl(const T&, std::false_type) { std::cout << "non-pointer type\n"; } template<class T> void describe(const T& value) { using IsPointer = std::bool_constant<std::is_pointer_v<T>>; describe_impl(value, IsPointer{}); } int main() { int value = 42; int* pointer = &value; describe(value); describe(pointer); } |
- 调用
|
1 2 3 4 5 6 7 |
describe(pointer); // 编译过程是: T = int* std::is_pointer_v<T> = true IsPointer = std::bool_constant<true> IsPointer = std::true_type |
Tag 类型和 Tag 对象
- 定义
- 这是一个
tag类型
- 这是一个
|
1 |
struct binary_mode_tag {}; |
- 构造
- 这是一个临时
tag对象
- 这是一个临时
|
1 |
binary_mode_tag{} |
- 也可以提供一个命名对象
|
1 2 3 |
struct binary_mode_t {}; inline constexpr binary_mode_t binary_mode{}; |
- 于是调用者可以写
binary_mode_t是类型binary_mode是对象- 函数参数通过
binary_mode_t区分重载
|
1 |
open_file("data.bin", binary_mode); |
- 标准库中有很多类似设计
|
1 2 3 4 5 |
std::nothrow std::adopt_lock std::defer_lock std::try_to_lock std::in_place |
Tag 为什么通常是空类
Tag为什么通常是空类- 分类
- 能力
- 模式
- 编译期选择
|
1 2 |
struct input_iterator_tag {}; struct random_access_iterator_tag {}; |
- 虽然空类对象通常仍满足
- 但临时
tag对象通常可以被编译器优化掉 Tag dispatch的关键优势来自编译期重载选择,并不是“空类的大小一定为零”
- 但临时
|
1 |
sizeof(input_iterator_tag) >= 1 |
- 如果参数需要携带运行期数据,例如
- 它更像配置或策略对象,而不是纯
tag
- 它更像配置或策略对象,而不是纯
|
1 2 3 4 |
struct RetryConfig { int max_retries; int timeout_ms; }; |
为什么不直接传 bool
- 下面不是
tag dispatch
|
1 2 3 4 5 6 7 |
void process(bool fast_mode); // 因为 bool fast_mode = get_runtime_config(); process(fast_mode); // 只能在运行期判断 |
- 真正的
tag dispatch使用不同类型
|
1 2 3 4 5 6 7 8 |
struct fast_tag {}; struct safe_tag {}; void process(fast_tag); void process(safe_tag); // 选择发生在重载解析阶段 process(fast_tag{}); |
- 如果条件本身是编译期常量,可以转换为布尔
tag
|
1 2 |
using Tag = std::bool_constant<Condition>; process(Tag{}); |
最经典的使用:迭代器 Tag
- 不同迭代器具有不同能力
Tag |
主要能力 |
input_iterator_tag |
向前读取一次 |
forward_iterator_tag |
可重复向前遍历 |
bidirectional_iterator_tag |
可以 ++ 和 -- |
random_access_iterator_tag |
支持 +=、下标、距离 |
contiguous_iterator_tag |
C++20,元素还保证连续存储 |
- 它们具有继承关系
output_iterator_tag是另一条独立分类,不属于这条继承链
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct input_iterator_tag {}; struct forward_iterator_tag : input_iterator_tag {}; struct bidirectional_iterator_tag : forward_iterator_tag {}; struct random_access_iterator_tag : bidirectional_iterator_tag {}; struct contiguous_iterator_tag : random_access_iterator_tag {}; |
- 这种继承表达的是
- 随机访问迭代器也具备双向、前向和输入迭代能力
实现 advance 的 Tag Dispatch
|
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#include <iostream> #include <iterator> #include <list> #include <vector> template<class Iterator, class Distance> void advance_impl( Iterator& iterator, Distance distance, std::input_iterator_tag) { std::cout << "linear implementation\n"; // 为了简化,本示例只处理非负距离 while (distance > 0) { ++iterator; --distance; } } template<class Iterator, class Distance> void advance_impl( Iterator& iterator, Distance distance, std::random_access_iterator_tag) { std::cout << "constant-time implementation\n"; iterator += distance; } template<class Iterator, class Distance> void my_advance( Iterator& iterator, Distance distance) { using Category = typename std::iterator_traits< Iterator >::iterator_category; advance_impl( iterator, distance, Category{} ); } int main() { std::list<int> list{10, 20, 30, 40}; auto list_iterator = list.begin(); my_advance(list_iterator, 2); std::cout << *list_iterator << '\n'; std::vector<int> vector{10, 20, 30, 40}; auto vector_iterator = vector.begin(); my_advance(vector_iterator, 2); std::cout << *vector_iterator << '\n'; } |
|
1 2 3 4 |
linear implementation 30 constant-time implementation 30 |
std::list 如何完成派发
- 对于
std::list<int>::iterator
|
1 2 3 4 5 6 7 8 9 |
using Category = std::bidirectional_iterator_tag; // 调用变成 advance_impl( iterator, 2, std::bidirectional_iterator_tag{} ); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 候选 上面只实现了这两个 advance_impl(..., std::input_iterator_tag); advance_impl(..., std::random_access_iterator_tag); // 因为 bidirectional_iterator_tag -> forward_iterator_tag -> input_iterator_tag // 所以它可以向上转换成 input_iterator_tag // 但不能向下转换成 random_access_iterator_tag // 最终选择线性版本 |
std::vector 为什么选择随机访问版本
- 对于
std::vector<int>::iterator
|
1 2 |
using Category = std::random_access_iterator_tag; |
|
1 2 3 4 5 6 7 8 9 10 11 |
// 两个重载理论上都可行 advance_impl(..., input_iterator_tag); advance_impl(..., random_access_iterator_tag); // 因为随机访问 tag 继承自输入 tag // 但第二个版本是精确匹配 random_access_iterator_tag -> random_access_iterator_tag // 精确匹配更好,所以选择 iterator += distance; |
为什么需要一个公共包装函数
- 可以让用户直接调用
- 但这会暴露实现细节,而且用户可能传错
tag
- 但这会暴露实现细节,而且用户可能传错
|
1 2 3 4 5 |
advance_impl( iterator, distance, some_tag{} ); |
|
1 2 3 4 5 6 7 8 9 |
advance_impl( list_iterator, 10, std::random_access_iterator_tag{} // 错误分类 ); // 编译器会尝试实例化 list_iterator += 10; // 然后报错 |
- 因此标准结构是
|
1 2 3 4 5 6 7 8 |
template<class Iterator> void public_function(Iterator iterator) { using Tag = typename SomeTraits<Iterator>::category; implementation(iterator, Tag{}); } |
- 公共包装函数负责
- 推导模板参数
- 通过
trait提取正确tag - 调用内部实现
- 防止调用者手动传错
tag
Trait 不会自动验证 Tag 是否真实
- 例如
|
1 2 3 4 5 6 |
struct FakeIterator { using iterator_category = std::random_access_iterator_tag; FakeIterator& operator++(); }; |
|
1 2 3 4 5 |
// 如果它没有实现 operator+= // 但仍然声称自己是随机访问迭代器,那么 tag dispatch 会选择 iterator += distance; // 最终产生编译错误 |
- 因此
tag是一种类型级承诺- 我声明自己属于这个类别,并满足这个类别对应的接口和语义
C++20 Concepts能更直接地检查部分操作是否存在- 但一些语义要求,例如“多次遍历得到相同结果”,仍然无法完全由编译器证明
布尔Tag与分类Tag
- 布尔
Tag只有两个分支- 适合“是或否”的能力
|
1 2 |
std::true_type std::false_type |
|
1 2 3 |
std::is_trivially_copyable<T> std::is_pointer<T> std::is_integral<T> |
- 分类
Tag存在多个类别- 适合描述分层能力
|
1 2 3 4 |
input_iterator_tag forward_iterator_tag bidirectional_iterator_tag random_access_iterator_tag |
- 模式
Tag- 由调用者主动选择
- 适合区分构造方式或
API语义
|
1 2 3 |
std::adopt_lock std::defer_lock std::in_place |
网络缓冲区中的 Tag Dispatch
- 假设网络发送支持两类缓冲区
- 连续缓冲区:一次普通发送
- 分段缓冲区:使用
writev或WSASend的scatter/gather
- 定义
tag
|
1 2 |
struct contiguous_buffer_tag {}; struct segmented_buffer_tag {}; |
- 定义
trait
|
1 2 3 4 5 6 7 8 9 10 11 |
#include <cstddef> #include <vector> template<class Buffer> struct BufferTraits; template<> struct BufferTraits<std::vector<std::byte>> { using category = contiguous_buffer_tag; }; |
- 假设有分段缓冲类型
|
1 2 3 4 5 6 7 8 9 10 |
struct BufferSequence { // 多个缓冲区片段 }; template<> struct BufferTraits<BufferSequence> { using category = segmented_buffer_tag; }; |
- 内部实现
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Socket; template<class Buffer> void send_impl( Socket& socket, const Buffer& buffer, contiguous_buffer_tag) { // 循环 send,处理部分写入 } template<class Buffer> void send_impl( Socket& socket, const Buffer& buffer, segmented_buffer_tag) { // Linux: writev // Windows: WSASend } |
- 公共接口
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<class Buffer> void send_buffer( Socket& socket, const Buffer& buffer) { using Category = typename BufferTraits<Buffer>::category; send_impl( socket, buffer, Category{} ); } |
|
1 2 |
// 调用者只写 send_buffer(socket, buffer); |
- 不需要知道最终是普通
send()、writev()还是WSASend() - 但
tag dispatch不会处理(它只负责选择实现)- 部分发送
- 断线
- 缓冲区生命周期
- 并发关闭
socket - 异步操作取消
继承型Tag的隐藏优势:自动回退
- 假设有
|
1 2 3 |
struct basic_tag {}; struct optimized_tag : basic_tag {}; |
- 只实现通用版本
|
1 2 |
template<class T> void process_impl(T value, basic_tag); |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// 即使 trait 返回 optimized_tag // 仍然可以退回通用实现,因为 optimized_tag -> basic_tag // 以后再添加 template<class T> void process_impl(T value, optimized_tag); // 优化版本就会因为精确匹配而自动胜出 // 这使库可以按能力逐渐增加优化实现 |
进阶:priority_tag
- 大型模板项目中还会看到
|
1 2 3 4 5 6 7 8 9 |
template<int N> struct priority_tag : priority_tag<N - 1> { }; template<> struct priority_tag<0> { }; |
|
1 2 3 4 5 6 |
// 继承关系 priority_tag<3> → priority_tag<2> → priority_tag<1> → priority_tag<0> |
- 用来表达多级回退
|
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 |
#include <iostream> template<class T> auto dump_impl( const T& value, priority_tag<2>) -> decltype(value.dump(), void()) { value.dump(); } template<class T> auto dump_impl( const T& value, priority_tag<1>) -> decltype(std::cout << value, void()) { std::cout << value; } template<class T> void dump_impl( const T&, priority_tag<0>) { std::cout << "unsupported"; } template<class T> void dump(const T& value) { dump_impl(value, priority_tag<2>{}); } |
- 选择顺序
- 如果有
value.dump(),选择优先级2 - 否则,如果支持
operator<<,选择优先级1 - 否则,退回优先级
0
- 如果有
- 这里结合了:
Tag继承- 重载解析
SFINAE- 表达式检测
Tag Dispatch、if constexpr 和 Concepts怎么选
| 技术 | 主要目的 | 适用场景 |
Tag dispatch |
根据类型分类选择实现 | 有稳定类别或继承层次 |
if constexpr |
在一个模板内部选择分支 | 分支较少、逻辑紧密 |
SFINAE |
排除无效候选 | C++11/14/17 接口约束 |
Concepts |
声明接口要求并参与重载排序 | C++20 泛型接口 |
普通 if |
根据运行期值选择 | 条件运行时才知道 |
C++17可以把简单的布尔tag改写成- 这种写法更短
|
1 2 3 4 5 6 7 8 9 |
template<class T> void describe(const T& value) { if constexpr (std::is_pointer_v<T>) { std::cout << "pointer\n"; } else { std::cout << "non-pointer\n"; } } |
- 但
tag dispatch仍适合- 不同实现很长,需要分开
- 多个算法共享同一分类
- 分类具有继承层次
- 需要多级自动回退
- 维护
C++11/14项目
C++20 Concepts更适合控制“这个接口能不能调用”tag dispatch更偏向:类型已经合法,现在选择哪种实现
|
1 2 |
template<std::random_access_iterator Iterator> void advance_fast(Iterator&, std::ptrdiff_t); |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Soui一03/17
- ♥ Spdlog记述:三07/23
- ♥ 编译器扩展语法:一07/06
- ♥ STL_list05/04
- ♥ C++标准库 _string04/16
- ♥ C++并发编程 _ 基于锁的数据结构08/19