web-dev-qa-db-ja.com

テンプレート引数リストを明示的に指定できないため、関数の引数タイプでテンプレートパラメータパックが使用されるのはなぜですか

私は次のコードを持っています:

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

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

このコードにより、コンパイルエラーが発生します。 g++ -std=c++1zを使用してコンパイルすると、エラーは次のように表示されます。

prog.cc: In function 'int main()':
prog.cc:8:24: error: no matching function for call to 'f<int, int>(std::nullptr_t)'
     f<int, int>(nullptr);
                        ^
prog.cc:5:6: note: candidate: template<class ... Args> void f(AAA<Args ...>*)
 void f(AAA<Args...> *) {}
      ^
prog.cc:5:6: note:   template argument deduction/substitution failed:
prog.cc:8:24: note:   mismatched types 'AAA<Args ...>*' and 'std::nullptr_t'
     f<int, int>(nullptr);

clang++ -std=c++1zを使用すると、エラーは次のようになります。

prog.cc:8:5: error: no matching function for call to 'f'
    f<int, int>(nullptr);
    ^~~~~~~~~~~
prog.cc:5:6: note: candidate template ignored: could not match 'AAA<int, int, Args...> *' against 'nullptr_t'
void f(AAA<Args...> *) {}
     ^
1 error generated.

上記のものをMSYS2MinGW-w64環境で実行しています。私のGCCバージョンはGCC 7.1.0で、Clangバージョンは4.0.0です。私がGCCとClangの両方で使用する標準ライブラリは、私のGCCコンパイラにバンドルされているlibstdc ++です。

私の意見では、関数テンプレートfooの呼び出しには、テンプレートパラメータが明示的に指定されているため、テンプレートパラメータパックと関数の引数の型はすでに指定されているはずです。ただし、上記のエラー診断は、関数パラメーターの正確なタイプとnullptr引数が一致しないことを示唆しているようです。これは、関数引数の推論が発生した場合にのみ発生する可能性がある問題のようです。だから私の質問は、なぜそのようなエラーが発生するのですか?それは単なるコンパイラのバグですか、それともC++標準には、元のコードの形式が正しくないことを示すいくつかのルールがありますか?

39
gnaggnoyil

コンパイラはパックをint ,intとして推測する必要があると思うかもしれませんが、C++標準では観察した動作が明示的に要求されます。

[[temp.arg.explicit/9]

テンプレート引数の推定では、シーケンスに明示的に指定されたテンプレート引数が含まれている場合でも、テンプレートパラメータパックに対応するテンプレート引数のシーケンスを拡張できます。 [例:

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

—例を終了]

上記は、いくつかのパラメータが指定されていても、控除が終了しないことを意味します。パラメータパックは、常に引数の演繹によって拡張可能である必要があります。これは、指定された明示的な引数が、後続のパラメーターパックを持つテンプレートのインスタンス化であるかのようです。以下と組み合わせた場合:

[temp.arg.explicit/3]

デフォルトのテンプレート引数から推定または取得できる末尾のテンプレート引数は、明示的なテンプレート引数のリストから省略できます。 他の方法では推定されない後続のテンプレートパラメータパックは、テンプレート引数の空のシーケンスに推定されます。.. ..

コンパイラーは、推論されていない引数を空のパックに一致させる必要があります。しかし、それからそれを推定することは何もありません。

そのため、Args...AAAに接続しようとしても一致しない可能性があります。型シーケンスは末尾にリストがある2つの型であるため(コンパイラーはnullptrから空であると推測できません)。一方、AAAは2つのタイプのみを想定しています。

typename ...Argsを使用している場合、コンパイラは、int, intが使用中のすべてのテンプレートパラメータであるかどうか、または関数の引数を推測することでそれ以上が使用可能かどうかを認識しません。したがって、関数はまだインスタンス化されておらず、コンパイラは関数の引数からパラメータパックの他のすべての可能なパラメータを推測しようとし続けます。

言い換えれば、これは機能します:

f<int>(new AAA<int, int>);

最初のパラメーターはintであると言っていますが、コンパイラーはパラメーターリストを期待し、関数の引数からどんどんパラメーターを貪欲に見つけようとするので、関数テンプレートをインスタンス化します。

あなたの場合も多かれ少なかれ同じことが起こりますが、コンパイラは関数の引数が一致しないためにnullptr_tから何も推測できません。 A<...>へのポインタを想定していますが、nullptrを渡す場合はそうではありません。
これは代わりに機能します:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

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

コンパイラはテンプレート引数が2つであることを認識しており、それらすべてを提供しているため、推測するものはなく、関数をインスタンス化できます。また、AAAは2つのテンプレートパラメータのみを受け入れるため、fのパラメータパックはここでは役に立たないように思われるため、はるかに理にかなっています。

15
skypjack

簡単な解決策を追加するだけです。

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want

後者は、パックArgs...{int, int}に強制します。現在、呼び出し自体は、関数テンプレートの呼び出しではなく、単なる関数ポインターの呼び出しです。 AAA<int, int>*を受け取る関数を呼び出していますが、nullptrを渡すことももちろん可能です。

楽しみのために、任意の数の*sを追加することもできます。

(*****f<int, int>)(nullptr); // still OK - does what you want

...しかし、あなたは知っています...そうではありません。

12
Barry

{}概念を呼び出す別のソリューションを追加したい

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>({});
}

引数が{}の場合、パラメーターの演繹は無効(非演繹コンテキスト)であるため、不一致はなく、パラメーターの初期化によって実際にヌルポインターも生成されます。

@ skypjackによるすばらしい回答

コンパイラが関数の引数を推測するのを助ける必要があります:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

基本的に、nullptranyポインタ型に割り当てることができる「オブジェクトなし」を表します。

4
Saurav Sahu