10

Suppose I have this variadic base class-template:

template <typename ... Types>
class Base
{
public:
    // The member foo() can only be called when its template 
    // parameter is contained within the Types ... pack.

    template <typename T>
    typename std::enable_if<Contains<T, Types ...>::value>::type
    foo() {
        std::cout << "Base::foo()\n";
    }
};

The foo() member can only be called when its template-parameter matches at least one of the parameters of Base (the implementation of Contains is listed at the bottom at this post):

Base<int, char>().foo<int>(); // fine
Base<int, char>().foo<void>(); // error

Now I define a derived class that inherits twice from Base, using non-overlapping sets of types:

struct Derived: public Base<int, char>,
                public Base<double, void>
{};

I was hoping that when calling e.g.

Derived().foo<int>();

the compiler would figure out which base-class to use, because it is SFINAE'd out of the one that does not contain int. However, both GCC 4.9 and Clang 3.5 complain about an ambiguous call.

My question then is two-fold:

  1. Why can't the compiler resolve this ambiguity (general interest)?
  2. What can I do to make this work, without having to write Derived().Base<int, char>::foo<int>();? EDIT: GuyGreer showed me that the call is disambiguated when I add two using-declarations. However, since I'm providing the base-class for the user to inherit from, this isn't an ideal solution. If at all possible, I don't want my users to have to add those declarations (which can be quite verbose and repetitive for large type-lists) to their derived classes.

Implementation of Contains:

template <typename T, typename ... Pack>
struct Contains;

template <typename T>
struct Contains<T>: public std::false_type
{};

template <typename T, typename ... Pack>
struct Contains<T, T, Pack ...>: public std::true_type
{};

template <typename T, typename U, typename ... Pack>
struct Contains<T, U, Pack ...>: public Contains<T, Pack...>
{};
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
JorenHeit
  • 3,877
  • 2
  • 22
  • 28
  • 1
    It's a manifestation of the general rule that names from different scopes do not overload in C++. – T.C. Feb 05 '15 at 16:25

2 Answers2

18

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks, I understand where the ambiguity comes from now. However, as I mentioned in the comment to GuyGreer's answer and in the edited question, this solution isn't very friendly to my users. I suppose that, if there's no easy workaround, I'll just have to document carefully. – JorenHeit Feb 05 '15 at 16:30
  • @JorenHeit If you're willing to introduce one more class in the hierarchy, one that collects a set of `Base` instantiations, and have the user inherit from that one, you can make it work. – jrok Feb 05 '15 at 16:33
  • @jrok But then I'd have to know the types that `Base` will be instantiated with, wouldn't I? – JorenHeit Feb 05 '15 at 16:34
  • @JorenHeit Provided a solution that does all the `using`s for you. – Barry Feb 05 '15 at 16:43
  • @Barry shouldn't you also bring in the `foo`s from `BaseCollector`? Also need something to stop the recursive inheritance. – T.C. Feb 05 '15 at 18:20
  • @T.C. Fixed typo on stopping recursive inheritance. I don't need to bring in `BaseCollector::foo` since there's just one subtype of `Derived`, so there can't be conflict be conflicting declaration sets. – Barry Feb 05 '15 at 18:25
  • @Barry no, I meant in your `BaseCollector ` specialization. http://coliru.stacked-crooked.com/a/a806eae2503ad800 – T.C. Feb 05 '15 at 18:26
  • @T.C. Oh hmm. I think you're right. This compiles on both gcc and clang, but now i'm not sure it should. – Barry Feb 05 '15 at 18:30
  • @Barry What you have does compile, but it only brings in the `foo` from the first base in the list. That `using` shadows any `foo` in `BaseCollector`. – T.C. Feb 05 '15 at 18:33
  • @T.C. Ohh! Picking `double` was just the wrong choice... `foo(0.0)` just called `Base::foo(int )`... – Barry Feb 05 '15 at 18:44
  • If you are gonna use this as a canonical dupe target (yay), could you add the binary-tree log-depth instantiation using solution? The linear template one sort of limits you to a short list of bases. – Yakk - Adam Nevraumont Sep 10 '18 at 17:02
3

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

SirGuy
  • 10,660
  • 2
  • 36
  • 66
  • Thats interesting... +1. However, if at all possible, I don't want my users to have to add those using-declarations to their code when inheriting from the baseclass I'm providing. Thanks nonetheless! – JorenHeit Feb 05 '15 at 16:10
  • 1
    @JorenHeit I'm not sure why it makes sense to derive from `Base` twice. Sure they're non-overlapping, but why not just derive from `Base` directly? – SirGuy Feb 05 '15 at 16:12
  • @GuyGreer I'm providing another class for the user, let's call it `Special`, which is itself derived from `Base` using a reserved type (hidden from the user, so it's guaranteed that the user won't use this type, hence non-overlapping). It would be nice if it's possible to derive from both `Special` and `Base`. – JorenHeit Feb 05 '15 at 16:18
  • @JorenHeit you can always provide a `SpecialAndBase` that derives from `Special` and `Base` and adds the proper `using`s... – T.C. Feb 05 '15 at 16:33
  • @T.C. Hm... that's not bad actually. And so obvious... Thanks! – JorenHeit Feb 05 '15 at 16:37