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:
- Why can't the compiler resolve this ambiguity (general interest)?
- 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...>
{};
Here's a simpler example:
The reason for that comes from the merge rules [class.member.lookup]:
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 ofC
(Derived
) is empty. So to avoid it, we make it non-empty:That works because the rule for applying
using
isThere's no comment there about whether or not the members differ - we effectively just provide
Derived
with two overloads onfoo
, bypassing the member name lookup merge rules.Now,
Derived().foo(0)
unambiguously callsBase2<int>::foo(int )
.Alternatively to having a
using
for each base explicitly, you could write a collector to do them all:In C++17, you can pack expand
using
declarations also, which means that this can be simplified into: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;
andusing Base<double, void>::foo;
toDerived
and it compiles fine now.Tested with
clang-3.4
andgcc-4.9