引入

SFINAE是C++模板编程中必不可少的一环,利用SFINAE我们可以对类型或表达式的可行性就行判断,进而弥补一些缺少泛型的缺点.然而,据说SFINAE其实是Concept的劣化临时解决方案,那么能不能用SFINAE实现一部分Concept呢.

这里用一个问题来贯通整个文章:判断成员函数存在性,并定义一个偷懒宏:

struct Shitizen { };
struct Citizen { void Speak(){} };
#define val(expr) std::declval<expr>()


SFINAE

SFINAE - cppreference.com?

en.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的玩具就完成了.

推荐阅读:

相关文章