3

我的第一个代码片段可以编译并且工作正常:

template <class x> struct B1
{
    template <class x> struct B2     { int getX() { return(16); } };
    template <class x> struct B2<x*> { int getX() { return(20); } };
};

void main(int argc, char* argv[])
{
    B1<int>::B2<int>  a1;
    B1<int>::B2<int*> a2;
    printf("a1=%d, a2=%d.\r\n", a1.getX(), a2.getX());
}

请注意,模板参数的名称x在两个模板中,这不会混淆编译器。第二个示例因编译器崩溃(MSVC 2008)而失败,即没有给出任何语法错误:

template <class x> struct B1
{
    template <class x> struct B2 { int getX() { return(16); } };
    template <class x> struct B2<x*>;
};

template <class x> template <class x>
struct B1<x>::B2<x*>
{
    int getX() { return(3); }
};

我的问题不是关于如何修复这个例子,这很明显。

我查看了有关访问模板头参数的规则的标准(C++ 2003),但找不到任何相关内容。也许我错过了一些东西。

在处理B1<x>时,编译器是否应该只考虑第一个模板头中的参数?更有可能是的。那么在处理B2<x>时,是否应该同时考虑两者?当 B1/B2 的参数本身包含限定标识符并且这些标识符想要访问模板头的参数时,更复杂的情况特别有趣。为简单起见,我没有给出完整的例子。

如果有人遇到这个并且可以评论或知道一些文章/书籍、YouTube 视频等,我很想听听这个。

更新:我刚刚使用 MSVC 尝试了以下操作:

template <class x> struct B1
{
    x qq1;

    struct B2
    {
        int x;
    };
};

这可以按预期编译和工作。我还尝试了一个访问内部数据字段的 exe x。这表明 MS 编译器在内部范围内实现了隐藏模板参数。对我来说,即使这不符合标准,这也是合乎逻辑的。

4

1 回答 1

4

尽管评论已考虑回答该问题,但我将在下面提供更多详细信息。请注意,我的答案仅基于 C++11 (ISO/IEC 14882-2011)。

第 1 部分:模板参数可以重新用作嵌套成员模板的模板参数吗?

标准中有两个关于模板参数状态的关键语句(特别是类型参数——这是唯一与问题相关的类型)。第一条语句将它们描述为与typedef-names具有相同的状态:

(§14.1/3) 标识符不跟在省略号后面的类型参数将其标识符定义为模板范围内的 typedef-name(如果使用 class 或 typename 声明)或 template-name(如果使用 template 声明)宣言。[...]

关于 typedef-names 的可能重新声明,我们有:

(第 7.1.3/6 节)在给定范围内,不应使用 typedef 说明符重新定义在该范围内声明的任何类型的名称以引用不同的类型。[...]

(备注:上述规则似乎只适用于 typedef 说明符的使用,尽管别名声明(§7.1.3/2)和模板参数声明(§14.1/3)也可用于声明 typedef-names。也就是说,上面的规则并没有明确排除使用别名声明或模板参数来重新声明同一范围内的 typedef-name,尽管这显然是预期的含义。措辞应该是“没有 typedef-name 声明应使用”而不是“不应使用 typedef 说明符”。)

这意味着您不能这样做:

{
  typedef int   T;
  typedef float T;
}

因为第二个声明发生在T最初声明的相同范围内。然而,这:

{
  typedef int   T;
  {
    typedef float T;
  }
}

根据上述规则完全合法,因为第二个声明位于块范围内(尽管第一个声明在T那里仍然有效)不是T最初声明的范围。

由于上面引用的 §14.1/3,我们必须假设该规则也适用于模板参数声明,因此类似于

template <typename X> template <typename X>
struct Outer<X>::Inner<X> {

};

即使在简单的基础上也是非法的,它意味着在同一范围内两次声明相同的 typedef-name。

但是,在这样的情况下

template <typename X>
struct Outer {

  template <typename X>
  struct Inner {
  };

};

有人可能会争辩说,第二个声明template <typename X>适用于嵌套范围。幸运的是,该标准确实提供了以下关于模板参数状态的第二条声明

(§14.6.1/6)不应在其范围内重新声明模板参数(包括嵌套范围)。模板参数的名称不得与模板名称相同。[例子:

template<class T, int i> class Y {
  int T; // error: template-parameter redeclared

  void f() {
    char T; // error: template-parameter redeclared
  }
};

template<class X> class X; // error: template-parameter redeclared

—结束示例]

