I refer to the following as “multiple re-inheritance”:
- inheriting a class once directly and one or more times indirectly by inheriting one or more of its descendants
- inheriting a class indirectly two or more times by inheriting two or more of its descendants
I want to know if it exists and how to unambiguously access embedded subobjects.
1.) [Professional C++, 2nd ed.]† states a compilable program can't have a class that directly inherits both its immediate parent and said parent's parent class. Is it true?
Given a GrandParent
and Parent
, which extends GrandParent
, VC12 and g++ allows a GrandChild
to directly inherit from both Parent
and GrandParent
. In VC12 and g++, it’s possible to define these classes as follows:
GrandParent
declares an int num
data member. Parent
declares its own num
in addition to inheriting GrandParent
's num
. GrandChild
declares its own num
in addition to inheriting Parent
's and GrandParent
's num
s.
VC12 seems to allow unambiguous member access across the board, but g++ only allows it for some cases.
#include <iostream>
using std::cout;
using std::endl;
struct GrandParent { int num; };
struct Parent : GrandParent { int num; };
struct GrandChild : GrandParent, Parent { int num; };
int main()
{
GrandChild gc;
gc.num = 2;
gc.Parent::num = 1;
gc.Parent::GrandParent::num = 0; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’
gc.GrandParent::num = 5; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’
// --VC12 output; g++ output--
cout << gc.num << endl; // 2 ; 2
cout << gc.Parent::num << endl; // 1 ; 1
cout << gc.Parent::GrandParent::num << endl; // 0 ; N/A due to above error
cout << gc.GrandParent::num << endl; // 5 ; N/A due to above error
}
2.) Why is (a) gc.Parent::GrandParent::num
ambiguous in g++ when (b) gc.Parent::num
isn't? (a) uniquely describes its location on the inheritance tree. gc
only has 1 Parent
subobject, which only has 1 GrandParent
subobject, which only has 1 num
. For (b), gc
has one Parent
, which has its own num
but also a GrandParent
subobject with another num
.
3.) For gc.GrandParent::num
, it seems VC12 looks into gc
's immediate GrandParent
base subobject for the latter's num
. I’m guessing the reason it is unambiguous is that it’s a name lookup qualified by gc
, so the entity to the right of .
is looked for first in gc
's scope, and the most immediate GrandParent
to gc
's scope is the directly inherited one, not the indirectly inherited one via Parent
. Am I wrong?
4.) Why is gc.GrandParent::num
ambiguous to g++ when gc.Parent::num
isn't? If one is ambiguous, then shouldn't both be equally ambiguous? For the prior, gc
has two GrandParent
s; and for the latter, Parent
has 2 num
s.
†Gregoire, Marc R. et al. Professional C++, 2nd ed. Indianapolis, IN: Wiley Pubishing, 2011. p. 241. Print.
The common term for this is the diamond pattern (or diamond problem).
It is not an error per se, but as noted in the comments here, any attempt to access a direct base that is reduplicated elsewhere in the hierarchy will result in an ambiguity error.
One workaround would be to make the base indirect. The new inheriting constructors feature in C++11 allows perfect wrappers:
template< typename base, typename tag >
struct disambiguated_base : base
{ using base::base; };
Given an unused tag type, this generates a new class derived from, and functionally identical to, the given base. The tag type may be an incomplete class denoted by an elaborated-type-specifier:
struct GrandChild : Parent,
disambiguated_base< GrandParent, class grandchild_grandparent_tag > {
typedef disambiguated_base< GrandParent, grandchild_grandparent_tag >
my_direct_grandparent;
int num;
};
Now GrandChild
can use my_direct_grandparent::
to disambiguate member accesses.
I’m adding to the accepted answer. It states a derived class cannot access a direct base
class if the derived class also indirectly inherits base
. Its solution makes the base
class indirect by wrapping it with a template whose second type argument is tag
. This ensures base
is indirect to the derived class provided the derived class extends the wrapped base with a unique tag
. The below example will use a non-type tag
.
If the diamond-like problem were generalized to contain more generations in the form of:
- ith class inherits from
base
and (i – 1)th, - (i – 1)th inherits from
base
and (i – 2)th, - …, and
- 2nd inherits from
base
,
then it's an improvised container, where each element is stored in each uniquely tagged base
. In that case, tag
-making should be automated. One way is to consolidate all derived classes via a non-type template. Its non-type parameter N
can specify the number of recursive inheritance iterations. By making tag
a non-type parameter, the value of the parameter determining the number of subclasses can be uniquely related to that of the one tagging each subobject type. For example, tag = 10
corresponds to N = 10
, which refers to the 10th generation on the hierarchy:
// disambiguated_wrapper.h
struct int_wrapper {
int num;
};
template < typename base, unsigned int tag >
struct disambiguated_wrapper : base {
using base::base;
};
// improvised_container.h
#include "disambiguated_wrapper.h"
template <unsigned int N>
struct improvised_container :
protected disambiguated_wrapper<int_wrapper, N>,
protected improvised_container<N - 1> {
unsigned int size() const { return N; }
int& at(const unsigned int index) {
if (index >= N) throw "out of range";
else return (index == N - 1) ?
this->disambiguated_wrapper<int_wrapper, N>::num :
this->helper(index);
}
protected:
int& helper(const unsigned int index) {
return (index == N - 1) ?
this->disambiguated_wrapper<int_wrapper, N>::num :
this->improvised_container<N - 1>::helper(index);
}
};
#include "specializations.h"
// specializations.h
template <>
struct improvised_container<0> {
improvised_container() = delete;
}; // ^ prohibits 0-length container
template <>
struct improvised_container<1> :
protected disambiguated_wrapper<int_wrapper, 1> {
unsigned int size() const { return 1; }
int& at(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper<int_wrapper, 1>::num;
}
protected:
int& helper(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper<int_wrapper, 1>::num;
}
};
// main.cpp
#include "improvised_container.h"
#include <iostream>
int main() {
improvised_container<10> my_container;
for (unsigned int i = 0; i < my_container.size(); ++i) {
my_container.at(i) = i;
std::cout << my_container.at(i) << ",";
} // ^ Output: "0,1,2,3,4,5,6,7,8,9,"
}
Element access at
cannot decrement the index
to recursively call itself, because index
isn’t a compile-time constant. But N
is. So, at
calls helper
, which recursively calls the (i – 1)th version of itself in the (i – 1)th subobject, decrementing N
until it equals index – 1
, with each call moving one scope deeper, and finally returning the element of the target scope. It checks against index – 1
and not index
, because the 0thimprovised_container
specialization's ctor is delete
d. at
compensates for the off-by-one.
improvised_container
uses protected
inheritance to prevent client code from accessing the at
and size
methods of its base subobjects. The subobject’s size is less than the enclosing object’s.
This works in g++ 4.8. The inheriting constructor using base::base
causes errors in VC12, but it can be omitted because the element type is int
.
来源:https://stackoverflow.com/questions/20693848/what-is-multiple-re-inheritance