名前空間
変種
操作

std::enable_if

From cppreference.com
< cpp‎ | types
 
 
メタプログラミングライブラリ
型特性
型のカテゴリ
(C++11)
(C++11)(DR*)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11)
(C++11) 
(C++11)
(C++11)
型のプロパティ
(C++11)
(C++11)
(C++14)
(C++11)(C++26で非推奨)
(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)
(C++11)(C++20まで*)(C++17)

enable_if
(C++11)
(C++17)
コンパイル時有理数演算
コンパイル時整数シーケンス
 
ヘッダ <type_traits> で定義
template< bool B, class T = void >
struct enable_if;
(C++11以降)

Btrue の場合、std::enable_if は公開メンバの typedef type を持ち、T と等しくなります。それ以外の場合、メンバ typedef は存在しません。

このメタ関数は、C++20 の concepts よりも前の 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; };

[編集] 注釈

よくある間違いは、デフォルトのテンプレート引数のみが異なる2つの関数テンプレートを宣言することです。これは、宣言が同じ関数テンプレートの再宣言として扱われるため、機能しません(デフォルトのテンプレート引数は、関数テンプレートの等価性では考慮されません)。

/* 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 を使用する際には注意が必要です。Itanium ABI のような一部の ABI 仕様では、非型テンプレートパラメータのインスタンシエーション依存部分がマンリングに含まれていないため、2つの異なる関数テンプレートの特殊化が同じマンल्ड名を持ち、誤ってリンクされる可能性があります。例えば、

// 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)を持ちます。これは、リンカがそれらを誤って同じエンティティと見なすことを意味します(Itanium C++ ABI)。

[編集]

#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)
void可変引数エイリアステンプレート
(エイリアステンプレート)[編集]
English 日本語 中文(简体) 中文(繁體)