有时候我们编写一个模板,希望用户使用我们期望的类型来实例化它,就需要对实参进行检查,限制不满足条件的实例化版本,同时给出便于理解的编译时信息。

对于 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++ 模板实参类型限制的更多相关文章

  1. C++ Templates (1.4 默认模板实参 Default Template Arguments)

    返回完整目录 目录 1.4 默认模板实参 Default Template Arguments 1.4 默认模板实参 Default Template Arguments 可以为模板参数定义默认值,这 ...

  2. C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则

    背景:    最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引发了一系列的“探索”,于是就有了现在这篇博文. 前言:     ...

  3. 【C++ Primer 第16章】2. 模板实参推断

    模板实参推断:对于函数模板,编译器利用调用中的函数实参来确定模板参数,从函数实参来确定模板参数的过程被称为模板实参推断. 类型转换与模板类型参数 与往常一样,顶层const无论在形参中还是在是实参中, ...

  4. C++ template —— 实例化和模板实参演绎(四)

    本篇讲解实例化和模板实参演绎-------------------------------------------------------------------------------------- ...

  5. C++学习笔记(4)----模板实参推断

    1. 如图所示代码,模板函数 compare(const T&, const T&) 要求两个参数类型要一样. compare("bye","dad&qu ...

  6. 模板实参推导 & xx_cast的实现

    首先,类模板必须被显式特化.当然了,可以通过一个辅助函数,通过参数类型,返回特化的类模板,来间接处理. 这个技术被广泛应用在ptr_fun, make_pair, mem_fun, back_inse ...

  7. C++中decltype(*)作为模板实参时的隐藏问题

    在函数模板中使用智能指针时,可能会希望根据指针的类型推导出指针引用的对象类型作为模板参数,于是写出以下代码: shared_ptr<decltype(*objPtr)>(objPtr); ...

  8. C++ Templates (1.2 模板实参推断 Template Argument Deduction)

    返回完整目录 目录 1.2 模板实参推断 Template Argument Deduction 1.2 模板实参推断 Template Argument Deduction 当调用函数模板(如max ...

  9. C++模板参数类型(转载)

    实际上有三种类型模板参数:类型模板参数.无类型模板参数和模板模板参数(以模板作为模板的参数). .类型模板参数 类型模板参数是我们使用模板的主要目的.我们可以定义多个类型模板参数: template& ...

  10. 《深入实践C++模板编程》之三——模板参数类型详解

    非类型模板参数 和 模板型模板参数 整数以及枚举类型:指向对象或者函数的指针:对对象或函数的引用:指向对象成员的指针.统称为非类型模板参数. 模板型模板参数,是指模板参数还可以是一个模板.   1.整 ...

随机推荐

  1. NCS开发学习笔记-基础篇-第 1 课 – nRF Connect SDK 简介

    第 1 课 – nRF Connect SDK 简介 目标 了解 nRF Connect SDK 的结构和内容 在内部,nRF Connect SDK 代码分为四个主要存储库: nrf – 应用程序. ...

  2. Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露

    一:背景 1. 讲故事 前面跟大家分享过一篇 C# 调用 C代码引发非托管内存泄露 的文章,这是一个故意引发的正向泄露,这一篇我们从逆向的角度去洞察引发泄露的祸根代码,这东西如果在 windows 上 ...

  3. Selenium IDE工具:火狐浏览器实例讲解IDE命令

    在本文中,通过Firefox浏览器上的示例学习Selenium IDE: 我们将使用的网址是"https://accounts.google.com"作为测试程序,通过本文你会 了 ...

  4. 记录composer 安装 yii2项目

    先带上一个痛苦面具 前段时间换成mac系统,自己以前的yii2项目老是安装不上,因为暂时用不上就没去管,现在想用了,折腾了半天才安装好.下面我记录下坑 国内记得换镜像(我换了系统后,应该是忘记了) c ...

  5. 【Bug记录】Powershell 无法将“vue”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 - PowerShell 执行策略

    Powershell 无法将"vue"项识别为 cmdlet.函数.脚本文件或可运行程序的名称 造成该问题主要是 PowerShell 执行策略,不支持执行全局脚本和程序的运行. ...

  6. zstd压缩算法概述与基本使用

    本文仅关注zstd的使用,并不关心其算法的具体实现 并没有尝试使用zstd的所有功能模式,但是会简单介绍每种模式的应用场景,用到的时候去查api吧 step 0:why zstd? zstd是face ...

  7. go generate

    介绍 go generate 命令是go 1.4版本里面新添加的一个命令,当运行 go generate 时,它将扫描与当前包相关的源代码文件,找出所有包含 //go:generate 的特殊注释,提 ...

  8. jquery的radio的change事件

    一.用的jquery的radio的change事件:当元素的值发生改变时,会发生 change 事件,radio选择不同name值选项的时候恰巧是值发生改变 表单单选框 <input type= ...

  9. go math/rand包详解

    go math/rand package rand import "math/rand" rand包实现了伪随机数生成器. math_rand go官方标准文档 随机数从资源生成. ...

  10. SQL语句(一)—— DDL

    SQL 全称 Structured Query Language,结构化查询语言.操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 . 一.SQL 基础知识 (一)SQL 通用语法 在学 ...