views:

142

answers:

3

Compiling this code using g++ 4.2.1:

struct S { };
template<typename T> struct ST { };

template<typename BaseType>
class ref_count : private BaseType { };

template<typename RefCountType>
class rep_base : public RefCountType { };

class wrap_rep : public rep_base<ref_count<S> > {
  typedef rep_base<ref_count<S> > base_type;      // line 11
};

I get:

bug.cpp:1: error: ‘struct S’ is inaccessible
bug.cpp:11: error: within this context

However, if I change the wrap_rep class to use ST:

class wrap_rep : public rep_base<ref_count< ST<int> > > {
  typedef rep_base<ref_count< ST<int> > > base_type;
};

it compiles fine. Alternatively, if I change the original code to:

class wrap_rep : public rep_base<ref_count<S> > {
  typedef rep_base<ref_count< ::S > > base_type;  // now using ::
};

it also compiles fine. To me, the original code seems fine as-is. Is this a g++ bug? If not, then why does using a template work? And, for the other case, why is the ::S necessary?

A: 

The original code compiled fine in "Sun WorkShop 6 update 2 Compilers C++". This is the only one I have access to in my office. May be try on any other compiler you have.

Jagannath
+3  A: 

Your struct S is a base class of wrap_rep which means that it's injected into wrap_rep as if there was an anonymous typedef.

Using the operator :: before S in your typedef will tell your compiler not to use the S you inherit from, but S in the global namespace.

See this link.

Bertrand Marron
But then why does the code work with a template base class?
Paul J. Lucas
+6  A: 

Both of those codes are invalid (only the last one is valid), but your compiler (which is not conforming) only diagnoses one. As another answer says, this uses the injected class name. A class S is considered to have a member name S denoting that same class. For example (notice the "class" keyword before S::S in the first example is necessary to force a reference to the injected class name, instead of the default constructor):

class S { };

class S::S object; // creates an S object
class X : S::S::S::S { }; // derives from class S

Class templates also have an injected class name. Like the injected class name, it is inherited to derived classes, and thus ST<int> is ill-formed because it uses that injected class name, which however is not accessible. If you use GCC less 4.5, it may have something to do with a change introduced with GCC4.5:

G++ now implements DR 176. Previously G++ did not support using the injected-class-name of a template base class as a type name, and lookup of the name found the declaration of the template in the enclosing scope. Now lookup of the name finds the injected-class-name, which can be used either as a type or as a template, depending on whether or not the name is followed by a template argument list. As a result of this change, some code that was previously accepted may be ill-formed because

  1. The injected-class-name is not accessible because it's from a private base, or
  2. The injected-class-name cannot be used as an argument for a template template parameter.

In either of these cases, the code can be fixed by adding a nested-name-specifier to explicitly name the template. The first can be worked around with -fno-access-control; the second is only rejected with -pedantic.


To have a bit more fun with injected class names - notice that the injected class name is not equivalent to a typedef as one might think first. The injected class name is a class-name, but is not classified as a typedef-name, which means it can be hidden by function, object or enumerator names:

// valid, the data-member hides the injected class name
struct S { int S; };

To refer to the injected class name you can say class S::S (likewise, in a base-class list, non-type names are ignored thus you don't need special pre-cautions there), but a simple lookup to S::S will refer to the data-member.

Johannes Schaub - litb