Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Virtual inheritance and typedef

182 views
Skip to first unread message

Trevor Vaughan

unread,
Nov 11, 2009, 8:00:36 PM11/11/09
to

Hi,

In the book _C++ Templates: The Complete Guide_ (2003), Vandevoorde
and Josuttis devote section 16.1 (pages 285-9) to a technique for
implementing "Named Template Arguments". The full code is too long to
quote, but can be accessed at the book's website:
http://www.josuttis.com/tmplbook/examples.html,
in the file "inherit/namedtmpl.cpp".

I have compiled this using gcc [g++-4 (GCC) 4.3.2 20080827 (beta) 2],
and confirmed that the code compiles without errors or warnings and
runs correctly.

At the heart of the code are the following class definitions:

// default policies
class DefaultPolicy1 {};
class DefaultPolicy2 {};
class DefaultPolicy3 {
public:
static void doPrint() {
std::cout << "DefaultPolicy3::doPrint()\n";
}
};
class DefaultPolicy4 {};

// define default policies as P1, P2, P3, P4
class DefaultPolicies {
public:
typedef DefaultPolicy1 P1;
typedef DefaultPolicy2 P2;
typedef DefaultPolicy3 P3;
typedef DefaultPolicy4 P4;
};

template <typename Policy>
class Policy3_is : virtual public DefaultPolicies {
public:
typedef Policy P3; // overriding typedef
};

In an attempt to understand how the code works, I tried removing the
'virtual' specifier from class Policy3_is, and this produced a long
compiler error, the gist of which was "error: no type named 'P3'".

A search on the Net (including the FAQ) yielded much information on
virtual inheritance -- the 'dreaded diamond', special constructor
syntax, object layout with pointers to the common base object -- but
all of this relates to class data members, and the above classes
contain no data, only typedefs.

So, can someone please explain how (and why) virtual inheritance
changes the semantics of classes when these classes contain no data
members (or even methods, for that matter), only typedefs? And why
this missing ingredient seems to be left out of all the standard
explanations of virtual inheritance?

Thanks a lot,
Trevor

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Joe Smith

unread,
Nov 12, 2009, 3:44:29 AM11/12/09
to

"Trevor Vaughan" <clcppm...@this.is.invalid> wrote in message
news:op.u28embegxofjdi@trevor-pc...

>
> Hi,
>
> In the book _C++ Templates: The Complete Guide_ (2003), Vandevoorde
> and Josuttis devote section 16.1 (pages 285-9) to a technique for
> implementing "Named Template Arguments". The full code is too long to
> quote, but can be accessed at the book's website:
> http://www.josuttis.com/tmplbook/examples.html,
> in the file "inherit/namedtmpl.cpp".
>
> [snip]

>
> In an attempt to understand how the code works, I tried removing the
> 'virtual' specifier from class Policy3_is, and this produced a long
> compiler error, the gist of which was "error: no type named 'P3'".
>

The following code (without virtual) compiles without any issue on Comeau
C++, with C++0x extentions DISabled.

---BEGIN---
#include <iostream>

// default policies
class DefaultPolicy1 {};
class DefaultPolicy2 {};
class DefaultPolicy3 {
public:
static void doPrint() {
std::cout << "DefaultPolicy3::doPrint()\n";
}
};
class DefaultPolicy4 {};

// define default policies as P1, P2, P3, P4
class DefaultPolicies {
public:
typedef DefaultPolicy1 P1;
typedef DefaultPolicy2 P2;
typedef DefaultPolicy3 P3;
typedef DefaultPolicy4 P4;
};

template <typename Policy>
class Policy3_is : public DefaultPolicies {


public:
typedef Policy P3; // overriding typedef
};

int main()
{
Policy3_is<int> a;
}

---END---

The problem is that you oversimplified your test case, and misinerpreted the
error message.

Here is there error message commeau C++ gives on the full version if I omit
the "virtual":

"ComeauTest.c", line 88: error: "PolicySelector<Setter1, Setter2, Setter3,
Setter4>::P3 [with Setter1=Policy3_is<CustomPolicy>,
Setter2=DefaultPolicyArgs, Setter3=DefaultPolicyArgs,
Setter4=DefaultPolicyArgs]" is ambiguous
Policies::P3::doPrint();
^
detected during instantiation of "void BreadSlicer<PolicySetter1,
PolicySetter2, PolicySetter3, PolicySetter4>::print()
[with PolicySetter1=Policy3_is<CustomPolicy>,
PolicySetter2=DefaultPolicyArgs,
PolicySetter3=DefaultPolicyArgs,
PolicySetter4=DefaultPolicyArgs]" at line 108


That states the problem pretty clearly. Your problem is that without the
Virtual,
in BreadSlicer::print, there are two types named P3 in scope. I'm not enough
of an expert to say why the virtual makes a difference, so I post below a
slight abridgment of the full code that still shows the issue.


