std::enable_if
| ヘッダ <type_traits> で定義
|
||
template< bool B, class T = void > struct enable_if; |
(C++11以上) | |
B が true であれば、 std::enable_if は T に等しいパブリックメンバ型 type を持ちます。 そうでなければ、メンバ型は存在しません。
このメタ関数は、型特性に基づいて条件的にオーバーロード解決から関数を削除したり、異なる型特性に対して異なる関数のオーバーロードや特殊化を提供するために、 SFINAE を利用するための便利な方法です。 std::enable_if は関数の追加の引数として (演算子オーバーロードには適用できません)、戻り値の型として (コンストラクタやデストラクタには適用できません)、またはクラステンプレートや関数テンプレートの引数として使用できます。
メンバ型
| 型 | 定義 |
type
|
B の値によって、 T またはそのようなメンバは存在しないのいずれか
|
ヘルパー型
<tbody> </tbody> 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つの関数テンプレートを宣言することです。 宣言が同じ関数テンプレートの再宣言として扱われるため、これは動作しません (デフォルトテンプレート引数は関数テンプレートの同等性に対して考慮されません)。
/*** 間違い ***/
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // エラー、再宣言として扱われます。
};
/* 正しい */
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}
template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // OK。
};
名前空間スコープの関数テンプレートのテンプレート非型引数の型に enable_if を使用するときは注意が必要です。 Itanium ABI のような一部の ABI の仕様は、マングリングにおける非型テンプレート引数の実体化依存の部分を含みません。 これは、2つの異なる関数テンプレートの特殊化が同じマングリング名になり、誤ってリンクされるかもしれないということを意味します。 例えば、
// 1つめの翻訳単位
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
// 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 <type_traits>
#include <iostream>
#include <string>
namespace detail { struct inplace_t{}; }
void* operator new(std::size_t, void* p, detail::inplace_t) {
return p;
}
// #1 戻り値を用いて有効化されます。
template<class T,class... Args>
typename std::enable_if<std::is_trivially_constructible<T,Args&&...>::value>::type
construct(T* t,Args&&... args)
{
std::cout << "constructing trivially constructible T\n";
}
// #2
template<class T, class... Args>
std::enable_if_t<!std::is_trivially_constructible<T,Args&&...>::value> //Using helper type
construct(T* t,Args&&... args)
{
std::cout << "constructing non-trivially constructible T\n";
new(t, detail::inplace_t{}) T(args...);
}
// #3 引数を用いて有効化されます。
template<class T>
void destroy(
T* t,
typename std::enable_if<
std::is_trivially_destructible<T>::value
>::type* = 0
){
std::cout << "destroying trivially destructible T\n";
}
// #4 テンプレート引数を用いて有効化されます。
template<class T,
typename std::enable_if<
!std::is_trivially_destructible<T>{} &&
(std::is_class<T>{} || std::is_union<T>{}),
int>::type = 0>
void destroy(T* t)
{
std::cout << "destroying non-trivially destructible 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 と同じシグネチャを持ちます。
*/
// テンプレート引数を用いて有効化される部分特殊化の例。
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()
{
std::aligned_union_t<0,int,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> a1; // OK、プライマリテンプレートにマッチします。
A<double> a2; // OK、部分特殊化にマッチします。
}
出力:
constructing trivially constructible T
destroying trivially destructible T
constructing non-trivially constructible T
destroying non-trivially destructible T
関連項目
(C++17) |
void の可変個引数エイリアステンプレート (エイリアステンプレート) |