有两种情况:
template<class... Ts>
struct Outer {
template<typename Ts::type...>
void inner(){}
};
我认为这非常简单,因为自 C++11 以来这在技术上是可能的,但编译器之间的结果行为差异很大: 对变量
Outer<std::type_identity<bool>, std::type_identity<int>> outer{};
调用以下成员函数会产生以下结果(Demo - 对 main
中的特定行进行注释以查看编译器输出):
outer.inner<true, 42>();
:
error: wrong number of template arguments (2, should be 1)
outer.inner<1.0, 2.0>();
:
error: wrong number of template arguments (2, should be 1)
candidate template ignored: invalid explicitly-specified argument for template parameter 'Vals'
Failed to specialize function template 'void Outer<std::type_identity<bool>, std::type_identity<int>>::inner(void)'
(后跟 ICE
)outer.inner<true>();
:
ICE
candidate template ignored: deduced too few arguments for expanded pack 'Vals'; no argument for 2nd expanded parameter in deduced argument pack <true>
outer.inner<>();
:
candidate template ignored: deduced too few arguments for expanded pack 'Vals'; no argument for 1st expanded parameter in deduced argument pack <>
虽然 clang 的行为符合我个人的预期,但编译器之间的差异让我觉得我正在做一些官方不允许的事情(也许是 clang 扩展?),那么官方标准说这里应该发生什么?
template<class, std::size_t>
concept At = true;
template<std::size_t... Is>
struct Outer{
template<At<Is>... Ts>
void inner(Ts...){}
};
在变量 Outer<0, 1> outer{};
上调用以下成员函数会产生以下结果(Demo - 对
main
中的特定行进行注释以查看编译器输出):
outer.inner(0, 1);
:
outer.inner(0, 1, 2);
:
error: mismatched argument pack lengths while expanding 'At<Ts, Is>'
candidate function [with Ts = <int, int>] not viable: requires 2 arguments, but 3 were provided
the associated constraints are not satisfied
outer.inner(0);
:
error: mismatched argument pack lengths while expanding 'At<Ts, Is>'
candidate template ignored: deduced too few arguments for expanded pack 'Ts'; no argument for 2nd expanded parameter in deduced argument pack <int>
the associated constraints are not satisfied
GCC
和
MSVC
是正确的,因为它们的行为似乎与重写的方式相同,在这种情况下
clang
也表现得像
GCC
和
MSVC
(Demo ):
template<std::size_t... Is>
struct Outer{
template<class... Ts>
requires (At<Ts, Is> && ...)
void inner(Ts...){}
};
但是clang
似乎创建了某种固定长度的参数包,它可以实现一些有趣的场景,例如领先包(Demo):
template<std::size_t... Is>
struct Outer{
template<At<Is>... Ts, class U>
void inner(Ts..., U){}
};
int main() {
Outer outer<0, 1>{}.inner(true, 1, 2.0);
}
与 clang
编译并推导出
Ts = <bool, int>
和
U = double
。那么问题又来了,根据标准,哪种行为是正确的?
[...]这意味着
typename
后跟限定 ID 表示非类型参数声明 中的类型。 [...]
typename Ts::type
表示类型为
Ts::type
的非类型模板参数。它实际上不能是其他任何东西:它不能是名为
Ts::type
的类型模板参数,因为您无法声明模板参数具有限定名称。然后,[temp.param]/17 说
[...] 模板参数包是一个其中一个例子是参数声明,其类型包含一个或多个未扩展的包,是包扩展。 [...]
template <class... T>
struct value_holder {
template <T... Values> struct apply { }; // Values is a non-type template parameter pack
}; // and a pack expansion
这清楚地表明了应该发生的事情:...
在
T
的声明中扩展了包
apply
。例如,类
value_holder<bool, int>
包含一个嵌套类模板
apply
,它采用一个
bool
和一个
int
模板参数。在您的示例中,成员模板
inner
的模板参数包未命名,但这种差异并不相关:
typename Ts::type...
只是一个参数声明,省略了可选名称。 Clang 行为正确;我不知道为什么其他编译器不这样做。请针对 GCC 提交错误。
在你的第二个例子中,Clang 也是正确的。后来在[temp.param]/17中我们有这样一句话:
带有type-constraint 且包含未扩展参数包的类型参数包是包扩展。
At<Is>... Ts
是一个类型参数包,其type-constraint 为
At<Is>
,因此此声明扩展了包
Is
,从而产生固定长度的类型模板参数列表。
Ts
表示这个固定长度的列表,然后通过参数声明
Ts...
扩展为长度等于
Is
的函数参数列表。