使用变长模板的特化作为模板参数

4
请看以下内容:
template <class...>
struct MyT;

template <class T>
struct MyT<T> {};

template <template <class> class TT = MyT> struct A {}; // fine

using B = A<MyT>; // does not compile

int main() {
  return 0;
}

当将MyT用作A的默认参数时,编译器(g++ 5.4.0)会很高兴。然而,当它用于实例化A时,情况就不同了:
temp.cpp:19:16: error: type/value mismatch at argument 1 in template parameter list for ‘template<template<class> class TT> struct A’
 using B = A<MyT>;
                ^
temp.cpp:19:16: note:   expected a template of type ‘template<class> class TT’, got ‘template<class ...> struct MyT’

我可以通过引入别名来解决这个问题:

template <class T>
using MyTT = MyT<T>;

using B = A<MyTT>; // fine

问题:错误的原因是什么,有没有不引入别名的解决方案?
编辑请注意:代码中声明了A具有模板模板参数,不能更改。

然后,您应该定义一个模板类或别名声明,它期望恰好一个参数。 - skypjack
1
当B没有模板化时,你如何实例化MyT?你的struct A基本上是无意义的;如果你甚至不能使用它,为什么要接受一个模板参数呢? - AndyG
1
@skypjack,编译器为什么可以很好地接受MyT作为默认参数呢? - AlwaysLearning
1
@AlwaysLearning 不是的。尝试直接使用它。 - skypjack
@AndyG 实际应用要复杂得多,涉及到在预处理后替换未定义的预处理器符号的脚本。我同意在简单的示例中可能看起来毫无意义。然而,这个示例足以回答所提出的问题。 - AlwaysLearning
显示剩余2条评论
4个回答

3

您不能这样做,也不能使用此类型作为默认参数。事实上,只要您不依赖于它,似乎它是被接受的,并不意味着默认参数是有效的。
请考虑以下明确使用默认类型的代码:

template <class...>
struct MyT;

template <class T>
struct MyT<T> {};

template <template <class> class TT = MyT> struct A {}; // fine

int main() {
  A<> a;
  return 0;
}

错误信息很清楚:

模板模板参数与其相应的模板模板参数具有不同的模板参数。

在这种情况下,部分特化不被考虑,因此两个声明不同。
您应该将A声明为:

template <template <class...> class TT = MyT> struct A;

或者在某个地方声明一个只有单一参数的受限类型,例如通过使用声明。

编译器为什么没有检查MyT是否适合作为默认参数?我认为编译器在实例化模板之前通常会执行此类检查... - AlwaysLearning
不确定从标准的角度来看是否是强制性的,或者它是否是未定义行为。我认为这是有效的,因为两个主要的编译器都这样做了,但让我们等待比我更有经验的人用“标准术语”来解释一下。 :-) - skypjack

1

首先,默认参数也不起作用。

其次,模板模板参数是一种奇怪的东西。如果模板模板参数可以使用与模板模板参数中描述的签名相匹配的任何内容进行实例化,则会有意义。

但实际情况并非如此。

相反,它的工作方式正好相反。

template<template<class...>class Z> struct foo {};
template<template<class   >class Z> struct bar {};

template<class...>struct a{};
template<class   >struct b{};

foo将接受ab

bar将仅接受b

一旦你理解了这个,正确的反应是“到底是怎么回事?”如果你没有回答“到底是怎么回事?”,请回头看看是否能够理解。这基本上是从C ++中典型的输入参数类型向后工作的方式; 它的行为更像返回类型而不是参数。(如果您想直接讨论此内容,请学习协变和逆变等术语)

这非常不直观,为什么要这样工作的原因涉及追溯C ++的历史。

但是,好处是,template<class...>class参数实际上是“仅采用类型参数的任何模板”。我发现这非常有用。

缺点是,template<class>class参数几乎完全无用。

简而言之:使您的template<template参数成为template<template<class...>class,并仅使用仅接受类型的模板进行元编程。如果有一个接收值的模板,请编写一个类型包装器,将std::size_t X的要求替换为std::integral_constant< std::size_t, X >

0
暂且不考虑“你为什么要这样做”的问题,如果你没有进行任何模板特化,第一个版本是可以工作的。
template <class T>
struct MyT {  };

template <template <class> class TT = MyT> struct A 
{};

using B = A<MyT>;

通过模板特化,编译器必须确定最佳匹配,但由于您从未实际提供任何模板参数,因此存在歧义。

当您引入MyTT时,您正在使用单个模板参数,编译器足够聪明,可以看到只有一个参数时您有一个特化:

template <class T>
using MyTT = MyT<T>;

在这种情况下,它选择专业化而不是可变版本。

但现在我们回到了最重要的问题...为什么?除非在A中您始终使用特定类实例化MyT,否则使用A毫无意义:

template<template<class> class TT = MyT> struct A
{
   // use an instance of TT??
   TT<int> myInstance; // forced to choose `int`
};

如果您不提供特化并使用可变参数模板,则它将无法工作。请注意,在您的示例中,您实际上已经清除了主模板并使用了具有单个参数的特化。这样做是因为它是正确的,所以它可以工作。 - skypjack
编译器足够聪明,可以看出MyTT是一个模板,但却无法看出A的模板参数是一个模板?这有什么区别吗? - AlwaysLearning
@skypjack: 这就是我想要暗示的意思;在只有单一模板结构的情况下,它可以工作。我想要引入为什么当有一个主要的可变参数模板时,OP能够使它工作。 - AndyG

0
我想把你的问题分成两个部分。
A)考虑你的结构模板更简单。
template <class T>
  struct TestStruct {
};

template <
  template <class>
  class TT = TestStruct
>
struct A
{
  int a; // sorry to modify this. This help to show how it works
};

int main() {
  A< TestStruct > b;
  b.a; // it works!
  return 0;
}

它能够工作是因为模板类TT只接受带有<class...>模板的模板。专门化的类不计入其中(因为它的底层仍然是模板< class ... >)。
B) 即使您将结构体A更新为template<class...>,您仍然有一个问题。TT的模板参数是什么?请参见下面的示例。
template <class...>                                                                                                                 
struct MyT;                                                                                                                         

template <class T>                                                                                                                  
struct MyT<T> {                                                                                                                     
    int a;                                                                                                                          
};                                                                                                                                  

template <                                                                                                                          
    template <class...>                                                                                                             
    class TT = MyT
    // may be you need to add some more typename here, such as
    // typename T1, ... , and then TT<T1> a;                                                                                                                 
>                                                                                                                                   
struct A                                                                                                                            
{                                                                                                                                   
    TT<int> a; 
    // Here ! TT is a template only, do not have the template parameters!!!                                                                                                                   
};                                                                                                                                  

int main() {                                                                                                                        

  A< MyT > b;                                                                                                                                        
  b.a;     // it works!!                                                                                                                         

  return 0;                                                                                                                         
}                                                                                                                                   

但是,如果您真的无法更新这些定义的签名,您可以使用代理类

template< class T >
struct Proxy : MyT<T>
{
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接