启用 if

std::enable_if 是一个使用布尔条件触发 SFINAE 的便捷工具。它被定义为:

template <bool Cond, typename Result=void>
struct enable_if { };

template <typename Result>
struct enable_if<true, Result> {
    using type = Result;
};

也就是说,enable_if<true, R>::typeR 的别名,而 enable_if<false, T>::type 是不正确的,因为 enable_if 的特化没有 type 成员类型。

std::enable_if 可用于约束模板:

int negate(int i) { return -i; }

template <class F>
auto negate(F f) { return -f(); }

在这里,对 negate(1) 的调用会由于含糊不清而失败。但是第二个重载并不打算用于整数类型,所以我们可以添加:

int negate(int i) { return -i; }

template <class F, class = typename std::enable_if<!std::is_arithmetic<F>::value>::type>
auto negate(F f) { return -f(); }

现在,实例化 negate<int> 会导致替换失败,因为 !std::is_arithmetic<int>::valuefalse。由于 SFINAE,这不是一个硬错误,这个候选者只是从过载集中删除。因此,negate(1) 只有一个可行的候选人 - 然后被称为。

何时使用它

值得记住的是,std::enable_if 是 SFINAE 之上的帮手,但这并不是 SFINAE 首先发挥作用的原因。让我们考虑这两个替代方案来实现类似于 std::size 的功能,即产生容器或数组大小的重载集 size(arg)

// for containers
template<typename Cont>
auto size1(Cont const& cont) -> decltype( cont.size() );

// for arrays
template<typename Elt, std::size_t Size>
std::size_t size1(Elt const(&arr)[Size]);

// implementation omitted
template<typename Cont>
struct is_sizeable;

// for containers
template<typename Cont, std::enable_if_t<std::is_sizeable<Cont>::value, int> = 0>
auto size2(Cont const& cont);

// for arrays
template<typename Elt, std::size_t Size>
std::size_t size2(Elt const(&arr)[Size]);

假设 is_sizeable 被恰当地写入,这两个声明应该与 SFINAE 完全相同。哪个是最容易编写的,哪个最易于查看和理解?

现在让我们考虑一下如何实现算术助手,避免有符号整数溢出,有利于环绕或模块化行为。也就是说,例如 incr(i, 3) 将与 i += 3 相同,除非结果总是被定义,即使 i 是具有值 INT_MAXint。这是两种可能的选择:

// handle signed types
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(-1) < static_cast<Int>(0)]>;

// handle unsigned types by just doing target += amount
// since unsigned arithmetic already behaves as intended
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(0) < static_cast<Int>(-1)]>;
 
template<typename Int, std::enable_if_t<std::is_signed<Int>::value, int> = 0>
void incr2(Int& target, Int amount);
 
template<typename Int, std::enable_if_t<std::is_unsigned<Int>::value, int> = 0>
void incr2(Int& target, Int amount);

再次哪个是最容易编写的,哪个是最容易查看和理解的?

std::enable_if 的优势在于它如何与重构和 API 设计相结合。如果 is_sizeable<Cont>::value 是为了反映 cont.size() 是否有效,那么只使用 size1 所显示的表达式可以更简洁,尽管这可能取决于 is_sizeable 是否会在几个地方使用。与 std::is_signed 形成鲜明对比,与其实施内容相比,std::is_signed 更明确地反映了其意图 28。