【问题标题】:Name conflict between namespace and class template: different compiler behavior命名空间和类模板之间的名称冲突:不同的编译器行为
【发布时间】:2018-05-09 10:46:41
【问题描述】:

不同的编译器show different behavior编译如下代码:

namespace N
{
    namespace Foo
    {
        template <typename>
        struct Foo
        {
        };
    }
}

template <typename Type>
using Foo = N::Foo::Foo<Type>;

namespace N
{
    template <typename Type>
    struct Bar : Foo<Type>
    {
    };
}


int main()
{
}

经过测试的编译器及其编译标志:

  • clang++ 5.0.0:-std=c++14 -Wall -Wextra -Werror -pedantic-errors
  • g++ 7.2:-std=c++14 -Wall -Wextra -Werror -pedantic-errors
  • vc++ 19.10.25017(VS 2017):/EHsc /Za /std:c++14 /permissive-
  • icc 18.0.0:-std=c++14 -Wall -Werror

编译结果:

  • clang++:

    18 : <source>:18:15: error: expected class name
            struct Bar : Foo
                         ^
    
  • g++:编译成功

  • vc++:

    18 : <source>(18): error C2516: 'Foo': is not a legal base class
    13 : <source>(13): note: see declaration of 'Foo'
    20 : <source>(20): note: see reference to class template instantiation 'N::Bar<Type>' being compiled
    18 : <source>(18): error C2143: syntax error: missing ',' before '<'
    
  • icc:编译成功

哪种编译器行为符合标准?

附加信息:

【问题讨论】:

    标签: c++ templates namespaces c++14 language-lawyer


    【解决方案1】:

    规范说

    在查找基类名称期间,将忽略非类型名称 ([basic.scope.hiding])

    名称是Foo&lt;Type&gt;,它是一个类型名称。并且名称N::Foo 不是类型名称,因此必须忽略它。在某些名称被忽略的类似情况下,措辞更加明确

    如果嵌套名称说明符中的 ​::​ 范围解析运算符前面没有 decltype 说明符,则查找该名称之前的名称 ​::​ 仅考虑其特化为类型的名称空间、类型和模板

    这里,当它想要允许type-template&lt;arguments&gt; 时,它不仅说“类型名称” 或“非类型名称”。但它特别指出“特化为类型的模板”。我认为这种混乱是这里存在实现分歧的原因。名称Foo&lt;Type&gt; 是我所说的“复合名称”,因为它由其中的嵌套名称组成。所以可能不清楚哪些确切的名字应该被忽略,哪些不应该。

    【讨论】:

    • 更准确地说:clang++ 和 vc++ 在实现名称解析机制时存在错误,g++ 和icc 在这方面符合标准。我理解正确吗?
    • 同样的代码没有模板呢? clang++ 在这种情况下是否也有错误?
    • @Constructor 我不知道。标准在这里不是很清楚。我建议在标准讨论中发布投诉。没有模板,clang 显然有一个 bug。
    【解决方案2】:

    在查找Foo&lt;Type&gt; 之前查找名称Foo。 Foo 的查找不是基类名称的查找,因此 [class.derived]/2 中的查找规则不适用。

    参考[basic.lookup.unqual]/7:

    在成员函数体、默认参数、无异常说明符、非静态数据成员的大括号或等式初始化器或嵌套类定义之外的类 X 定义中使用的名称应在以下方式之一:

    • 在 X 类中使用之前或成为 X 的基类的成员 ([class.member.lookup]),或

    • 如果 X 是 Y 类的嵌套类,在 Y 中 X 的定义之前,或者应该是 Y 的基类的成员(此查找依次适用于 Y 的封闭类,从最里面的封闭类开始) ,26 或

    • 如果 X 是局部类或者是局部类的嵌套类,则在包含 X 定义的块中的 X 定义之前,或

    • 如果 X 是命名空间 N 的成员,或者是作为 N 成员的类的嵌套类,或者是局部类或作为 N 成员的函数的局部类中的嵌套类,在命名空间 N 或 N 的封闭命名空间之一中定义类 X 之前。

    [ 示例:

    namespace M {
      class B { };
    }
    namespace N {
      class Y : public M::B {
        class X {
          int a[i];
        };
      };
    }
    
    // The following scopes are searched for a declaration of i:
    
    // 1) scope of class N​::​Y​::​X, before the use of i
    // 2) scope of class N​::​Y, before the definition of N​::​Y​::​X
    // 3) scope of N​::​Y's base class M​::​B
    // 4) scope of namespace N, before the definition of N​::​Y
    // 5) global scope, before the definition of N
    

    — 结束示例 ]

    命名空间N在全局命名空间之前被考虑,所以N::foo首先被找到,导致程序格式错误。

    【讨论】:

      猜你喜欢
      • 2013-12-11
      • 2023-03-11
      • 2011-10-16
      • 2014-07-01
      • 2012-11-04
      • 1970-01-01
      • 2013-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多