DISCLAIMER: This is not my code, originating instead with _C++ Templates:
The Complete Guide_ (2003).

----BEGIN----
#include <iostream>


template <typename Base, int D>
class Discriminator : public Base {
};

template <typename Setter3, typename Setter4>
class PolicySelector : public Discriminator<Setter3,3>,
public Discriminator<Setter4,4> {


};
class DefaultPolicy3 {
public:
static void doPrint() {
std::cout << "DefaultPolicy3::doPrint()\n";
}
};
class DefaultPolicy4 {};

class DefaultPolicies {
public:


typedef DefaultPolicy3 P3;
typedef DefaultPolicy4 P4;
};

class DefaultPolicyArgs : virtual public DefaultPolicies {
};

template <typename Policy>
class Policy3_is : /*virtual*/ public DefaultPolicies {


public:
typedef Policy P3; // overriding typedef
};

template <typename PolicySetter3 = DefaultPolicyArgs,
typename PolicySetter4 = DefaultPolicyArgs>
class BreadSlicer {
typedef PolicySelector<PolicySetter3, PolicySetter4>
Policies;
public:
void print () {
Policies::P3::doPrint();
}
};
class CustomPolicy {
public:
static void doPrint() {
std::cout << "CustomPolicy::doPrint()\n";
}
};

int main()
{
BreadSlicer<> bc1;
bc1.print();

BreadSlicer<Policy3_is<CustomPolicy> > bc2;
bc2.print();
}
---END---

Trevor Vaughan

unread,
Nov 12, 2009, 1:35:17 PM11/12/09
to

Joe Smith <unknown...@hotmail.com> wrote:

Thanks a lot for clarifying the problem. For the record, here is the
compiler error I get from g++-4 when compiling your abridged version
of the full code:

../source/ComeauTest.cpp: In member function
'void BreadSlicer<PolicySetter3, PolicySetter4>::print()
[with PolicySetter3 = Policy3_is<CustomPolicy>,
PolicySetter4 = DefaultPolicyArgs]':
../source/ComeauTest.cpp:59: instantiated from here
../source/ComeauTest.cpp:42: error: no type named 'P3' in
'class PolicySelector<Policy3_is<CustomPolicy>, DefaultPolicyArgs>'

The diagnostic from the Comeau C++ compiler ("... P3 ... is ambiguous")
is certainly much more helpful than the "no type named 'P3'" I get
from g++!

But I hope someone will explain the role 'virtual' is playing here.

Joe Smith

unread,
Nov 12, 2009, 9:18:22 PM11/12/09
to

"Trevor Vaughan" <clcppm...@this.is.invalid> wrote in message
news:op.u3ae9s0ixofjdi@trevor-pc...

Yuck.

I'm reporting this as a bug in g++, since that error message is so
misleading it is not even funny. This is now gcc bug 42021:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42021


If it helps, I shrunk this down to a 32 line test case that does not use
templates.
This may make the class relations more clear.

The issue at stake is why both C1 and C2 must use virtual inheritance to
avoid error.

----BEGIN---
struct Policy1 {
static void doPrint() {}
};

struct Policy2 {
static void doPrint() {}
};

struct B {
typedef Policy1 P3;
};

struct C1 : /*virtual*/ B {
typedef Policy2 P3;
};

struct C2 : /*virtual*/ B {
};

struct PolicySelector: C1, C2 {
};

struct BreadSlicer {
typedef PolicySelector Policies;


void print () {
Policies::P3::doPrint();
}
};

int main()
{
BreadSlicer bc2;
bc2.print();
}
---END---


Here are the errors each compile gives under this.

Visual C++ v8:
>sourceFile.cpp(27) : error C2385: ambiguous access of 'P3'
> could be the 'P3' in base 'C1'
> or could be the 'P3' in base 'B'

(A suprisingly clear and helpful error message.)

Comeau/EDG:
>"sourceFile.cpp", line 27: error: "PolicySelector::P3" is ambiguous
> Policies::P3::doPrint();
> ^

G++:
>sourceFile.cpp: In member function `void BreadSlicer::print()':
>sourceFile.cpp:27: no type named `P3' in `struct PolicySelector'

David Vandevoorde

unread,
Nov 13, 2009, 3:42:51 PM11/13/09
to
On Nov 12, 1:35 pm, "Trevor Vaughan" <clcppm-pos...@this.is.invalid>
wrote:
[...]

> The diagnostic from the Comeau C++ compiler ("... P3 ... is ambiguous")
> is certainly much more helpful than the "no type named 'P3'" I get
> from g++!
>
> But I hope someone will explain the role 'virtual' is playing here.

