yohhoyの日記

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

関数テンプレートでの可変長引数テンプレート明示特殊化の複雑な関係

プログラミング言語C++において、可変長引数テンプレートのテンプレート・パラメータパック(template parameter pack)は、関数テンプレートに対する明示的なテンプレート実引数指定では完全に特殊化できない(パターンがある)。

本記事の内容はStack Overflowで見つけた質問と回答に基づく。

下記コードでは関数テンプレートfのテンプレート・パラメータパックを Types={int, int} で完全に特殊化しようとしているが、概念的には Types={int, int, Ts...} のような特殊化と解釈される。続いて関数呼び出しのためにテンプレートの実引数推定(template argument deduction)が行われるが、実際には部分特殊化しか行われておらず実引数nullptr(nullptr_t型)からはTs部分を推定できない。*1

#include <tuple>

template <typename ...Types>
void f(std::tuple<Types...>*) {}

int main() {
  f<int, int>(nullptr);  // ★ NG
}

関数呼び出しの実引数で特殊化された型(std::tuple<int, int>*)を明示するか、関数テンプレートfのテンプレート実引数推定を確定させることで回避可能。*2

std::tuple<int, int>* p = nullptr;
f<int, int>(p);  // OK
// または
auto g = f<int, int>;
g(nullptr);  // OK
// または
(*f<int, int>)(nullptr);  // OK

同言語仕様を利用(?)すると、テンプレート・パラメータパックの一部(前半部分)のみを特殊化できる。

std::tuple<int, float, double>* q = nullptr;
f<int, float>(q);  // OK

C++14 14.8.1/p9より引用。

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [Example:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);
  // Types is deduced to the sequence int*, float*, int
}

-- end example]

メモ:同じ関数呼び出しパターンでも、リスト初期化(list initialization)によるf<int, int>({});であれば期待通りに型推論が行われる(ポインタ型に対する{}はヌルポインタ)。関数呼び出しの実引数にリスト初期化が指定された場合、non-deduced contextとみなされるため(C++14 14.8.2.1/p1, 14.8.2.5/p5)。じゃないかな?テンプレートわからん。

*1:同様に、実引数にリテラル 0 や (void*)0 を指定しても推定に失敗する。

*2: (f<int, int>)(nullptr); は f<int, int>(nullptr); と同義のため、単純な括弧だけでは効果がない。単項演算子 * のほかには、単項演算子 & や単項演算子 + も利用できる。いずれも関数テンプレートのアドレス取得によるテンプレート実引数推定が行われる(C++14 14.8.2.2/p1)。ような気がする。