std::enable_if
| ヘッダ <type_traits> で定義 |
||
| template< bool B, class T = void > struct enable_if; |
(C++11以降) | |
B が true の場合、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可変引数エイリアステンプレート (エイリアステンプレート) |