Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
like image 805
Yuushi Avatar asked Jan 09 '23 20:01

Yuushi


2 Answers

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

like image 67
Nawaz Avatar answered Jan 12 '23 10:01

Nawaz


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.

like image 29
T.C. Avatar answered Jan 12 '23 08:01

T.C.