把玩C++模板:仿造Concept
引入
SFINAE是C++模板编程中必不可少的一环,利用SFINAE我们可以对类型或表达式的可行性就行判断,进而弥补一些缺少泛型的缺点.然而,据说SFINAE其实是Concept的劣化临时解决方案,那么能不能用SFINAE实现一部分Concept呢.
这里用一个问题来贯通整个文章:判断成员函数存在性,并定义一个偷懒宏:
struct Shitizen { };
struct Citizen { void Speak(){} };
#define val(expr) std::declval<expr>()
SFINAE
SFINAE - cppreference.com
首先来看看SFINAE的解法:
template<class T, class Enabler = void>
struct can_speak : std::true_type {};
template<class T>
struct can_speak<T, decltype(val(T).Speak())> : std::true_type {};
得益于Expression SFINAE,代码还挺简洁:通过一个额外的模板参数Enabler进行偏特化,如果偏特化实例化成功,则代表T拥有Speak函数,反之没有:
bool result = can_speak<Shitizen>::value;
Concept
虽然上面的代码已经很简单了,但是显而易见的还是有很多"样板代码".
理想情况下应该类似于:
Concept<T> can_speak{ void Speak(); };
require T:can_speak
那么要怎么模拟这种效果呢,在当前可以确定的是:
- can_speak是一个模板
- require接受这个模板和目标类型进行检查
有了以上结论,require的实现也呼之欲出了:
- 首先require要接受一个模板: 模板模板参数 : template<template<class...> class Concept>
- 目标类型的数量不固定: 变长模板参数 : template<class... Args>
然后,把SFINAE的样板代码转移到require中,在require中测试有效性,完整的代码如下:
template <template <class...> class Trait, class Enabler, class... Args>
struct require_helper : std::false_type {};
template <template <class...> class Trait, class... Args>
struct require_helper<Trait, std::void_t<Trait<Args...>>, Args...> : std::true_type {};
template <template <class...> class Trait, class... Args>
using require = typename require_helper<Trait, void, Args...>::type;
然后我们通过require来试试实现can_speak
template<class T>
using can_speak = decltype(val(T).Speak());
bool result = require<can_speak, Shitizen>::value;
这里得益于using进一步的简化了代码,对比最初的版本高低立判,进一步的,甚至还可以这么做:
#define Concept(name, expr) using name = decltype(expr)
template<class T>
Concept(can_speak,
val(T).Speak());
结束
至此第一个总代码不超过10的玩具就完成了.
推荐阅读: