为什么在函数参数types中使用的模板参数包作为模板参数列表不能被显式指定

我有以下一段代码:

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. 

我在MSYS2 MinGW-w64环境中运行以上。 我的GCC版本是GCC 7.1.0,我的Clang版本是4.0.0; 我在GCC和Clang中使用的标准库是与我的GCC编译器捆绑在一起的libstdc ++。

在我看来,函数模板foo的调用具有明确指定的模板参数,因此应该已经指定了模板参数包和函数参数types。 但是,上面显示的错误诊断似乎表明,函数参数的确切types和nullptr参数不匹配,这似乎是只有在函数参数推演发生时才可能发生的问题。 所以我的问题是,为什么会出现这样的错误? 这只是一个编译器错误,还是C ++标准有一些规则说明原始代码是不正确的?

你可能认为编译器应该推导出这个包为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不可能匹配。 因为types序列是尾随列表的两种types(即编译器无法从nullptr )。 而AAA期望只有两种types。

因为你正在使用typename ...Args ,编译器不知道int, int是所有正在使用的模板参数,或者通过推导函数参数来获得更多的参数。 因此函数还没有实例化,编译器继续从函数参数中推导出参数包的所有其他可能的参数。

换句话说,这是有效的:

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

因为你说第一个参数是int ,但是编译器需要一个参数列表,并继续试图从函数参数中贪婪地find越来越多的参数,然后它实例化函数模板。

你的情况或多或less会发生同样的情况,但编译器无法从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); } 

因为编译器知道模板参数是两个,并且提供了所有这些参数,所以没有什么可推断的,并且可以实例化该函数。 它也更有意义,因为AAA只接受两个模板参数,所以f的参数包在这里似乎没用。

只需添加一个简单的解决scheme:

 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 

…但是,你知道…不。

我想添加另一个调用{}概念

 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. 

从根本上说, nullptr表示一个“无对象”,可以指定给任何指针types。