yohhoyの日記

技術的メモをしていきたい日記

クラステンプレートの非テンプレートメンバ関数でSFINAE

クラステンプレートのテンプレートパラメータに基づき、非テンプレートなメンバ関数オーバーロードで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では非型テンプレートパラメータAlwaysTruestd::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