命名空间
变体
操作

std::enable_if

来自 cppreference.com
< cpp‎ | types
 
 
元编程库
类型特征
类型类别
(C++11)
(C++14)  
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
类型属性
(C++11)
(C++11)
(C++14)
(C++11)
(C++11)(直到 C++20*)
(C++11)(C++20 中已弃用)
(C++11)
类型特征常量
元函数
(C++17)
支持的操作
关系和属性查询
类型修改
(C++11)(C++11)(C++11)
类型转换
(C++11)(C++23 中已弃用)
(C++11)(C++23 中已弃用)
(C++11)
enable_if
(C++11)
(C++17)

(C++11)(直到 C++20*)(C++17)
编译时有理数运算
编译时整数序列
 
定义在头文件 <type_traits>
template< bool B, class T = void >
struct enable_if;
(自 C++11 起)

如果 Btruestd::enable_if 将具有一个公共成员类型定义 type,它等于 T;否则,将没有成员类型定义。

此元函数是在 C++20 的 概念 之前利用 SFINAE 的便捷方式,尤其是根据类型特征有条件地从 候选集 中删除函数,允许根据这些不同的类型特征分别进行函数重载或特化。

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

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

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

内容

[编辑] 成员类型

类型 定义
type T 或没有此成员,具体取决于 B 的值

[编辑] 辅助类型

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; };

[编辑] 注释

常见的错误是声明两个仅在默认模板参数方面不同的函数模板。这将不起作用,因为声明将被视为同一函数模板的重新声明(默认模板参数在 函数模板等效性 中不被考虑)。

/* WRONG */
 
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) {} // error: treated as redefinition
};
 
/* RIGHT */
 
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) {} // OK
};

在使用 enable_if 作为命名空间范围函数模板的模板非类型参数的类型时应小心。一些 ABI 规范,如 Itanium ABI,不包括非类型模板参数的依赖于实例化的部分,这意味着两个不同函数模板的特化最终可能具有相同的 mangled 名称,并被错误地链接在一起。例如

// first translation unit
 
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
 
// second translation unit
 
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) 中具有相同的 mangled 名称,这意味着链接器会错误地认为它们是同一个实体。

[编辑] 示例

#include <iostream>
#include <new>
#include <string>
#include <type_traits>
 
namespace detail
{ 
    void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } 
}
 
// #1, enabled via the return type
template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type 
    construct(T*) 
{
    std::cout << "default constructing trivially default constructible T\n";
}
 
// same as above
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type 
    construct(T* p) 
{
    std::cout << "default constructing non-trivially default constructible T\n";
    ::new(detail::voidify(p)) T;
}
 
// #2
template<class T, class... Args>
std::enable_if_t<std::is_constructible<T, Args&&...>::value> // Using helper type
    construct(T* p, Args&&... args) 
{
    std::cout << "constructing T with operation\n";
    ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}
 
// #3, enabled via a parameter
template<class T>
void destroy(
    T*, 
    typename std::enable_if<
        std::is_trivially_destructible<T>::value
    >::type* = 0)
{
    std::cout << "destroying trivially destructible T\n";
}
 
// #4, enabled via a non-type template parameter
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 << "destroying non-trivially destructible T\n";
    t->~T();
}
 
// #5, enabled via a type template parameter
template<class T,
	 typename = std::enable_if_t<std::is_array<T>::value>>
void destroy(T* t) // note: function signature is unmodified
{
    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) {} // error: has the same signature with #5
*/
 
// the partial specialization of A is enabled via a template parameter
template<class T, class Enable = void>
class A {}; // primary template
 
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
{}; // specialization for floating point types
 
int main()
{
    union { int i; char s[sizeof(std::string)]; } u;
 
    construct(reinterpret_cast<int*>(&u));
    destroy(reinterpret_cast<int*>(&u));
 
    construct(reinterpret_cast<std::string*>(&u), "Hello");
    destroy(reinterpret_cast<std::string*>(&u));
 
    A<int>{}; // OK: matches the primary template
    A<double>{}; // OK: matches the partial specialization
}

输出

default constructing trivially default constructible T
destroying trivially destructible T
constructing T with operation
destroying non-trivially destructible T

[编辑] 另请参阅

(C++17)
可变参数别名模板
(别名模板)[编辑]