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:
-std=c++14 -Wall -Wextra -Werror -pedantic-errors
-std=c++14 -Wall -Wextra -Werror -pedantic-errors
/EHsc /Za /std:c++14 /permissive-
-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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With