Namespaces
Variants

std:: enable_if

From cppreference.net
Metaprogramming library
Type traits
Type categories
(C++11)
(C++11) ( DR* )
Type properties
(C++11)
(C++11)
(C++14)
(C++11) (deprecated in C++26)
(C++11) ( until C++20* )
(C++11) (deprecated in C++20)
(C++11)
Type trait constants
Metafunctions
(C++17)
Supported operations
Relationships and property queries
Type modifications
Type transformations
(C++11) (deprecated in C++23)
(C++11) (deprecated in C++23)
(C++11)
(C++11) ( until C++20* ) (C++17)

enable_if
(C++11)
(C++17)
Compile-time rational arithmetic
Compile-time integer sequences
定义于头文件 <type_traits>
template < bool B, class T = void >
struct enable_if ;
(C++11 起)

B true ,则 std::enable_if 具有公开成员类型定义 type ,等同于 T ;否则不存在该成员类型定义。

该元函数是在C++20的 概念 出现之前利用 SFINAE 的便捷方式,特别是用于根据类型特征有条件地从 候选集 中移除函数,从而基于不同的类型特征实现单独的函数重载或特化。

std::enable_if 可以以多种形式使用,包括:

  • 作为附加函数参数(不适用于大多数运算符重载),
  • 作为返回类型(不适用于构造函数和析构函数),
  • 作为类模板或函数模板参数。

如果程序为 std::enable_if 添加特化,则行为是未定义的。

目录

成员类型

类型 定义
type 根据 B 的值,可能是 T 或不存在该成员

辅助类型

template < bool B, class T = void >
using enable_if_t = typename enable_if < B,T > :: type ;
(自 C++14 起)

可能的实现

template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };

注释

一个常见的错误是声明两个仅在默认模板参数上存在差异的函数模板。这种做法是无效的,因为这类声明会被视为同一函数模板的重复声明(默认模板参数不参与 函数模板等价性 判定)。

/* 错误示例 */
struct T
{
    enum { int_t, float_t } type;
    template<typename Integer,
             typename = std::enable_if_t<std::is_integral<Integer>::value>>
    T(Integer) : type(int_t) {}
    template<typename Floating,
             typename = std::enable_if_t<std::is_floating_point<Floating>::value>>
    T(Floating) : type(float_t) {} // 错误:被视为重复定义
};
/* 正确示例 */
struct T
{
    enum { int_t, float_t } type;
    template<typename Integer,
             std::enable_if_t<std::is_integral<Integer>::value, bool> = true>
    T(Integer) : type(int_t) {}
    template<typename Floating,
             std::enable_if_t<std::is_floating_point<Floating>::value, bool> = true>
    T(Floating) : type(float_t) {} // 正确
};

在命名空间作用域函数模板的常量模板参数类型中使用 enable_if 时需要特别注意。某些ABI规范(如Itanium ABI)不会在名称修饰中包含常量模板参数的实例化依赖部分,这意味着两个不同函数模板的特化可能最终具有相同的修饰名称,从而被错误地链接在一起。例如:

// 第一个翻译单元
struct X
{
    enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value1, int> = 0>
void func() {} // #1
template void func<X>(); // #2
// 第二个翻译单元
struct X
{
    enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value2, int> = 0>
void func() {} // #3
template void func<X>(); // #4

函数模板 #1 与 #3 具有不同的函数签名,属于不同的模板。然而,#2 和 #4 虽然是不同函数模板的实例化,但在 Itanium C++ ABI 中具有相同的修饰名 (_Z4funcI1XLi0EEvv) ,这意味着链接器会错误地将它们视为同一实体。

示例

#include <iostream>
#include <new>
#include <string>
#include <type_traits>
namespace detail
{ 
    void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } 
}
// #1,通过返回类型启用
template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type 
    construct(T*) 
{
    std::cout << "默认构造平凡可默认构造的 T\n";
}
// 同上
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type 
    construct(T* p) 
{
    std::cout << "默认构造非平凡可默认构造的 T\n";
    ::new(detail::voidify(p)) T;
}
// #2
template<class T, class... Args>
std::enable_if_t<std::is_constructible<T, Args&&...>::value> // 使用辅助类型
    construct(T* p, Args&&... args) 
{
    std::cout << "通过操作构造 T\n";
    ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}
// #3,通过参数启用
template<class T>
void destroy(
    T*, 
    typename std::enable_if<
        std::is_trivially_destructible<T>::value
    >::type* = 0)
{
    std::cout << "销毁平凡可析构的 T\n";
}
// #4,通过常量模板参数启用
template<class T,
         typename std::enable_if<
             !std::is_trivially_destructible<T>{} &&
             (std::is_class<T>{} || std::is_union<T>{}),
             bool>::type = true>
void destroy(T* t)
{
    std::cout << "销毁非平凡可析构的 T\n";
    t->~T();
}
// #5,通过类型模板参数启用
template<class T,
	 typename = std::enable_if_t<std::is_array<T>::value>>
void destroy(T* t) // 注意:函数签名未修改
{
    for (std::size_t i = 0; i < std::extent<T>::value; ++i)
        destroy((*t)[i]);
}
/*
template<class T,
	 typename = std::enable_if_t<std::is_void<T>::value>>
void destroy(T* t) {} // 错误:与 #5 具有相同签名
*/
// A 的偏特化通过模板参数启用
template<class T, class Enable = void>
class A {}; // 主模板
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
{}; // 浮点类型的特化
int main()
{
    union { int i; char s[sizeof(std::string)]; } u;
    construct(reinterpret_cast<int*>(&u)<span class="br

参见

(C++17)
void 可变参数别名模板
(别名模板)