3

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.

CodeBricks
  • 1,771
  • 3
  • 17
  • 37
  • I don't "troll" ***anyone's*** questions, though I do troll some people's answers, as there are a handful of brilliant minds on this site, and you you can never stop learning. And if you posted it, was it not your intent in doing so to do the very thing you now-say no one (yourself included) asked? Namely, to *read it*? – WhozCraig Nov 27 '13 at 00:26
  • @WhozCraig Eh, it's long but there's a central thesis. The answers flow along as the questions do. – Potatoswatter Nov 27 '13 at 00:27
  • @Potatoswatter I concur, It is actually very well written, and methodically presented, reading almost as a step-by-step analysis and tutorial. I commend you on addressing every point, considering you were (apparently) not asked to do so. – WhozCraig Nov 27 '13 at 00:28
  • So, I could take back the one of only two up-votes this received? Had I the ability and in-depth knowledge required to answer your litany, I would have. I found it.. humorous.. the length and detail in which what was ultimately an overtone of a summary of name-lookup rules and regulations was presented, and if that is what I'm accused I can only I accept. That is reminded me of my college days when professors would say "I have only one question... and it as 27 sub-parts." is long-lost. Rather, I will simply accept the unappeasable nature of the situation and simply say good night. – WhozCraig Nov 27 '13 at 03:26

1 Answers1

9
  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.
    
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • +1 top-notch drill-down. I was actually surprised how weak some of the answers on this site were regarding name lookup rules. ADL is, of course, beat to death, but it seems non-ADL questions somewhat fall the wayside for some odd reason (either that or I'm just not searching for them well enough, always a possibility). – WhozCraig Nov 27 '13 at 00:33
  • @WhozCraig The surface of ADL is covered by this site pretty well, but I'm not aware of any resources (besides the standard and the ISO defect report registry) for learning the nuances of name lookup — and there are a lot. – Potatoswatter Nov 27 '13 at 00:35
  • Deep truth in that, to be sure. It also seems to be an interesting sticking point where compiler vendors, if they're going to screw something up, like to do it there. Sometimes i think the standard itself didn't bring enough clarity as the root cause of that, but it really is a difficult thing to make crystal-clear; harder than most think. – WhozCraig Nov 27 '13 at 00:37
  • Can you really have a member of `Base` named `Base`? Surely that is the name of the constructor... Presumably you could have a member of `Derived` named `Base`, though. (Oh, and +1) – rici Nov 27 '13 at 00:38
  • @rici The constructor declaration is a special grammatical case activated by using the injected-class-name. Such a data member would conflict with the typedef aspect of the injected-class-name. The `Derived` member case is what this question concerns. – Potatoswatter Nov 27 '13 at 00:40
  • 1
    @rici Constructors in C++ do not have names. – Captain Obvlious Nov 27 '13 at 00:47
  • @CaptainObvlious: I'll give you that, since it's a quote from the standard. The class has a name, and that name interferes with an attempt to use the same identifier to declare a member, since a declaration using the name of the class declares a constructor. Still, on a philosophical note, a name by any other rose... – rici Nov 27 '13 at 03:25