C++ 模板实参类型限制

打印 上一主题 下一主题

主题 1947|帖子 1947|积分 5841

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
有时间我们编写一个模板,希望用户使用我们盼望的类型来实例化它,就须要对实参进行检查,限制不满足条件的实例化版本,同时给出便于理解的编译时信息
对于 C++20 后的版本,可以将条件包装为concept:
折叠代码
  1. template<typename T>
  2. concept check = requires(T t)
  3. {
  4.   T{};//可以默认构造
  5.   typename T::value_type;//定义了value_type类型名
  6.   t.x;//具有名为x的成员变量
  7.   t.set(1);//具有名为set的成员函数,并且可以使用(int)1调用
  8. };
  9. struct A//完全满足所有要求
  10. {
  11.   typedef float value_type;
  12.   value_type x;
  13.   void set(value_type _x){}//concept检查接口调用时接受int到float的隐式转换
  14. };
  15. struct B//无默认构造函数
  16. {
  17.   typedef int value_type;
  18.   value_type x;
  19.   B(int _x):x(_x){}
  20.   void set(value_type _x){}
  21. };
  22. struct C//没有定义value_type类型名
  23. {
  24.   int x;
  25.   void set(int _x){}
  26. };
  27. struct D//没有名为x的数据成员
  28. {
  29.   typedef int value_type;
  30.   void set(value_type _x){}
  31. };
  32. struct E//没有名为set的成员函数
  33. {
  34.   typedef int value_type;
  35.   value_type x;
  36. };
  37. template<check T>
  38. struct tp1{};
  39. tp1<A> a;//OK
  40. tp1<B> b;//错误,无默认构造函数可用
  41. tp1<C> c;//错误,value_type未定义
  42. tp1<D> d;//错误,x不是D的成员
  43. tp1<E> e;//错误,set不是E的成员
复制代码
通过concept可以方便的包装条件,并且在编译时给出相对易于理解的错误信息,但是假如我们的编译环境不支持 C++20,这些检查的实现就会颇为复杂:折叠代码
  1. #define DETECT_TYPE_DEFINITION(name)                                                                                                                                \
  2. template<typename T, typename = void>                                                                                                                                \
  3. struct detect_type_definition_##name##_impl:std::false_type {};                                                                                \
  4. template<typename T>                                                                                                                                                                \
  5. struct detect_type_definition_##name##_impl<T, std::void_t<typename T::name>>:std::true_type {};        \
  6. template<typename T>                                                                                                                                                                \
  7. constexpr bool has_type_definition_##name()                                                                                                                        \
  8. {                                                                                                                                                                                                        \
  9.   return detect_type_definition_##name##_impl<T>::value;                                                                                        \
  10. }
  11. //《C++ Templates》中讲到的方法,impl函数利用 SFINAE 特性,只用作返回值类型推导,无需函数体
  12. #define DETECT_MEMBER(name)                                                                                                                                                        \
  13.   template <typename T>                                                                                                                                                            \
  14.   auto detect_member_##name##_impl(int) -> decltype(std::declval<T>().name, std::true_type{});            \
  15.   template <typename>                                                                                                                                                                \
  16.   auto detect_member_##name##_impl(...) -> std::false_type;                                                                                    \
  17.   template <typename T>                                                                                                                                                            \
  18.   constexpr bool has_member_##name()                                                                                                                                \
  19.   {                                                                                                                                                                                                    \
  20.     return decltype(detect_member_##name##_impl<T>(0))::value;                                                                            \
  21.   }
  22. #define DETECT_MEMBER_FUNC(name)                                                                                                                                        \
  23.   template<typename T, typename... Args>                                                                                                                        \
  24.   auto detect_member_func_##name##_impl(int) ->                                                                                                            \
  25.     decltype(std::declval<T>().name(std::declval<Args>()...), std::true_type{});                                    \
  26.   template<typename, typename...>                                                                                                                                        \
  27.   auto detect_member_func_##name##_impl(...) -> std::false_type;                                                                        \
  28.   template<typename T, typename... Args>                                                                                                                        \
  29.   constexpr bool has_member_func_##name()                                                                                                                        \
  30.   {                                                                                                                                                                                                    \
  31.     return decltype(detect_member_func_##name##_impl<T, Args...>(0))::value;                                            \
  32.   }
  33. //使用宏可以方便地扩展到不同名称的成员检测上,便于复用
  34. DETECT_TYPE_DEFINITION(value_type);//生成对名为value_type的类型定义的检测模板
  35. DETECT_MEMBER(x);//生成对名为x的成员变量的检测模板
  36. DETECT_MEMBER_FUNC(set);//生成对名为成员函数set的检测模板
  37. //辅助检测基类
  38. template<typename T>
  39. struct check
  40. {
  41.   static_assert(std::is_default_constructible<T>::value, "no default constructor");//是否可以默认构造
  42.   static_assert(has_type_definition_value_type<T>(), "no definition of 'value_type'");//是否定义了value_type类型名
  43.   static_assert(has_member_x<T>(), "no member named 'x'");//是否有名为x的成员
  44.   static_assert(has_member_func_set<T, int>(), "no member function named 'set' or "
  45.     "the member function 'set' can not be called with an integer");//是否有名为set,并且可用int调用的成员函数
  46. };
  47. template<typename T /*, typename trigger_check = check<T>若tp2内未使用check<T>,check<T>的实例化将会被跳过*/>
  48. struct tp2: check<T>//继承自check以保证check被实例化
  49. {
  50.   //using trigger_check = check<T>;//同默认模板实参一样,可能由于惰性实例化而跳过
  51. };
  52. //类A、B、C、D、E为先前的定义
  53. tp2<A> a;//OK
  54. tp2<B> b;//错误,no default constructor
  55. tp2<C> c;//错误,no definition of 'value_type'
  56. tp2<D> d;//错误,no member named 'x'
  57. tp2<E> e;//错误,no member function named 'set' or the member function 'set' can not be called with an integer
复制代码
通过继续(代码注释中已解释为什么不用默认模板实参和类型别名)可以将检查条件封装于基类中,使用静态断言,可在发生编译错误时提供可读性更高的错误提示。在多个模板类都须要相同的实参约束条件时,将约束条件收集到基类中可以增加代码复用性,减少搬砖性子的劳动。例如在编写几何类库时,很多类都要求使用算术类型来实例化:折叠代码
  1. template<typename T>
  2. struct arithmetic_check
  3. {
  4.   static_assert(std::is_arithmetic<T>::value, "instanciation requires arithmetic types");
  5. };
  6. template<typename T>
  7. class point : arithmetic_check<T> {...};
  8. template<typename T>
  9. class rect : arithmetic_check<T> {...};
  10. template<typename T>
  11. class line_segment : arithmetic_check<T> {...};
  12. ...
复制代码
若通用条件无法满足需求,可以通过继续扩充条件约束:折叠代码
  1. //不仅需要算术类型,还要求是有符号
  2. template<typename T>
  3. struct singed_check : arithmetic_check<T>
  4. {
  5.   static_assert(std::is_signed<T>::value, "instanciation requires signed arithmetic types");
  6. };
  7. template<typename T>
  8. class real_point : singed_check {...};
复制代码
上述简单例子有些故意为之,但是足以展示编码思路。由于所有的条件约束类都不存在非静态数据成员,编译器可以针对它们启用空基类优化策略(EBCO,Empty Base Class Optimization),不会增加内存占用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表