在c++中真的不可能跳过带有默认实参的模板形参吗?为什么语法不这么认为?

Is it really impossible to skip template parameters with default arguments in C++, why does syntax suggest otherwise?

本文关键字:为什么 形参 语法 不可能 真的 c++ 实参 默认      更新时间:2023-10-16

我一直在试图找到一种方法来跳过不位于模板参数列表末尾的模板参数,在派生类中已在其基类中分配了默认值。

我对这个话题做了一些研究,也在SO上。虽然类似的问题已经在SO上讨论过了,但许多答案基本上表明它不起作用,这些问题与非常特殊的情况有关,比如这里的哈希映射情况。我还找到了"Potatoswatter"的答案,在我看来,这与跳过这样一个参数的不可能性相矛盾。在他的回答中,他声称这个声明是有效的:

template< class A, class B = int, class C >
class X;

假设模板形参不能被跳过(除非在实参列表的末尾),这样的声明根本没有意义。由于B被赋了一个默认值,但后面跟着没有默认值的C,在这种情况下,B总是必须显式赋值,使得int作为B的默认赋值完全无用。上面的X声明有意义的唯一场景是下列Y声明之一是有效的:

class Y : public X<double, , const std::string&> { ... }
class Y : public X<A = double, C = const std::string&> { ... }

那么,在派生专门化类时,真的不可能跳过不在模板形参列表末尾的模板形参吗?

如果这是不可能的,为什么会这样,为什么法律语法明显地表明不是这样(参见上面的类X示例)?

如果实际上不是不可能,如何跳过已被赋值为默认值的模板参数?

相关标准请参见"模板参数[temp.param]"(14.1)。

本质上,只有当它所应用的参数后面没有任何没有默认参数([temp.param]/11)的非包参数时,才可以使用默认参数。但是,您引用的语法在[temp.param]/10:

所描述的情况下可以在声明中使用。

可使用的默认模板参数集是通过合并来自的默认参数获得的模板的所有先前声明都与默认函数实参相同(8.3.6)。(例子:

template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;

等价于

template<class T1 = int, class T2 = int> class A;

- 结束示例]

这个词"类"是隐藏在你的问题,所以我想我会提到(即使这不是一个确切的答案),模板参数与默认值可以被"跳过",在调用函数模板。考虑以下代码:

template<class X, class Y = int, class Z>
void foo(X x, Y y, Z z) {
    (void)x, (void)y, (void)z;
    puts(__PRETTY_FUNCTION__);
}
int main()
{
    foo(3.14, {}, 1.45f);
}
  • 模板参数X推导为double(默认值,如果有,将不使用)。
  • 本次调用无法推导出模板参数Y,因此使用默认值int
  • 模板参数Z推导为float
由于{}在函数实参列表中的不可演绎性,在STL中为函数模板形参提供默认值的情况很多。参见std::exchangestd::optional的构造函数#8。

您可能期望c++ 17中的构造函数模板参数推导可以使用类似的技巧,但我的实验表明,这不是的情况。在模板定义中,如果在列表中较早地放置template-parameter-with-default而不是template-parameter-without-default,编译器将生成诊断。

template<class X, class Y = int, class Z>  // error!
struct Foo {};