Yuushi
Yuushi

Reputation: 26080

Using std::enable_if and variadic base classes

Here's a cut down example of what I'm trying to do:

#include <string>
#include <iostream>
#include <type_traits>

template <typename T>
class foo
{
public:
    template <typename U>
    typename std::enable_if<std::is_same<T, U>::value>::type
    bar(const U& t)
    {
        std::cout << t << "\n";
    }
};

template <typename... Args>
class baz
  : public foo<Args>...
{
    
};

int main()
{
    baz<double, std::string> b;
    b.bar(1.0);
}

This gives me ambiguous function errors:

error: request for member 'bar' is ambiguous

b.bar(1.0);

note: candidates are: template<class U> typename std::enable_if<std::is_same<T, U>::value>::type foo<T>::bar(const U&) [with U = U; T = std::basic_string<char>]

note: template<class U> typename std::enable_if<std::is_same<T, U>::value>::type foo<T>::bar(const U&) [with U = U; T = double]

My questions are twofold:

  1. Why is the inner template U not deduced? I'm supposing that it's due to ordering of template deduction and overload resolution, but can someone explain this?
  2. Is there another way of going about what I'm trying to do?

Upvotes: 4

Views: 393

Answers (2)

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361802

I think the error message is misleading. The problem is actually name bar is available in multiple base classes and you've not used using directive to bring the names you want into the derived class scope.

Here is one working solution:

template <typename X, typename... Args>
class baz : public foo<X>, public baz<Args...>
{
    public:
        using foo<X>::bar;        //bring the name from the first base
        using baz<Args...>::bar;  //bring the name from the second base
};

template <typename X>
class baz<X> : public foo<X>   //specialization for one argument
{
        //no using directive needed, as there is one base only!
};

Complete Demo

Upvotes: 5

T.C.
T.C.

Reputation: 137404

The problem has nothing to do with variadic templates, template argument deduction, or the like. It is that member functions of the same name from different base classes don't overload. Minimized example:

struct foo {
  void f(int &);
};

struct bar {
  void f(const int &);
};

struct foobar : foo, bar { };

int main(){
  foobar fb;
  int i;
  fb.f(i); // clang complains: error: member 'f' found in multiple base classes of different types
}

Since in your code, foo<double> and foo<std::string> are distinct types, and lookup for bar finds a declaration in each, your code is ill-formed.


A possible fix is to write a baz::bar that explicitly dispatches to the appropriate foo::bar:

template <typename... Args>
class baz
  : public foo<Args>...
{
  public:
    template <typename U>
    void
    bar(const U& t)
    {
        foo<U>::bar(t);
    }

};

You can SFINAE baz::bar on U being one of the types in Args, if desired.

Another possible solution is to use the recursive implementation shown in Nawaz's answer.

Upvotes: 4

Related Questions