プログラミング言語C++における関数テンプレートのオーバーロードにおいて、SFINAEと組み合わせてオーバーロード解決の優先順を制御するテクニック。
選択候補が2個のケース
2つの型T
, U
に対して、1) 演算 T / U
が定義されていれば同演算子を、2) そうでなければ T * (1/U)
を選択するケースを考える。*1
// 実装関数 第1候補 template <typename T, typename U> auto f_impl(T a, U b, int) -> decltype(a / b) { return a / b; } // 実装関数 第2候補 template <typename T, typename U> auto f_impl(T a, U b, char) -> decltype(a * (U{1} / b)) { return a * (U{1} / b); } // 公開関数 template <typename T, typename U> auto f(T a, U b) { return f_impl(a, b, 0); }
実装関数テンプレートの第3引数に 1)int
型、2)char
型 を指定し、f_impl
呼び出し側で 0 指定により優先順制御を行う。関数のオーバーロード解決(overload resolution)において、リテラル 0
がint
型として扱われる規則が、char
型として扱われる規則より優先されるため。*2
選択候補がN個のケース
優先順位付けを表現するrank<I>
ヘルパクラスを導入する。公開関数で指定するrank<2>
実引数に対して、オーバーロード解決時には基底クラスrank<1>
よりもrank<2>
へと優先的にマッチするため。同様にrank<0>
よりもrank<1>
が優先する。
template <int I> struct rank : rank<I-1> {}; template <> struct rank<0> {}; // 実装関数 第1候補 template <typename T, typename U> auto f_impl(T a, U b, rank<2>) -> decltype(a / b) { return a / b; } // 実装関数 第2候補 template <typename T, typename U> auto f_impl(T a, U b, rank<1>) -> decltype(a * (U(1) / b)) { return a * (U(1) / b); } // 実装関数 第3候補 template <typename T, typename U> int f_impl(T a, U b, rank<0>) { return 0; } // 公開関数 template <typename T, typename U> auto f(T a, U b) { return f_impl(a, b, rank<2>{}); }
メモ:Web上で本テクニックを利用するコードをちらほら見かける。名前の付いたIdiomではないのだろうか?
関連URL
*1:ここでは公開関数 f でのみ通常関数での戻り値型推論を利用している。実装関数 f_impl の末尾戻り値型宣言を省略するとSFINAEが機能せずに、式 T / U の有効有無にかかわらず1)にオーバーロード解決されてしまう。
*2:リテラル 0 はC++14 2.14.2/p2よりint型と解釈される。優先順位は13.3.3.1.1のstandard conversion sequenceにある1) No conversions required → Eact Match Rank と2) Integral conversions → Conversion Rank に基づく…ような気がする。オーバーロード解決規則は難解すぎて理解できない。