I refer to the following as “multiple re-inheritance”:
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:
base
and (i – 1)th,base
and (i – 2)th,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
struct improvised_container :
protected disambiguated_wrapper,
protected improvised_container {
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::num :
this->helper(index);
}
protected:
int& helper(const unsigned int index) {
return (index == N - 1) ?
this->disambiguated_wrapper::num :
this->improvised_container::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 {
unsigned int size() const { return 1; }
int& at(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper::num;
}
protected:
int& helper(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper::num;
}
};
// main.cpp
#include "improvised_container.h"
#include
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 0th improvised_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
.