Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Name conflict between namespace and class template: different compiler behavior

Different compilers show different behavior compiling the following code:

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()
{
}

Compilers tested and their compilation flags:

  • 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

Results of the compilation:

  • clang++:

    18 : <source>:18:15: error: expected class name
            struct Bar : Foo
                         ^
    
  • g++: successful compilation

  • 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: successful compilation

Which compiler behavior is standard compliant?

Additional information:

  • same code with anonymous namespaces
  • same code without templates
  • same code without templates and with anonymous namespaces
like image 946
Constructor Avatar asked Nov 25 '17 19:11

Constructor


2 Answers

The spec says

During the lookup for a base class name, non-type names are ignored ([basic.scope.hiding])

The name is Foo<Type>, it's a type name. And the name N::Foo is not a type name, so it must be ignored. In similar situations where certain names are ignored, the wording is more explicit though

If a ​::​ scope resolution operator in a nested-name-specifier is not preceded by a decltype-specifier, lookup of the name preceding that ​::​ considers only namespaces, types, and templates whose specializations are types

Here, it doesn't only say "type names" or "non-type names" when it wants to allow type-template<arguments>. But it specifically says "templates whose specializations are types". I think this confusion is the reason why there's implementation divergence here. The name Foo<Type> is what I would call a "composite name", because it consists of nested names inside of it. So it may be unclear which exact names in it are to be ignored and which not.

like image 87
Johannes Schaub - litb Avatar answered Sep 19 '22 06:09

Johannes Schaub - litb


The name Foo is looked up before the lookup of Foo<Type>. The lookup for Foo is not a lookup for a base class name, so the lookup rule in [class.derived]/2 does not apply.

Refer to [basic.lookup.unqual]/7:

A name used in the definition of a class X outside of a member function body, default argument, noexcept-specifier, brace-or-equal-initializer of a non-static data member, or nested class definition25 shall be declared in one of the following ways:

  • before its use in class X or be a member of a base class of X ([class.member.lookup]), or

  • if X is a nested class of class Y, before the definition of X in Y, or shall be a member of a base class of Y (this lookup applies in turn to Y's enclosing classes, starting with the innermost enclosing class),26 or

  • if X is a local class or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or

  • if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the definition of class X in namespace N or in one of N's enclosing namespaces.

[ Example:

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

— end example ]

Namespace N is considered before the global namespace, so N::foo is found firstly, which causes the program ill-formed.

like image 32
xskxzr Avatar answered Sep 22 '22 06:09

xskxzr