如何优雅地检测类型/表达式有效性?

注1:本文至少需要编译器支持C 11。

注2:本文不考虑使用宏。

一、老办法

在写C 的时候,有时候可能需要检查一个类是否有特定的成员类型,例如:

// 检查 T::type 是否存在,存在则 value 为 true,否则为 falsetemplate <typename T>struct has_type;
struct A {};struct B { using type = int; };static_assert(!has_type::value, "Failed"); // 不存在static_assert(has_type::value, "Failed"); // 存在

或者需要检查一个类是否有特定的成员函数,例如:

// 检查 T.get() 是否存在,存在则 value 为 true,否则为 falsetemplate <typename T>struct has_get;
struct A {};struct B { int get() { return 42; } };static_assert(!has_get::value, "Failed"); // 不存在static_assert(has_get::value, "Failed"); // 存在

如果是在很久以前的 C 98 时代,可能会这样利用 SFINAE 实现:

template struct has_type {private:    typedef char one;    typedef struct { char data[2]; } two;    // 存在的话返回类型为 one    template  static one test(typename U::type*);    // 不存在的话返回类型为 two    template  static two test(...);public:    enum { value = sizeof(test(0)) == sizeof(one) };};

如果 T::type 存在的话就会选择第一个重载,否则就会选择第二个重载,由此判断 T::type 是否存在。但是这样的代码阅读起来可能会挺费劲的……于是,现在有了 void_t!

二、void_t

void_t<...> 其实就是 void,但它可以在 SFINAE 中帮助判断类型是否存在,示例如下:

template <typename T, typename = void>struct has_type : std::false_type {};template struct has_type<T, void_t> : std::true_type {};

(看起来是不是和 enable_if 的某种用法有相似之处?)

虽然 void_t 在 C 17 才成为标准库的一部分,但是我们可以在 C 11 中自己造一个:

template  struct make_void { using type = void; };template  using void_t = typename make_void::type;

需要注意的是上面的定义是为了兼容 C 11 / C 14 而这样写的,因为别名模板中未被使用的模板参数可能会被忽略。但如果是 C 17 的话,编译器就不能忽略别名模板中未被使用的模板参数,就可以直接这样写:

template using void_t = void;

(当然有 C 17 的话就能用标准库的 std::void_t 了……)

同理,我们也可以用同样的方式判断成员函数是否存在:

template <typename T, typename = void>struct has_get : std::false_type {};template struct has_get<T, void_t<decltype(std::declval().get())>> : std::true_type {};

三、Detection Idiom:is_detected

即使我们有了 void_t,但每次需要一个新的判定就得再撸一遍 SFINAE,依然有点不够直观(你说用宏?…… 我什么都没听到)。那么我们为什么不把这种判定也提炼成模板呢?有请 is_detected 出场——

template using has_type_t = typename T::type;template using has_type = is_detected;

看起来使用 is_detected 的方法比之前的 has_type 清爽多了吧,而且非常直观。

虽然 is_detected 还没有进入标准,但我们依然可以在 C 11 中把它造出来:

template <typename, template <typename...> class Op, typename... T>struct is_detected_impl : std::false_type {};template <template <typename...> class Op, typename... T>struct is_detected_impl<void_t<Op>, Op, T...> : std::true_type {};
template <template <typename...> class Op, typename... T>using is_detected = is_detected_impl;

如果仔细看的话,你就能够发现这就是给之前的方法加上了模板模板参数,使得它更容易使用。下面是用 is_detected 判断成员函数是否存在:

template using has_get_t = decltype(std::declval().get());template using has_get = is_detected;

当然,is_detected 还可以做到更多,只要你能够写出 Op 的话就有很多可以做的事情,比如说做各种 concept 的检查。除了 is_detected 之外,Detection Idiom 还有 detected_t 和 detected_or 等工具,可以用于在 trait 中实现默认类型,这里就不再展开介绍,感兴趣的话可以到上面的链接里看一下。

来源:https://zhuanlan.zhihu.com/p/2615546

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值