如明确所述,适用于声明范围内的任何 typedef-name 的不重新声明规则也适用于嵌套范围以及模板参数的情况。

这是一个例子来激发我为什么认为该规则实际上也很有用。考虑:

template <typename T1>
struct Outer
{
  static const int outerID = 5;

  template <typename T2>
  struct Inner
  {
    int id1() { return Outer<T1>::outerID; }
    int id2() { return Outer::outerID; }
    int id3() { return outerID; }
  };
};

内部模板的三个函数都引用外部类的同一个静态成员,但是以三种不同的方式。id2()id3()这样做是因为 §14.6.2.1/4 要求Outer::outerID并被outerID解释为引用当前实例化,即Outer<T1>::outerID.

如果我们现在将内部模板的模板参数替换T1为 ,与外部模板相同,则 的含义id1()将会改变(因为Outer<T1>现在将引用内部模板中的任何定义T1),但是id2()并且id3()- 最自然 -仍然是指模板outerID所属的当前实例化。因此,可能会返回与andid1()不同的值,这将是最尴尬的。id2()id3()

第 2 部分:在成员模板的部分特化中,成员的模板参数是否可以用作封闭类的模板参数,反之亦然?

该问题解决的另一个问题是成员模板的专业化或类外定义,例如

template <typename A> template <typename B>
struct Outer<A>::Inner<B> {
  // ...
};

外部模板(即在这种情况下)的模板参数列表<A>可以使用为内部模板(即B在这种情况下)定义的参数,反之亦然。

让我们首先考虑问题中给出的特殊情况,其中两个参数相同:

template <typename A> template <typename A>
struct Outer<A>::Inner<A> {
  // ...
};

尽管由于重新声明问题,我们已经在第 1 部分中排除了这一点,但我们仍然可以考虑此语法的预期含义,即: 定义内部类模板的显式特化,其中模板参数假定为与外部模板相同。语法正确的写法是

template <typename A> template <>
struct Outer<A>::Inner<A> {
  // ...
};

即第二个参数列表将为空。不幸的是,这相当于成员模板的显式特化,这是非法的,除非封闭模板也被显式特化(第 14.7.3/16 节)。

但是,如果我们考虑具有两个或多个参数的成员模板的部分而不是显式特化,则声明变得合法:

// Primary declaration
template <typename A>
struct Outer {
  template <typename A, typename B>
  struct Inner {
    // ...
  };
};

// Partial specialization of member template
template <typename A> template <typename B>
struct Outer<A>::Inner<B,A> {
  // ...
};

我们现在也使用封闭模板的模板参数A作为内部模板的专用第二个参数。对外部类的模板参数以及内部类的第二个模板参数使用相同数据类型的模板的任何实例化,例如

Outer<int>::Inner<float,int> myvar;

将实例化上面定义的特化。

因此,在成员模板的模板参数列表中使用封闭类的模板参数是没有问题的,原因是在Inner<B,A>评估时,A已经具有在范围级别定义的 typedef-name 的状态Outer

但是反过来做,例如

template <typename A> template <typename B>
struct Outer<B>::Inner<B,A> {
  // ...
};

将不起作用,因为它只是范围B的 typedef-name Inner。该标准规定:

(§14.5.2/1)[...] 在其类模板定义之外定义的类模板的成员模板应指定类模板的模板参数,后跟成员的模板参数模板。[例子:

    template<class T> struct string {
      template<class T2> int compare(const T2&);
      template<class T2> string(const string<T2>& s) { /∗ ... ∗/ }
    };
    template<class T> template<class T2> int string<T>::compare(const T2& s) {
    }

—结束示例]

我将此解释为意味着两个模板参数列表(一个后跟另一个)是分开保存的,不被视为模板参数的一个组合列表。因此在

template <typename A> template <typename B>
struct Outer<B>::Inner<B,A>

的解释Outer<B>不能使用第二个模板参数列表,并且B将是未定义的,而在前一种情况下

template <typename A> template <typename B>
struct Outer<A>::Inner<B,A>

的解释Inner<B,A>是可能的,因为在属于的范围内声明A了 typedef-name 的状态,即它将被成功解释为,而这又与.InnerInner<B,Outer::A>Inner<B,Outer<A>::A>

于 2012-07-07T09:41:46.293 回答