为什么在函数参数类型中使用模板参数包作为其模板参数列表无法显式指定

Why is template parameter pack used in a function argument type as its template argument list not able to be explicit specified

本文关键字:参数 列表 类型 包作 为什么 函数      更新时间:2023-10-16

我有以下一段代码:

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的调用明确指定了其模板参数,因此应该已经指定了模板参数包和函数参数类型。但是,上面显示的错误诊断似乎表明函数参数的确切类型和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不可能匹配。因为类型序列是带有尾随列表的两种类型(编译器无法从nullptr推断为空(。而AAA预计只有两种类型。

对于您正在使用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);
}

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

只是为了添加一个简单的解决方案:

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在那里是可以接受的。

为了好玩,您还可以添加任意多个*

(*****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表示可以分配给任何指针类型的"无对象"。