yohhoyの日記

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

オーバーロード関数とテンプレート引数推論

プログラミング言語C++において、オーバーロードされた関数群をテンプレートパラメータへ引き渡す方法。

テンプレート引数の関数型を一意に推論できないため、ジェネリックラムダ式でラップする必要がある。テンプレート引数Fラムダ式に対応する固有のクロージャ型へと推論され、operator()演算子の呼び出しまでオーバーロード解決が遅延される。

#include <utility>

// オーバーロードされた関数f
void f(int);
void f(int, int);

// 1引数バージョンを呼び出す
template <class F>
void call1(F f)
  requires requires { f(0); }
{ f(42); }

// 2引数バージョンを呼び出す
template <class F>
void call2(F f)
  requires requires { f(0, 0); }
{ f(1, 2); }

int main()
{
  // NG: ill-formed
  call1(f); 
  call2(f);
  // GCC: no matching function for call to 'callN(<unresolved overloaded function type>)'
  //   couldn't deduce template parameter 'F'
  // Clang: no matching function for call to 'call1N'
  //   couldn't infer template argument 'F'
  // MSVC: no matching overloaded function found
  //   could not deduce template argument for 'F'

  // OK: ジェネリックラムダ式
  auto g = []<class... Ts>(Ts&&... args)
    { return f(std::forward<Ts>(args)...); };
  call1(g);
  call2(g);
}

C++20機能を使えない場合のフォールバック実装:

// C++14/17: SFINAE版
template <class F>
auto call1(F f)
  -> decltype(f(0), void())
{ f(42); }

template <class F>
auto call2(F f)
  -> decltype(f(0, 0), void())
{ f(1, 2); }

auto g = [](auto&&... args)
  { return f(std::forward<decltype(args)>(args)...); };

関連URL