Injected Class Name Compiler Discrepancy

Injected class name compiler discrepancy

It appears that clang is wrong in this case. The relevant exception I was looking for is in [class.qual]/2:

2 In a lookup in which function names are not ignored and the nested-name-specifier nominates a class C:

  • (2.1)
    if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C, or

  • [...]


the name is instead considered to name the constructor of class C.

The standard has a near-equivalent (non-normative, obviously) example:

struct A { A(); };
struct B: public A { B(); };

A::A() { }
B::B() { }

B::A ba;// object of type A
A::A a;// error, A::A is not a type name
struct A::A a2;// object of type A

However, clang actually issues a correct diagnostic in this case:

error: qualified reference to 'A' is a constructor name rather than a type wherever a constructor can be declared

Perhaps clang interprets the line In a lookup in which function names are not ignored as In a lookup in which a constructor declaration is valid, but that doesn't seem to be a correct interpretation.

There is an existing bug for this in the clang bugzilla.

What happens when Injected-Class-Name occurs? (C++)

So what is really happening in the code? Is X* p turned into X::X* p?

Basically. The name lookup rules start in the narrowest scope. When you do X* p; in f is looks in f's scope and doesn't find anything. Then it checks X's scope since f is scoped to X. It finds X since it is injected into the class scope so it stops there and you get the class type.

When you do ::X* q; then ::X says look for X in the global namespace, and there is finds a variable, not a type so you get an error.

Why injected-class-name is sometimes not treated as a template name in a class template?

The relevant rule here is [temp.local]/4:

A lookup that finds an injected-class-name ([class.member.lookup]) 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 used as a template-name, 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 ]

Which I think means that this is a gcc and a clang bug. In:

using a = A<Base>;

All of the injected-class-names that are found do refer to specializations of the same class template (Base<T>) and the name is used as a template-name (because it's a template argument for a template template parameter), so this should just refer to the class template itself and not be ambiguous.

Ambiguous injected class name is not an error

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.

Injected-class-names of class templates

gcc is correct; your snippet is ill-formed!

// reduced testcase
template<class T>
class A { };

int main () {
A<float>::A<int> x; // ill-formed, bug in `clang` and `icc`
}

In the above reduced testcase we have a nested-name-specifier, A<float>::, followed by an unqualified-id A, which is then followed by some gibberish (<int>).

This is because the context in which the nested-name-specifier appears mandates that, during a look-up, function names are included (meaning that the constructor is found first, and the expression is ill-formed).


Relevant Bug Reports:

  • llvm.org/bugs/ - #8263; Incorrect constructor name resolution

How to circumvent the "problem"?

There are contexts in which member names that are looked up through a nested-name-specifier (that nominates a class) shall not include functions (hence, contexts where the constructor is not found), below are a few examples:

template<class T>
struct A {
typedef T value_type;
};
  struct A<float>::A<int>  x;     // ok, context: elaborate-type-specifier
typename A<float>::A<int> (); // ok, context: [expr.type.conv]p1
A<float>::A::value_type x; // ok, context: nested-name-specifier

struct X : A<float>::A<int> { }; // ok, context: base-specifier

What does the Standard say?

3.4.3.1p2 Class members [class.qual]

In a lookup in which function names are not ignored88 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
  • in a using-declaration (7.3.3) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the *nested-name-specicifier,

the name is instead considered to name the constructor of class C.


[ Note: ... ]


Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor or in a using-declaration.




88. Lookups in which function names are ignored include names appearing in a nested-name-specifier, an elaborated-type-specifier, or a base-specifier.

14.6.1p2 Locally declared names [temp.local]

Like normal (non-template) classes, class templates have an injected-class-name
(Clause 9). The injected-class-name can be used as a template-name or a
type-name.


When it is used with a template-argument-list, as a
template-argument for a template template-parameter, or as the final
identifier in the elaborated-type-specifier of a friend class template
declaration, it refers to the class template itself.


Otherwise, it is
equivalent to the template-name followed by the template-parameters of the
class template enclosed in <>.

Class Name Injection and Constructors

Clang-5 is very much correct. Over at [class.qual]/2:

In a lookup in which function names are not ignored 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
  • ...

the name is instead considered to name the constructor of class C.

As for the other part of the question. Yes, it's definitely worth it to submit bug reports. Standard compliance (or at least more diagnostics towards it) are to be encouraged IMO.

Where am I wrong here? The lookup for the name Node finds the injected-class-name for the struct Node

The first sentence of [basic.scope.pdecl]/7 makes clear that that paragraph deals with a class first declared in an elaborated-type-specifier; it is therefore not applicable to the struct Node in struct Node* Next;, because Node is not first declared in that elaborated-type-specifier.

Instead, the unqualified name lookup for Node does find the injected-class-name (see [basic.lookup.unqual]/p1 and 7.1) rather than ::Node. You'll need to write some rather contrived code for this to matter, though. For example:

struct A {};
struct B : private A {};
struct C : public B {
struct A m_a; // error: A is the inaccessible injected-class-name, not ::A
struct ::A m_a2; // OK
};

Note that GCC accepts this code, which I believe is a bug.

Program being compiled differently in 3 major C++ compilers. Which one is right?

GCC is correct, at least according to C++11 lookup rules. 3.4.3.1 [class.qual]/2 specifies that, if the nested name specifier is the same as the class name, it refers to the constructor not the injected class name. It gives examples:

B::A ba;           // object of type A
A::A a; // error, A::A is not a type name
struct A::A a2; // object of type A

It looks like MSVC misinterprets it as function-style cast expression creating a temporary C with y as a constructor parameter; and Clang misinterprets it as a declaration of a variable called y of type C.



Related Topics



Leave a reply



Submit