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的成员
折叠代码
#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() \
{ \
return 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() \
{ \
return 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() \
{ \
return 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 singed_check : arithmetic_check<T>
{
static_assert(std::is_signed<T>::value, "instanciation requires signed arithmetic types");
};
template<typename T>
class real_point : singed_check {...};