g++/Clang 中的另一个错误?[C++ 模板很有趣]

2021-12-13 00:00:00 templates c++

看看下面的代码(只是为了好玩)

Check out the following code (written just for fun)

namespace N
{
   template<typename T>
   struct K
   {

   };
}
template<typename T>
struct X
{
   typename T::template K<T> *p; //should give error 
                                 //N::K<int> has no template member named `K`
};

int main()
{
   X<N::K<int> > l;
}

代码在 g++(4.5.1) 和 Clang 上编译,而 Comeau 和 Intel C++ 给出(类似)错误.

The code gets compiled on g++(4.5.1) and Clang whereas Comeau and Intel C++ give (similar) errors.

我在 Comeau 上遇到的错误是:

The errors that I get on Comeau are :

"ComeauTest.c", line 13: error: class "N::K<int>" has no member "K"
     typename T::template K<T> *p;
                          ^
          detected during instantiation of class "X<T> [with T=N::K<int>]" at
                    line 18

"ComeauTest.c", line 13: error: expected an identifier
     typename T::template K<T> *p;
                           ^
          detected during instantiation of class "X<T> [with T=N::K<int>]" at
                    line 18

所以我的问题是代码示例格式错误吗?"据我说是".这是否意味着这是 g++/Clang 中的另一个错误?

So my question is "Is the code sample ill-formed ?" According to me "Yes". Does that mean this is yet another bug in g++/Clang?

推荐答案

为什么 GCC 和 Clang 认为他们是对的

K,即注入的类名,在K的范围内具有双重性质.您可以在没有模板参数的情况下使用它.然后它引用 K(指向它自己的类型).

Why GCC and Clang think they are right

K, which is the injected class name, has a dual nature in the scope of K<int>. You can use it without template arguments. Then it refers to K<int> (to its own type).

它后面也可以跟一个模板参数列表.IMO 可以合理地说您需要使用 template 作为前缀,因为解析器与后面的 < 有歧义.然后它引用由模板参数确定的指定类型.

It can be followed by a template argument list too. IMO it's reasonable to say that you need to prefix it with template because of the parser ambiguity with the < that follows. It then refers to the specified type that's determined by the template arguments.

因此可以将其视为成员模板和嵌套类型,具体取决于它后面是否跟有模板参数列表.当然,K 并不是真正的成员模板.尽管如此,注入的类名的双重性质在我看来更像是一种黑客攻击.

So it can be treated as a member template and as a nested type, depending on whether it's followed by a template argument list. Of course, K is not really a member template. The dual nature of the injected class name seems to me more of a hack anyway, though.

标准有一个这样的例子:

The Standard has an example that reads like this:

template <class T> struct Base { };
template <class T> struct Derived: Base<int>, Base<char> {
   typename Derived::Base b; // error: ambiguous
   typename Derived::Base<double> d; // OK
};

人们可能倾向于由此得出结论,目的是您可以放弃模板.标准说

One might be inclined to conclude from this that the intent is that you could leave off the template. The Standard says

对于要由模板参数显式限定的模板名称,必须知道该名称以引用模板.

For a template-name to be explicitly qualified by the template arguments, the name must be known to refer to a template.

我看不出这如何不适用于 T::K.如果 T 是一个依赖类型,那么你可以向后靠,因为在解析它时你不知道 K 指的是什么,所以为了理解代码,你只需要能够以 template 为前缀.请注意,n3225 也有这个例子,但它不是一个缺陷:如果你在 C++0x 中查找模板自己的范围(它被称为当前实例化"),你可以正式放弃 template.

I can't see how this wouldn't apply to T::K<T>. If T is a dependent type then you can just lean back because you can't know what K refers to when parsing it, so to make any sense of the code, you just have to be able to prefix it with template. Notice that n3225 has that example too, but it's not a defect there: You can officially leave off template if you lookup into the template's own scope in C++0x (it's called the "current instantiation").

所以到目前为止,Clang 和 GCC 都很好.

So until now, Clang and GCC are fine.

为了让它更复杂,我们将不得不考虑 K 的构造函数.隐式声明了一个默认构造函数和一个复制构造函数.名称 K::K 将引用 K 的构造函数 除非 使用的名称查找将忽略函数(构造函数)名称.typename T::K 会忽略函数名吗?3.4.4/3 说明了详细的类型说明符,其中 typename ... 是其中之一:

Just to make it even more complicated, we will have to consider the constructors of K<int>. There is a default constructor and a copy constructor implicitly declared. A name K<int>::K will refer to the constructor(s) of K<int> unless the name lookup used will ignore function (constructor) names. Will typename T::K ignore function names? 3.4.4/3 says about elaborated type specifiers, which typename ... is one of:

如果名称是qualified-id,则根据其限定条件查找名称,如3.4.3 所述,但忽略任何已声明的非类型名称.

If the name is a qualified-id, the name is looked up according its qualifications, as described in 3.4.3, but ignoring any non-type names that have been declared.

然而,typename ... 使用不同的查找.14.6/4 说

However, a typename ... uses different lookup. 14.6/4 says

通常的限定名称查找 (3.4.3) 用于查找限定 ID,即使存在 typename 也是如此.

The usual qualified name lookup (3.4.3) is used to find the qualified-id even in the presence of typename.

3.4.3 的通常限定查找不会忽略非类型名称,如 14.6/4 所附示例所示.因此,我们将找到 3.4.3.1/1a 中指定的构造函数(仅在 not 忽略非类型时才会发生的附加扭曲是由后来的缺陷报告添加的,所有流行的 C++03 编译器虽然实现):

The usual qualified lookup of 3.4.3 won't ignore non-type names, as illustrated by the example attached to 14.6/4. So, we will find the constructor(s) as specified by 3.4.3.1/1a (the additional twist that this only happens when non-types are not ignored was added by a later defect report, which all popular C++03 compilers implement though):

如果嵌套名称说明符指定一个类 C,并且在嵌套名称说明符后面指定的名称在 C 中查找时是 C 的注入类名称(第 9 条),则名称为而是考虑命名类 C 的构造函数.这样的构造函数名称只能在出现在类定义之外的构造函数定义的声明符中使用.

If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9), the name is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of a constructor definition that appears outside of the class definition.

所以最后,我认为 comeau 的诊断是正确的,因为您尝试将模板参数列表放在非模板上,并且还违反了最后一部分中引用的要求(您在其他地方使用了该名称).

So in the end, I think comeau is correct to diagnose this, because you try to put a template argument list onto a non-template and also violate the requirement quoted in the last part (you use the name elsewhere).

让我们通过派生类访问注入的名称来更改它,这样就不会发生构造函数名称转换,并且您真的访问了类型,以便您真的 可以附加模板参数:

Let's change it by accessing the injected name by a derived class, so no constructor name translation occurs, and you really access the type so that you really can append the template arguments:

// just replace struct X with this:
template<typename T>
struct X
{
   struct Derived : T { };
   typename Derived::template K<T> *p;
};

现在所有东西都可以用 comeau 编译了!请注意,我已经向 clang 做了关于这件事的问题报告.请参阅错误的构造函数名称解析.顺便说一句,如果你在 K 中声明了一个默认构造函数,如果你使用 T::K

Everything compiles now with comeau too! Notice I already did problem report to clang about this exact thing. See Incorrect constructor name resolution. BTW, if you declare a default constructor in K, you can see comeau give a better error message if you use T::K<int>

"ComeauTest.c", line 13: error: overloaded function "N::K<T>::K [with T=int]" is
          not a template
     typename T::template K<T> *p;

相关文章