Virtual inheritance ensures that there is only one base class
DefaultPolicies (in the original example; see also Figure 16.1 in the
book). When you write Policies::P3 in the BreadSlicer template, a
lookup is performed and finds two types P3: The one in Policy3_is
(found once) and the one in DefaultPolicies (found three times, via
Discriminator<..., 2>, Discriminator<..., 3>, and Discriminator<...,
4>). However, DefaultPolicies is always a base class of Policy3_is,
so the "domination rule" applies and the type from the more derived
class is selected (i.e., Policy3_is).

Now, if ordinary inheritance is used instead, we find the same types
again, but this time the DefaultPolicies in which we find P3 (three
times) are base classes of Policy3_is. So the dominance rule does not
apply and the compiler cannot choose between the two P3 types.

I hope that helps.

Daveed

David Vandevoorde

unread,
Nov 15, 2009, 12:33:22 AM11/15/09
to
On Nov 13, 3:42 pm, David Vandevoorde <daveed.vandevoo...@gmail.com>
wrote:
[...]

> Now, if ordinary inheritance is used instead, we find the same types
> again, but this time the DefaultPolicies in which we find P3 (three
> times) are base classes of Policy3_is.

Correction: are NOT base classes of Policy3_is. Hence...

> So the dominance rule does not
> apply and the compiler cannot choose between the two P3 types.

Daveed

Trevor Vaughan

unread,
Nov 16, 2009, 9:27:22 AM11/16/09
to

"David Vandevoorde" <daveed.va...@gmail.com> wrote:

> Virtual inheritance ensures that there is only one base class
> DefaultPolicies (in the original example; see also Figure 16.1 in
> the book). When you write Policies::P3 in the BreadSlicer template,
> a lookup is performed and finds two types P3: The one in Policy3_is
> (found once) and the one in DefaultPolicies (found three times, via
> Discriminator<..., 2>, Discriminator<..., 3>, and Discriminator
> <..., 4>). However, DefaultPolicies is always a base class of
> Policy3_is, so the "domination rule" applies and the type from the
> more derived class is selected (i.e., Policy3_is).
>

> Now, if ordinary inheritance is used instead, we find the same
> types again, but this time the DefaultPolicies in which we find P3

> (three times) are NOT base classes of Policy3_is. So the dominance


> rule does not apply and the compiler cannot choose between the two
> P3 types.

I think I get it now:

---BEGIN CODE---

#include <iostream>

struct A
{
typedef int T;
};

struct B1 : /*virtual*/ A
{
typedef double T;
};

struct B2 : /*virtual*/ A
{
};

struct C : B1, B2
{
};

int main()
{
C::T x = 42;
std::cout << "x = " << x << std::endl;

return 0;
}

---END CODE---

Output on g++-4:

(1) With B1 and B2 both inheriting virtually from A, the program
compiles OK and prints: x = 42

(2) With either occurrence of 'virtual' (or both) commented out, the
following compiler error is generated:

main.cpp: In function 'int main()':
main.cpp:23: error: reference to 'T' is ambiguous
main.cpp: 5: error: candidates are: typedef int A::T
main.cpp:10: error: typedef double B1::T
[etc.]

(This time the g++ error is actually helpful!)

As I now understand it...

Lookup of C::T in main() finds both B1::T and A::T (the latter via
B2). With non-virtual inheritance, B1::T hides its own inherited
A::T, but cannot hide B2's inherited A::T, so both B1::T and A::T are
visible and C::T is therefore ambiguous.

But if both B1 and B2 inherit A virtually, the domination rule
applies and the most-derived declaration (in this case, B1::T) hides
all the others, even where these others are reachable "along a path
through the sub-object lattice that does not pass through the hiding
declaration." (to quote the C++98 Standard, Section 10.2/6)

Is this explanation correct?

I had noted that the _C++ Templates_ book refers to this Section of
the Standard in relation to the domination rule (footnote 2 on page
289), but, having never seen the Standard, I didn't realise that this
"domination rule" refers to virtual inheritance.

Since my original post I have discovered a draft version (2 Dec 1996)
of (most of) the C++98 Standard on Bjarne Stroustrup's web site:
ftp://ftp.research.att.com/pub/c++std/WP/CD2. But without Daveed's
explanation I doubt that I would ever have fathomed what Section
10.2/6 is getting at. (Stroustrup also has a draft C++0x Standard at:
http://www.research.att.com/~bs/SC22-N-4411.pdf. The domination rule
is now a Note in Section 10.2/10-11.)

Thanks to Daveed Vandevoorde and Joe Smith for your help.

P.S.
The _C++ Templates_ footnote referenced above (p. 289) also mentions
that there is a discussion of this issue in Section 10.1.1 of the
ARM. As I don't have access to a copy of this book, I will be
grateful if anyone who does can provide a short summary of the ARM's
rationale for the dominance rule.

0 new messages