What I read in the C++ standard about injected class names contradicts (as I see it) with the behavior of a sample program I will present shortly. Here's what I read:
From 3.4 (paragraph 3)
The injected-class-name of a class (clause 9) is also considered to be
a member of that class for the purposes of name hiding and lookup.
From 9 (paragraph 2)
A class-name is inserted into the scope in which it is declared
immediately after the class-name is seen. The class-name is also
inserted into the scope of the class itself; this is known as the
injected-class-name. For purposes of access checking, the
injected-class-name is treated as if it were a public member name.
From these I understand that the following is a well-formed translation unit and it compiles successfully.
#include <vector>
class X: std::vector<int>
{
vector mem;
};
However, I would suppose that the following should have produced an error, but it doesn't
#include <vector>
class X: std::vector<int>, std::vector<char>
{
vector mem; //compiles OK... mem is apparently std::vector<int>
};
Since the name vector is injected into both std::vector<int> and std::vector<char> as as if a public member name, then it should be inherited by X and therefore the name vector in X should be ambiguous. Am I missing something?
P.S. I am using MSVC9.0
I found it! It's right there in the standard! I was right! It should be ambiguous!
Clause 14.6.1 Paragraph
A lookup that finds an injected-class-name (10.2) can result in an
ambiguity in certain cases (for example, if it is found in more than
one base class). If all of the injected-class-names that are found
refer to specializations of the same class template, and if the name
is followed by a template-argument-list, the reference refers to the
class template itself and not a specialization thereof, and is not
ambiguous. [Example:
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
};
—end example]
Bottom line: This is yet another Microsoft compiler BUG. Disabling language extensions doesn't help either.
No, you are not missing anything, and your compiler seems to behave buggy. You can see how gcc handles it here: http://ideone.com/MI9gz
Its error message is:
prog.cpp:4:4: error: reference to 'vector' is ambiguous
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/bits/stl_vector.h:171:5: error: candidates are: class std::vector<char> std::vector<char>::vector
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/bits/stl_vector.h:171:5: error: class std::vector<int> std::vector<int>::vector
Related
When I compile the following snippet with g++
template<class T>
class A
{};
template<class T>
class B
{
public:
typedef A<T> A;
};
the compiler tells me
error: declaration of ‘typedef class A<T> B<T>::A’
error: changes meaning of ‘A’ from ‘class A<T>’
On the other hand, if I change the typedef to
typedef ::A<T> A;
everything compiles fine with g++. Clang++ 3.1 doesn't care either way.
Why is this happening? And is the second behavior standard?
g++ is correct and conforming to the standard. From [3.3.7/1]:
A name N used in a class S shall refer to the same declaration in its
context and when re-evaluated in the completed scope of S. No
diagnostic is required for a violation of this rule.
Before the typedef, A referred to the ::A, however by using the typedef, you now make A refer to the typedef which is prohibited. However, since no diagnostic is required, clang is also standard conforming.
jogojapan's comment explains the reason for this rule.
Take the following change to your code:
template<class T>
class A
{};
template<class T>
class B
{
public:
A a; // <-- What "A" is this referring to?
typedef A<T> A;
};
Because of how class scope works, A a; becomes ambiguous.
I will add to Jesse's answer about the seemingly peculiar behavior of GCC in compiling:
typedef A<T> A;
vs
typedef ::A<T> A;
This also applies to using statements as well of the form:
using A = A<T>;
using A = ::A<T>;
What seems to be happening within GCC, is that during the compilation of the typedef/using statement declaring B::A, that the symbol B::A becomes a valid candidate within the using statement itself. I.e. when saying using A = A<T>; or typedef A<T> A; GCC considers both ::A and B::A valid candidates for A<T>.
This seems odd behavior because as your question implies, you don't expect the new alias A to become a valid candidate within the typedef itself, but as Jesse's answer also says, anything declared within a class becomes visible to everything else inside the class - and in this case apparently even the declaration itself. This type of behavior may be implemented this way to permit recursive type definitions.
The solution as you found is to specify for GCC precisely which A you're referring to within the typedef and then it no longer complains.
The following code compiles and works on clang, but fails with "error: invalid use of non-static data member ‘Outer::a’" on gcc:
#include <functional>
#include <vector>
#include <assert.h>
#include <iostream>
#include <memory>
class Outer
{
public:
bool a = false;
virtual void f() = 0;
template <typename T>
class Inner : public T
{
public:
virtual void f() override
{
a = true; // Note: accessed through inheritance, not through outer scope
}
};
};
struct Foo : Outer { };
int main()
{
Outer::Inner<Foo> f;
f.f();
}
Adding "this->a" to the inner class makes it work on both compilers, but I'm wondering what's the correct behavior and what the standards says about this.
Interestingly the above code works with as part of a larger code base in VS2017 at work, but when I try it at home with VS2017 in isolation, it fails with the same error as GCC.
You can try compiling it here:
clang: https://rextester.com/SKAUEY50097
gcc: https://rextester.com/FLGL37556
This code is ill-formed no diagnostic required. So Gcc is right and friendly. And the absence of diagnostic for Clang and MSVC is just a compiler quality issue.
The rule of the standard involved is [temp.res]/8:
The validity of a template may be checked prior to any instantiation.
[ Note: Knowing which names are type names allows the syntax of every template to be checked in this way.
— end note
]
The program is ill-formed, no diagnostic required, if:
[..]
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or [...]
In f body, the unqualified id-expression a does not depend on any template parameter, so this id-expression should be resolved at the point of definition of the template without the knowledge of any template argument. And at this point, this expression is ill-formed.
Note: a non qualified id-expression, (out of a class member access) expression is supposed to be a member only if it names a member of that class or of a non-dependent base [temp.dep.type]/5:
A name is a member of the current instantiation if it is:
An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof.
This question already has an answer here:
Why are redundant scope qualifications supported by the compiler, and is it legal?
(1 answer)
Closed 6 years ago.
I noticed by accident that this code compiles and works correctly:
struct M { int some_int; };
static_assert(std::is_same<
decltype(M::M::M::M::some_int) /* <- this */,
int>::value, "Types must be int");
Why is this correct (decltype(M::M::M::M::some_int) <=> decltype(M::some_int))?
What other constructs one can use this pattern with class::class::...::member?
Compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23824.1 for x86
This is valid in all contexts, not just decltype. A class contains its own name as the injected class name. So within a class A::B::M, the name M is injected to refer to the class A::B::M. This means that you can then use M::M::M::some_member to refer to members of that class, if you really want to.
[Live example]
Note that when referring just to the class name itself (e.g. M::M::M), the situation is slightly different. If such a reference occurs in a place where a reference to a function could also potentially be correct, the syntax is taken to refer to the constructor instead. However, in type-only contexts, even such reference is valid. Example:
M::M::M m; // illegal, M::M interpreted as reference to constructor
struct D : public M::M::M // legal, a function could not be references here, so M::M::M means M
{};
This works because of the injected-class-name:
(N3337) [class]/2:A class-name is inserted into the scope in which it is declared immediately after the class-name is seen.
The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name.
For purposes of access checking, the injected-class-name is treated as if it were a public member name. [...]
So you can arbitrarily nest these, and they'll work with derived types as well:
struct A { using type = int; };
struct B : public A {};
using foo = B::B::B::A::A::A::type;
Note that in the case of A[::A]*::A, the injected-class-name can be considered to name the constructor instead:
[class.qual]/2: In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates
a class C:
— if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name
of C (Clause 9), or
— [...]
the name is instead considered to name the constructor of class C.
When I compile the following snippet with g++
template<class T>
class A
{};
template<class T>
class B
{
public:
typedef A<T> A;
};
the compiler tells me
error: declaration of ‘typedef class A<T> B<T>::A’
error: changes meaning of ‘A’ from ‘class A<T>’
On the other hand, if I change the typedef to
typedef ::A<T> A;
everything compiles fine with g++. Clang++ 3.1 doesn't care either way.
Why is this happening? And is the second behavior standard?
g++ is correct and conforming to the standard. From [3.3.7/1]:
A name N used in a class S shall refer to the same declaration in its
context and when re-evaluated in the completed scope of S. No
diagnostic is required for a violation of this rule.
Before the typedef, A referred to the ::A, however by using the typedef, you now make A refer to the typedef which is prohibited. However, since no diagnostic is required, clang is also standard conforming.
jogojapan's comment explains the reason for this rule.
Take the following change to your code:
template<class T>
class A
{};
template<class T>
class B
{
public:
A a; // <-- What "A" is this referring to?
typedef A<T> A;
};
Because of how class scope works, A a; becomes ambiguous.
I will add to Jesse's answer about the seemingly peculiar behavior of GCC in compiling:
typedef A<T> A;
vs
typedef ::A<T> A;
This also applies to using statements as well of the form:
using A = A<T>;
using A = ::A<T>;
What seems to be happening within GCC, is that during the compilation of the typedef/using statement declaring B::A, that the symbol B::A becomes a valid candidate within the using statement itself. I.e. when saying using A = A<T>; or typedef A<T> A; GCC considers both ::A and B::A valid candidates for A<T>.
This seems odd behavior because as your question implies, you don't expect the new alias A to become a valid candidate within the typedef itself, but as Jesse's answer also says, anything declared within a class becomes visible to everything else inside the class - and in this case apparently even the declaration itself. This type of behavior may be implemented this way to permit recursive type definitions.
The solution as you found is to specify for GCC precisely which A you're referring to within the typedef and then it no longer complains.
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;
}
The code gets compiled on g++(4.5.1) and Clang whereas Comeau and Intel C++ give (similar) errors.
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
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?
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).
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.
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.
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").
So until now, Clang and GCC are fine.
Why Comeau is right
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:
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.
However, a typename ... uses different lookup. 14.6/4 says
The usual qualified name lookup (3.4.3) is used to find the qualified-id even in the presence of typename.
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):
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.
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;
};
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;