启用 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>::type
是 R
的别名,而 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>::value
是 false
。由于 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_MAX
的 int
。这是两种可能的选择:
// 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。