c++inheritancenamespacesscope-resolutiondot-operator

Dot or arrow operator vs. scope resolution operator for accessing base subobject


C++

Given a base class Base and a derived class Derived, the first thing constructed by Derived’s constructor is the Base subobject. Since it’s called a subobject, I assumed it can be accessed from client code like any other member object by using the dot operator on the Derived object. I also assumed it can be accessed from Derived’s implementation code by this->Base. A statement comprised entirely of the name of an object that has already been initialized followed by a semicolon should compile but also have no effect. Following that logic, given a Derived object myderived, I tried: myderived.Base; in client code and this->Base; in Derived’s implementation and neither statement compiles.

Why? I know Base, by itself, is the name of the Base class and not of a Base object. But I thought Base qualified by the myderived. (client code) or this-> (implementation code) prefix refers to the base subobject because Base, without any prefix qualifications, is the way the Base subobject is referred to in Derived’s constructor initializer. Refer to the below code, which (commented-out code aside) works in VC12 and g++ 4.8. Derived extends Base and Derived’s definition declares a Base data member membase, so my Derived object should contain two Base objects. Assuming the successful compilation isn’t the result of any compiler-Standard-nonconformity, the console output (in the comments), which shows different values for the int members n for the two different Base objects, implies that in Derived’s ctor initializer, Base refers to the inherited Base subobject whereas membase refers to the declared data member object. In Derived’s ctor initializer, Base refers specifically to the inherited subobject, not just any Base object nor the Base class.

#include <iostream>

struct Base {
    Base(int par) : n(par) {}
    void foo() { std::cout << "Base: " << n << std::endl; }
    int n;
};

struct Derived : Base {
    Derived() : Base(2), membase(3) {}
    Base membase;
    void foo() { std::cout << "Derived: " << n << std::endl; }

    // void g() { this->Base; } // Error in VC12 & g++ 4.8
    // ^ VC12: error C2273: 'function-style cast' : illegal as
    // right side of '->' operator
};

int main() {
    Derived myderived;

    // myderived.Base; //Error in VC12 & g++ 4.8
    // ^ VC12: error C2274: 'function-style cast' : illegal as
    // right side of '.' operator

    myderived.foo();           // OUTPUT: "Derived: 2"
    myderived.Base::foo();     // OUTPUT: "Base: 2"
    myderived.membase.foo();   // OUTPUT: "Base: 3"
}
  1. Again, shouldn’t myderived.Base; or this->Base; uniquely refer to the inherited Base subobject and compile?

  2. Does the Base in myderived.Base or this->Base refer to the Base subobject or the Base class or anything at all?

  3. In general, are inherited base subobjects considered data members of derived classes?

  4. From the perspective of Derived, does Base only refer to the inherited subobject within the context of Derived’s constructor initializer and only refer to the Base class outside Derived’s ctor initializer?

  5. How can I access the inherited Base subobject through the Derived object, as in, how can I express “the inherited Base subobject of the Derived object” in Derived’s implementation code and in client code?

  6. The use of the scope resolution operator in myderived.Base::foo(), where foo() is a method of Base, compiles in VC12 and g++ 4.8. Does this mean Base is a data member of myderived, since it is qualified by myderived and the dot operator? If so, then is that Base the Base class or the Base subobject?

  7. But myderived.Base.foo() doesn’t compile. AFAIK access of an object’s member is qualified in client code by the object name and dot operator. Two kinds of things that are qualified by the scope resolution operator, instead of the object name and dot operator, are (a) outside access to anything that belongs to a namespace and (b) the names of static data members and names of member functions of definitions defined outside their class definition, in which case the Base that precedes the :: refers to the Base class, not any Base instance. Does this mean the Base in myderived.Base is a namespace or refers to the class?

  8. If so, then is its being a namespace or referring to the class conditional upon whether it is appended by a :: followed by a member of Base?

  9. If the answer to #7 is yes, then why? It would seem incongruous with the following logic: a namespace’s enclosure of one variable does not in itself enable the namespace to enclose or construct other instances of the variable’s type. The namespace only owns one instance of that type--the variable it encloses. The same goes for a member that is part of a class, as in, static data member. The class only owns one instance of that type--the static data member it encloses. In contrast, there are as many same-named non-static data members of a class as there are instances of the class.

  10. Given method h() of Base and a Derived object myderived, myderived.Base::h(); compiles in VC12 and g++ 4.8. Addionally, g++ 4.8 can take any arbitrary number of extra Base::s in that statement, like myderived.Base::Base::h();. Such a statement seems to imply Base is a member of Base. But VC12 gives error C3083: '{ctor}': the symbol to the left of a '::' must be a type. But given Base object mybase, VC12 compiles mybase.Base::h(); just fine, which would also imply VC12 is fine with treating a class as a member of itself. But that contradicts its inability to compile the prior statement. Also, VC12 cannot compile any version of mybase.Base::h(); that has any number of extra Base::s (e.g. mybase.Base::Base::h()), but g++ can. Which compiler is correct, if any?

  11. In any case, does that mean a namespace or class can contain itself? Given a global int variable x, the statement ::::x; (with two scope resolution operators) doesn’t compile in either compiler, so I’m assuming the global scope doesn’t contain the global scope.


Solution

    1. No, you can have a member named Base which is separate from the Base subobject. The :: punctuator limits name resolution to ignore a member object name.
    2. See #1. Usually the answer is no because you would be crazy to have a member alias a base on purpose. But, it can happen with templates where a class may be unaware of the names of its bases.
    3. No. Member subobjects and base subobjects are both subobjects, but they are accessed differently.
    4. It always refers to the class, and the name Base itself is inherited from Base. If you have a crazy member alias, then you need to use some other reference to Base such as a namespace-qualified id.
    5. static_cast< Base & >( derived_obj ).
    6. No, :: has higher precedence than . so the Base::foo member is looked up inside myderived and then the function call operator is applied. However parens are not allowed around (Base::foo) because :: is not an operator generating a subexpression; this is why I prefer to call it a punctuator.
    7. See #6. myderived.Base is not anything by itself because Base groups with :: before ..
    8. Right. Note that classes aren't namespaces; they are different things which both happen to work with the same scope notation.
    9. This seems to be explaining C++ in terms that might apply to another language. In Java for example a class is sort-of an object with own data members. In C++ static class members and namespace members are completely separate objects which may be defined anywhere.
    10. Base::Base::Base:: works because a class' name is injected into itself as if it were a member typedef. VC is probably making an error and interpreting that as a reference to the constructor. Per the spec, the special typedef (called an injected-class-name) refers to the constructor under special circumstances, but before the scope operator is not such a case.
    11. Every class contains an implicit typedef to itself. Again, namespaces and classes are completely different things.

      The prefix :: is not itself the name of the global namespace but just a special case in the grammar to compensate for its lack of a name. Likewise, for better or worse, you cannot declare

      namespace global = :: ; // error: :: alone does not name anything.