プログラミング言語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)。じゃないかな?テンプレートわからん。