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.


Though I can't tell you in detail why it doesn't work as is, I added using Base<int, char>::foo; and using Base<double, void>::foo; to Derived and it compiles fine now.

Tested with clang-3.4 and gcc-4.9