From: Trevor Vaughan on

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! ]

From: Joe Smith on

"Trevor Vaughan" <clcppm-poster(a)this.is.invalid> wrote in message
news:op.u28embegxofjdi(a)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---



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

From: Trevor Vaughan on

Joe Smith <unknown_kev_cat(a)hotmail.com> wrote:

> 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.

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.

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

From: Joe Smith on

"Trevor Vaughan" <clcppm-poster(a)this.is.invalid> wrote in message
news:op.u3ae9s0ixofjdi(a)trevor-pc...
>
> Joe Smith <unknown_kev_cat(a)hotmail.com> wrote:
>
>> 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.
>
> 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++!

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'



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

From: David Vandevoorde on
On Nov 12, 1:35 pm, "Trevor Vaughan" <clcppm-pos...(a)this.is.invalid>
wrote:
> Joe Smith <unknown_kev_...(a)hotmail.com> wrote:
> > 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.
[...]
> 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


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