Disambiguate class-member in multiple inheritance
Here's a simpler example:
template <typename T>
class Base2 {
public:
void foo(T ) { }
};
struct Derived: public Base2<int>,
public Base2<double>
{};
int main()
{
Derived().foo(0); // error
}
The reason for that comes from the merge rules [class.member.lookup]:
Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is
initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi,
and merge each such lookup set S(f,Bi) in turn into S(f,C).
— [..]
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous...
Since our initial declaration set is empty (Derived
has no methods in it), we have to merge from all of our bases - but our bases have differing sets, so the merge fails. However, that rule explicitly only applies if the declaration set of C
(Derived
) is empty. So to avoid it, we make it non-empty:
struct Derived: public Base2<int>,
public Base2<double>
{
using Base2<int>::foo;
using Base2<double>::foo;
};
That works because the rule for applying using
is
In the declaration set, using-declarations are replaced by the set
of designated members that are not hidden or overridden by members of the derived class (7.3.3),
There's no comment there about whether or not the members differ - we effectively just provide Derived
with two overloads on foo
, bypassing the member name lookup merge rules.
Now, Derived().foo(0)
unambiguously calls Base2<int>::foo(int )
.
Alternatively to having a using
for each base explicitly, you could write a collector to do them all:
template <typename... Bases>
struct BaseCollector;
template <typename Base>
struct BaseCollector<Base> : Base
{
using Base::foo;
};
template <typename Base, typename... Bases>
struct BaseCollector<Base, Bases...> : Base, BaseCollector<Bases...>
{
using Base::foo;
using BaseCollector<Bases...>::foo;
};
struct Derived : BaseCollector<Base2<int>, Base2<std::string>>
{ };
int main() {
Derived().foo(0); // OK
Derived().foo(std::string("Hello")); // OK
}
In C++17, you can pack expand using
declarations also, which means that this can be simplified into:
template <typename... Bases>
struct BaseCollector : Bases...
{
using Bases::foo...;
};
This isn't just shorter to write, it's also more efficient to compile. Win-win.
When using multiple inheritance, why is this qualified name ambiguous?
The problem is that C++ has no way to directly express the concept of "multiple-level" class members, such as "the member x
of the Top
subobject of Left
". What Left::Top::x
means is "the member x
in the type denoted by Left::Top
" - and the type denoted by Left::Top
is exactly Top
.
This is why you can write odd things like
int Left::* ptr = &Right::Top::x;
because the right hand side of the =
is exactly equivalent to &Top::x
, and a pointer-to-base-class-member is implicitly convertible to a pointer-to-derived-class-member. (The result of this conversion still refers to the member in the base-class subobject of the derived class.)
To disambiguate, you can either do something along the lines of static_cast<Left &>(b).Top::x
or use a pointer-to-member - given int Left::* ptr = &Top::x;
, b.*ptr
will refer to the x
in the Top
subobject of the Left
subobject of b
.
Multiple inheritance from a pack expansion
Does it just inherit every type passed into the varargs?
Yes. It inherits publicly from each of the passed arguments. A simplified version is given below.
From Parameter pack's documentation:
Depending on where the expansion takes place, the resulting comma-separated list is a different kind of list: function parameter list, member initializer list, attribute list, etc. The following is the list of all allowed contexts:
- Base specifiers and member initializer lists:
A pack expansion may designate the list of base classes in a class declaration.
Example
struct Person
{
Person() = default;
Person(const Person&)
{
std::cout<<"copy constrcutor person called"<<std::endl;
}
};
struct Name
{
Name() = default;
Name(const Name&)
{
std::cout<<"copy constructor Name called"<<std::endl;
}
};
template<class... Mixins>
//---------------vvvvvvvvv---------->used as list of base classes from which X inherits publicly
class X : public Mixins...
{
public:
//-------------------------------vvvvvvvvvvvvvvvvv---->used as member initializer list
X(const Mixins&... mixins) : Mixins(mixins)... {}
};
int main()
{
Person p;
Name n;
X<Person, Name> x(p, n); //or even just X x(p, n); works with C++17 due to CTAD
return 0;
}
The output of the above program can be seen here:
copy constrcutor person called
copy constructor Name called
constructor called
Explanation
In the above code, the X
class template uses a pack expansion to take each of the supplied mixins and expand it into a public base class. In other words, we get a list of base classes from which X
inherits publicly. Moreover, we also have a X
constructor that copy-initializes each of the mixins from supplied constructor arguments.
C++ multiple inheritance private member ambigious access
In C++, name-lookup happens before member access checking is performed. Hence, name-lookup (Unqualified in your case) finds two names, and that's ambiguous.
You can use a qualified name to disambiguate:
int main() {
M m;
m.A1::x; //qualifed name-lookup
}
Ambiguous multiple inheritance of template classes
The lookup rules for member names say that your code is ambiguous, because the name is found in two base classes and therefore the lookup set is invalid. You don't need to be familiar with all the details of lookup sets and merging; the important detail is that both base classes are checked and the name add_listener
is found in both, which creates an ambiguity.
The easy fix is to bring those base class names into A
with using-declarations. This means that both versions of add_listener
are looked up in A
, rather than in the base classes, so there is no merge ambiguity:
struct A : public Notifier< TimeListener >
, public Notifier< SpaceListener >
{
using Notifier<TimeListener>::add_listener;
using Notifier<SpaceListener>::add_listener;
//plus any more base classes
};
Live Demo
Ambiguous inheritance of function when multiple inheritance of classes that themselves have diamond inheritance in their hierarchy
Because of the virtual inheritance, there is only one vtable for the base class Interface_Partial - once you use virtual inheritance, the "virtualness" infects all derived classes at all levels
The inheritance is ambigous because MyClass
has two different version of f() available - one from ClassA
and one from ClassB
. Because of the virtual inheritance of Interface_Partial
, you have two derived-class implementations that are at the same level and are trying to override the same virtual function. Declaring a virtual base class makes all derived classes share the virtual base class, including its vtable. The shared vtable gets updated to contain the pointer of the virtual function which should be called. But since there are two equally "good" ones to choose from, there is no way to pick one over the other.
In the other example you give, Interface_Partial
is a non-virtual base class for ClassX
and ClassY
, so each class is overriding a completely different virtual function. This is unambigous to the compiler, although when you call them, you have to specify which specific f()
you want to call.
You can resolve this by providing an implementation of f()
in MyClass
.
Related Topics
Statically Linking System Libraries, Libc, Pthreads, to Aid in Debugging
Find Argc and Argv from a Library
Boost Asio Single Threaded Performance
Opencv Videocapture Lag Due to the Capture Buffer
Convert Mat to Array/Vector in Opencv
What Is a Non-Trivial Constructor in C++
How to Evaluate Mathematical Expressions in C++
Borderless Window Using Areo Snap, Shadow, Minimize Animation, and Shake
Why Isn't Malloc Filling Up Memory
How to Get a List of Installed True Type Fonts on Linux Using C or C++
Range-For-Loops and Std::Vector<Bool>
Math-Like Chaining of the Comparison Operator - as In, "If ( (5<J<=1) )"