Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are redundant class name qualifiers allowed?

I came across some code like this:

struct A {
    A() {}
    A(int) {}
};

struct B : A {
    void init(int i);
};

void B::init(int i) {
    A::A(i); // what is this?
}

int main() {
    B b;
    b.init(2);
}

This compiled and ran using VC11 beta with no errors or warnings with /W4.

The apparent intent is for calling B::init to reinitialize the B's A base subobject. I believe it actually parses as a variable declaration for a new variable named i with type A. Compiling with clang produces diagnostics:

ConsoleApplication1.cpp:11:14: warning: declaration shadows a local variable
        A::A(i);
             ^
ConsoleApplication1.cpp:10:22: note: previous declaration is here
    void B::init(int i) {
                     ^
ConsoleApplication1.cpp:11:14: error: redefinition of 'i' with a different type
        A::A(i);
             ^
ConsoleApplication1.cpp:10:22: note: previous definition is here
    void B::init(int i) {
                     ^

It seems curious that the type can be referred to with the redundant class qualification.

Also, A::A(i) appears to be parsed differently by VS11 and clang/gcc. If I do A::A(b) clang and gcc create a variable b of type A using the default constructor. VS11 errors out on that saying b is an unknown identifier. VS11 appears to parse A::A(i) as the creation of a temporary A using the constructor A::A(int) with i as the parameter. When the redundant qualifier is eliminated VS parses the source as a variable declaration like clang and gcc do, and produces a similar error about shadowing the variable i.

This difference in parsing explains why VS11 will choke on more than a single extra qualifier; A::A::A::A(i), and why, given that clang and gcc can accept one extra qualifier, any number more than one extra has the same result as one extra.

Here's another example with the redundant qualifiers in a different context. All compiler seem to parse this as a temporary construction:

class Foo {};

void bar(Foo const &) {}

int main() {
    bar(Foo::Foo());
}
  1. Why are redundant qualifiers allowed at all?
  2. There are some contexts where constructors can be referred to, such as the syntax for inheriting constructors (class D : B { using B::B; };) but VS seems to be allowing it anywhere. Is VS wrong and are clang and gcc right in how redundant qualifiers are parsed?
  3. I know VS is still a fair bit behind in terms of standards compliance, but I do find it a bit surprising that modern, actively developed compilers could be so divergent, in this case resolving a redundant qualifier as the name of a constructor (even though constructors don't have names) vs. resolving redundant qualifiers simply to the type, resulting in VS constructing a temporary where the others declare a variable. It can be made even worse where B b(A::A(i)); is parsed by clang and gcc as the most vexing parse, but VS sees it as declaring a variable b of type B with an initializer. Are there still many differences this severe?
  4. Clearly, redundant qualifiers should be avoided in portable code. Is there a good way to prevent this construct from being used?
like image 846
bames53 Avatar asked Jul 10 '12 23:07

bames53


1 Answers

While the phenomenon can probably be attributed to class name injection, as noted in ephemient's answer, for this specific example it has been outlawed by C++ language quite a while ago.

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#147

The combination A::A is required to refer to class constructor, not to the class injected name. The A::A(i) is supposed to be interpreted by a compliant compiler as an illegal (and therefore meaningless) expression involving constructor name. Comeau compiler, for one example, will refuse to compile your code for that reason.

Apparently VC11 continues to treat A::A as a reference to the injected class name. Interestingly enough, I don't observe this problem in VS2005.

Back in the day when A::A was interpreted as referring to the injected name, one could declare an A object as

A::A::A::A::A::A a;

and so on, with arbitrary number of As. But not anymore. Surprisingly, version of GCC (4.3.4?) used by ideone still suffers from this issue

http://ideone.com/OkR0F

You can try this with your version of VC11 and see if it allows that.

like image 121
AnT Avatar answered Oct 07 '22 13:10

AnT