クラステンプレートのテンプレートパラメータに基づき、非テンプレートなメンバ関数オーバーロードでSFINAEを実現する方法。
template<typename T, typename U> struct X { // テンプレートパラメータT, Uによるメンバ関数オーバーロード void mf(const T&) { ... } // A) void mf(const U&) { ... } // B) }; // NG: メンバ関数A), B)が同一シグネチャとなる X<int, int> x;
SFINAEは関数テンプレートパラメータ推論時にのみ機能する。下記コードB)は非テンプレートメンバ関数のためill-formed。
template<typename T, typename U> struct X { void mf(const T&) { ... } // A) // 戻り値型でSFINAEするつもりが... std::enable_if_t<!std::is_same_v<T, U>> mf(const U&) { ... } // B) // NG: "メンバ関数戻り値型が存在しない"状態となりコンパイルエラー }; X<int, int> x;
強引にSFINAEを有効化するため、B)をメンバ関数テンプレートとして定義する。パターン1では非型テンプレートパラメータAlwaysTrue
をstd::enable_if
条件式に含めることで、パターン2ではクラステンプレートパラメータT
, U
をメンバ関数テンプレートパラメータT1
, U1
のデフォルト値とすることで、メンバ関数オーバーロード解決時にSFINAEを引き起こす。
template<typename T, typename U> struct X { void mf(const T&) { ... } // A) // メンバ関数テンプレートとして定義する #if パターン1 template<bool AlwaysTrue = true> std::enable_if_t<!std::is_same_v<T, U> && AlwaysTrue> mf(const U&) { ... } // B) T==Uのときは定義されない #elif パターン2 template< typename T1 = T, typename U1 = U, typename = std::enable_if_t<!std::is_same_v<T1, U1>> > void mf(const U&) { ... } // B) T1==U1のときは定義されない #endif }; X<int, int> x; x.mf(42); // OK: A)を呼び出す
おまけ:C++2a(C++20) Conceptsで導入されるrequires節を利用すると、SFINAEに頼らない関数オーバーロード選択が可能となる。(→id:yohhoy:20170904)
// C++2a template<typename T, typename U> struct X { void mf(const T&) { ... } // A) void mf(const U&) requires !std::is_same_v<T, U> { ... } // B) T==Uのときは定義されない }; X<int, int> x; x.mf(42); // OK: A)を呼び出す
関連URL