使用模板模板参数时是否需要显式列出默认参数

Is it required to explicitly list default parameters when using template template parameter?

本文关键字:参数 默认 是否      更新时间:2023-10-16

我想问一下是否应该编译以下代码示例:

#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;
template <template <class...> class C>
struct convert_container
{
    using type = C<double>; 
    // Visual Studio requires this to be:
    // using type = C<double, std::allocator<doble>>
};
int main()
{
    std::cout << typeid(convert_container<std::vector>::type).name();
}

该代码在GCC 4.8.1和Clang 3.4中编译良好,但在Visual Studio 2013中则不然。我得到的错误:

error C2976: 'std::vector' : too few template arguments
    c:program files (x86)microsoft visual studio 12.0vcincludevector(650) : see declaration of 'std::vector'
    c:usersmichałdocumentsvisual studio 2013projectstransformtransform.cpp(14) : see reference to class template instantiation 'convert_container<std::vector>' being compiled

标准对此有何规定?在使用模板模板参数C时,我是否需要明确说明所有参数(包括默认参数(,或者这只是VC++中的一个错误?

上下文:这个问题源于Constructor对我之前问题的回答:https://stackoverflow.com/a/23874768/2617356

在搜索档案时,我发现了这个问题:带有模板参数的模板中的默认值(C++(基本上是关于同一个问题,问题作者指出,模板模板参数的默认参数"必须"明确说明。然而,询问者接受了在我的情况下不太适用的解决方案。问题不在于什么是符合标准的行为,所以我相信这不是重复的。

考虑类似的

template <typename = void, int = 0> struct A { };
template <template <typename ...> class T> struct B : T<> { };
template class B<A>;

这显然包含在标准中(14.3.3p3如果你感兴趣,我不会引用它,因为GCC和clang都已经实现了规则(,其中由于非类型模板参数,不允许使用A作为B的模板参数。如果模板模板参数的实例化可以使用模板模板参数默认的模板参数,则该规则毫无意义,因此MSVC和Intel的行为比GCC和clang的行为更一致。

当然,推理"如果这是有效的,则该标准将具有不一致性";实际上并不意味着它是无效的,只是它不应该是有效的。要真正检查标准上说的内容:

14.1模板参数[temp.param]

10可与模板声明或定义一起使用的默认模板参数集是通过合并定义中的默认参数(如果在范围内(和范围内的所有声明获得的,方法与默认函数参数相同(8.3.6(

8.3.6默认参数[dcl.fct.Default]

4不同作用域中的声明具有完全不同的默认参数集。也就是说,内部作用域中的声明不会从外部作用域中获取默认参数,反之亦然。

虽然不是专门针对默认模板参数的使用,但我认为它确实做到了。Nikos Athanasiou已经包含了标准中规定C的任何默认模板参数都可以使用的部分:

14.1模板参数[temp.param]

14允许模板模板参数模版参数有一个默认的template自变量。当指定此类默认参数时,它们将应用于模板模板参数范围内的模板template参数

由于使用了C的默认模板参数,所以std::vector没有,MSVC和Intel在这里似乎是正确的。

举一个例子,清楚地表明GCC和clang在这里不能被认为是一致的:

template <typename = char, typename = short>
struct A { };
template <template <typename = void, typename ...> class T>
struct B {
  using type = T<>;
};

GCC和clang都将B<A>::type视为A<void, short>,分别从TA中获取一个默认模板参数,尽管标准不允许在不同范围的声明中合并默认参数(因此也不允许合并默认模板参数(。


为了避免键入分配器参数,您可以使用一个模板别名:

template <template <class...> class C>
struct convert_container
{
  using type = C<double>; 
};
template <typename T>
using vector_default_alloc = std::vector<T>;
int main()
{
  std::cout << typeid(convert_container<vector_default_alloc>::type).name();
}

我现在无法在MSVC上进行测试,但英特尔接受了它,我看不出这个变体无效的原因。

来自标准14.1 Template parameters 的(看似相关的(报价

14。允许模板模板参数的模板参数具有默认模板参数。当指定此类默认参数时,它们将应用于模板模板参数范围内的模板模板参数

[示例:

template <class T = float> struct B {};
template <template <class TT = float> class T> struct A {
inline void f();
inline void g();
};
template <template <class TT> class T> void A<T>::f() { // (*)
T<> t; // error - TT has no default template argument
}
template <template <class TT = char> class T> void A<T>::g() {
T<> t; // OK - T<char>
}

--结束示例]

这是对使用模板模板参数的默认模板参数的唯一限制(第9节、第11节、第12节对定义/规范的限制(

正如评论中强调的那样,OP的情况不涉及convert_container中的默认参数(因此上述内容不明确适用(。IMHO有两种方式来解释这种情况:

  1. using type = C<double>是类模板的类型别名;该类"失去"使用默认模板参数的权利,因为它是作为模板模板参数传递的,并且(该TT参数的(所有默认参数都在"typedefing"的范围之外。那么VS是正确的。

  2. 通过跟踪实例化过程:假设一个正确的编译器将结构实例化为(这只是一个类型替换-没有隐含实际实例化过程的实际表示(

    struct convert_container
    {
        using type = vector<double>; 
    };
    

那么OP的情况看起来相当合法(gcc/clang是正确的(


FWIW

这在VS2013 中编译

template <template <class...> class C>
using tt = C<double>;
int main()
{
    std::cout << typeid(tt<std::vector>).name();
}

因此,传递给模板参数的默认模板参数是不合法的,这一论点似乎越来越不可靠。