C++ 模板实参类型限制
有时候我们编写一个模板,希望用户使用我们期望的类型来实例化它,就需要对实参进行检查,限制不满足条件的实例化版本,同时给出便于理解的编译时信息。
对于 C++20 后的版本,可以将条件包装为concept:
代码
template<typename T>
concept check = requires(T t)
{
T{};//可以默认构造
typename T::value_type;//定义了value_type类型名
t.x;//具有名为x的成员变量
t.set(1);//具有名为set的成员函数,并且可以使用(int)1调用
};
struct A//完全满足所有要求
{
typedef float value_type;
value_type x;
void set(value_type _x){}//concept检查接口调用时接受int到float的隐式转换
};
struct B//无默认构造函数
{
typedef int value_type;
value_type x;
B(int _x):x(_x){}
void set(value_type _x){}
};
struct C//没有定义value_type类型名
{
int x;
void set(int _x){}
};
struct D//没有名为x的数据成员
{
typedef int value_type;
void set(value_type _x){}
};
struct E//没有名为set的成员函数
{
typedef int value_type;
value_type x;
};
template<check T>
struct tp1{};
tp1<A> a;//OK
tp1<B> b;//错误,无默认构造函数可用
tp1<C> c;//错误,value_type未定义
tp1<D> d;//错误,x不是D的成员
tp1<E> e;//错误,set不是E的成员
通过concept可以方便的包装条件,并且在编译时给出相对易于理解的错误信息,但是如果我们的编译环境不支持 C++20,这些检查的实现就会颇为复杂:
代码
#define DETECT_TYPE_DEFINITION(name) \
template<typename T, typename = void> \
struct detect_type_definition_##name##_impl : std::false_type {}; \
template<typename T> \
struct detect_type_definition_##name##_impl<T, std::void_t<typename T::name>> : std::true_type {}; \
template<typename T> \
constexpr bool has_type_definition_##name = detect_type_definition_##name##_impl<T>::value;
//《C++ Templates》中讲到的方法,impl函数利用 SFINAE 特性,只用作返回值类型推导,无需函数体
#define DETECT_MEMBER(name) \
template<typename T> \
auto detect_member_##name##_impl(int) -> decltype(std::declval<T>().name, std::true_type{}); \
template<typename> \
auto detect_member_##name##_impl(...) -> std::false_type; \
template<typename T> \
constexpr bool has_member_##name = decltype(detect_member_##name##_impl<T>(0))::value;
#define DETECT_MEMBER_FUNC(name) \
template<typename T, typename... Args> \
auto detect_member_func_##name##_impl(int) -> \
decltype(std::declval<T>().name(std::declval<Args>()...), std::true_type{}); \
template<typename, typename...> \
auto detect_member_func_##name##_impl(...) -> std::false_type; \
template<typename T, typename... Args> \
constexpr bool has_member_func_##name = \
decltype(detect_member_func_##name##_impl<T, Args...>(0))::value;
//使用宏可以方便地扩展到不同名称的成员检测上,便于复用
DETECT_TYPE_DEFINITION(value_type);//生成对名为value_type的类型定义的检测模板
DETECT_MEMBER(x);//生成对名为x的成员变量的检测模板
DETECT_MEMBER_FUNC(set);//生成对名为成员函数set的检测模板
//辅助检测基类
template<typename T>
struct check
{
static_assert(std::is_default_constructible<T>::value, "no default constructor");//是否可以默认构造
static_assert(has_type_definition_value_type<T>, "no definition of 'value_type'");//是否定义了value_type类型名
static_assert(has_member_x<T>, "no member named 'x'");//是否有名为x的成员
static_assert(has_member_func_set<T, int>, "no member function named 'set' or "
"the member function 'set' can not be called with an integer");//是否有名为set,并且可用int调用的成员函数
};
template<typename T /*, typename trigger_check = check<T>若tp2内未使用check<T>,check<T>的实例化将会被跳过*/>
struct tp2: check<T>//继承自check以保证check被实例化
{
//using trigger_check = check<T>;//同默认模板实参一样,可能由于惰性实例化而跳过
};
//类A、B、C、D、E为先前的定义
tp2<A> a;//OK
tp2<B> b;//错误,no default constructor
tp2<C> c;//错误,no definition of 'value_type'
tp2<D> d;//错误,no member named 'x'
tp2<E> e;//错误,no member function named 'set' or the member function 'set' can not be called with an integer
通过继承(代码注释中已解释为什么不用默认模板实参和类型别名)可以将检查条件封装于基类中,使用静态断言,可在发生编译错误时提供可读性更高的错误提示。
在多个模板类都需要相同的实参约束条件时,将约束条件收集到基类中可以增加代码复用性,减少搬砖性质的劳动。例如在编写几何类库时,很多类都要求使用算术类型来实例化:
代码
template<typename T>
struct arithmetic_check
{
static_assert(std::is_arithmetic<T>::value, "instanciation requires arithmetic types");
};
template<typename T>
class point : arithmetic_check<T> {...};
template<typename T>
class rect : arithmetic_check<T> {...};
template<typename T>
class line_segment : arithmetic_check<T> {...};
...
若通用条件无法满足需求,可以通过继承扩充条件约束:
代码
//不仅需要算术类型,还要求是有符号
template<typename T>
struct signed_check : arithmetic_check<T>
{
static_assert(std::is_signed<T>::value, "instanciation requires signed arithmetic types");
};
template<typename T>
class real_point : signed_check<T> {...};
上述简单例子有些故意为之,但是足以展示编码思路。
由于所有的条件约束类都不存在非静态数据成员,编译器可以针对它们启用空基类优化策略(EBCO,Empty Base Class Optimization),不会增加内存占用。
C++ 模板实参类型限制的更多相关文章
- C++ Templates (1.4 默认模板实参 Default Template Arguments)
返回完整目录 目录 1.4 默认模板实参 Default Template Arguments 1.4 默认模板实参 Default Template Arguments 可以为模板参数定义默认值,这 ...
- C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则
背景: 最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引发了一系列的“探索”,于是就有了现在这篇博文. 前言: ...
- 【C++ Primer 第16章】2. 模板实参推断
模板实参推断:对于函数模板,编译器利用调用中的函数实参来确定模板参数,从函数实参来确定模板参数的过程被称为模板实参推断. 类型转换与模板类型参数 与往常一样,顶层const无论在形参中还是在是实参中, ...
- C++ template —— 实例化和模板实参演绎(四)
本篇讲解实例化和模板实参演绎-------------------------------------------------------------------------------------- ...
- C++学习笔记(4)----模板实参推断
1. 如图所示代码,模板函数 compare(const T&, const T&) 要求两个参数类型要一样. compare("bye","dad&qu ...
- 模板实参推导 & xx_cast的实现
首先,类模板必须被显式特化.当然了,可以通过一个辅助函数,通过参数类型,返回特化的类模板,来间接处理. 这个技术被广泛应用在ptr_fun, make_pair, mem_fun, back_inse ...
- C++中decltype(*)作为模板实参时的隐藏问题
在函数模板中使用智能指针时,可能会希望根据指针的类型推导出指针引用的对象类型作为模板参数,于是写出以下代码: shared_ptr<decltype(*objPtr)>(objPtr); ...
- C++ Templates (1.2 模板实参推断 Template Argument Deduction)
返回完整目录 目录 1.2 模板实参推断 Template Argument Deduction 1.2 模板实参推断 Template Argument Deduction 当调用函数模板(如max ...
- C++模板参数类型(转载)
实际上有三种类型模板参数:类型模板参数.无类型模板参数和模板模板参数(以模板作为模板的参数). .类型模板参数 类型模板参数是我们使用模板的主要目的.我们可以定义多个类型模板参数: template& ...
- 《深入实践C++模板编程》之三——模板参数类型详解
非类型模板参数 和 模板型模板参数 整数以及枚举类型:指向对象或者函数的指针:对对象或函数的引用:指向对象成员的指针.统称为非类型模板参数. 模板型模板参数,是指模板参数还可以是一个模板. 1.整 ...
随机推荐
- 5. Nginx 负载均衡配置案例(附有详细截图说明++)
5. Nginx 负载均衡配置案例(附有详细截图说明++) @ 目录 5. Nginx 负载均衡配置案例(附有详细截图说明++) 1. Nginx 负载均衡 配置实例 3. 注意事项和避免的坑 4. ...
- 获取另一个frame中的元素
<frameset cols="12,88" frameborder="no" noresize borders="no" frame ...
- pycharm clone GitHub 提示 OpenSSL SSL_read: Connection was reset, errno 10054
配置界面 错误提示 原因分析 clone的时候需要安全认证,当你在配置页面勾选上ssh ,就会报错 解决方案 在cmd里输入命令,然后再clone git config --global http.s ...
- 项目管理知识体系指南(PMBOK 指南)
项目管理知识体系指南(PMBOK 指南) 第6版--笔记项目管理十大知识领域,五大管理过程组,49个过程.如下表格:项目:项目的定义 : (Project Management Institute)项 ...
- 如何让tcxGrid左边显示序号
第一步: 设置cxgrid的属性, OptionsView.Indicator = True 第二步: 写OnCustomDrawIndicatorCell方法 procedure TForm1.cx ...
- [每日算法 - 华为机试] leetcode53 :最大子数组和 「算法中的哲学」
入口 53. 最大子数组和https://leetcode.cn/problems/maximum-subarray/ 题目描述 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数 ...
- 【Python】词频统计
需求:一篇文章,出现了哪些词?哪些词出现得最多? 英文文本词频统计 英文文本:Hamlet 分析词频 统计英文词频分为两步: 文本去噪及归一化 使用字典表达词频 代码: #CalHamletV1.py ...
- springboot+mongodb+jpa使用save()方法保存数据后id为0
最近做mongodb版本升级,springboot版本从1.4.0.RELEASE升级到1.5.10.RELEASE后出现jpa使用save()方法保存数据后id为0的问题. 解决: 以下为原代码,使 ...
- 【电子DIY神器】通吃各种5线步进电机!I2C接口控制28BYJ-48五线四相步进电机
总线单极性步进电机驱动板 摘要 总线单极性步进电机扩展板采用紧凑型设计,兼容XIAO系列主控板直连或独立使用,支持级联16个模块.板载ULN2003达林顿管驱动芯片(单通道500mA/整片2.5A), ...
- 使用Python进行切比雪夫插值
引言 在科学计算中,插值是一个非常重要的概念.简单来说,插值就是通过已知的离散数据点来估算未知点的值.今天,我们将重点介绍切比雪夫插值,它是一种非常有效的插值方法,特别适用于解决插值多项式高次时出现的 ...