16

I'm trying to create a variadic templated class which provides a method for each class in the typelist. An example is shown below which creates a print method for every class in the typelist:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

The commented line results in an error from GCC 4.6.3 on the commented line. What is the correct way to resolve the ambiguity or should I be looking at a different design?

Peter Ogden
  • 710
  • 5
  • 10
  • Same issue with gcc 4.7.2 (at [liveworkspace](http://liveworkspace.org/code/1a4YN8$0)). Thanks for the copy/pastable example btw. – Matthieu M. Dec 13 '12 at 17:20
  • 1
    clang 3.2 trunk 165721 says: `error: member 'print' found in multiple base classes of different types` – user786653 Dec 13 '12 at 17:30
  • 2
    See also [this question](http://stackoverflow.com/questions/5368862/why-do-multiple-inherited-functions-with-same-name-but-different-signatures-not). This is in essence a problem of the combination of hiding rules and the very limited capabilities of unpacking variadic template arguments. – Mikael Persson Dec 13 '12 at 18:09
  • @MikaelPersson: Thank you very much for the link to an answer quoting the standard. Yet, it made clear that the issue is not linked to the unpacking of variadic template arguments (the question is using an explicit list of parent classes, and the issue is the same). – Ad N Nov 07 '13 at 11:29

3 Answers3

10

To resolve the ambiguity, it is possible to do

template <typename... Ts>
struct Printer : PrintHelper<Ts>...
{
    template <typename U>
    void print (const U& t)
    {
        PrintHelper<U>::print (t);
    }
};

(see an example )

but this is not quite as robust as one would hope. In particular, you cannot print an object which is convertible to one of the types from the type list.

With some template metaprogramming, it is possible to dispatch to the correct printer however. To do this you have to select a type from Ts... to which U is convertible, and call the right PrintHelper, ie.

PrintHelper<typename find_convertible<U, Ts...>::type>::print (t);

where find_convertible<U, Ts...> is defined by

template <typename U, typename... Ts>
struct find_convertible
{};

template <typename U, typename V, typename... Ts>
struct find_convertible<U, V, Ts...> :
    std::conditional<
        std::is_convertible<U, V>::value, 
        std::common_type<V>, // Aka identity
        find_convertible<U, Ts...>
    >::type
{};

(see example)

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • Thank you for the help. The first example is sufficient for my use case. Following on from one of the comments above there is another potential solution to allowing conversions by changing the inheritance (http://liveworkspace.org/code/2PpiGS$1). This will use the C++ overload resolution in constrast to your method which will take the first convertible type in the typelist. – Peter Ogden Dec 13 '12 at 20:58
  • @PeterOgden: I really like your other method better. Could you please post it as an answer for future reference ? Online pastebins don't last for ever. – Alexandre C. Dec 13 '12 at 21:04
  • Such love for the utility of this. – kanielc Jan 10 '14 at 00:14
8

I don't like answering my own question but I've created the following solution from the comments here. It brings all print functions into scope and allows for C++ overload resolution on all of the functions.

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer
{};

template<typename T>
class Printer<T> : public PrintHelper<T>
{
public:
    using PrintHelper<T>::print;
};

template<typename T, typename... Ts>
class Printer<T, Ts...>: public PrintHelper<T>, public Printer<Ts...>
{
public:
    using PrintHelper<T>::print;
    using Printer<Ts...>::print;
};

int main()
{
    Printer<int, std::string> p;
    p.print("Hello World"); // Not an ambiguous Call
}
Peter Ogden
  • 710
  • 5
  • 10
  • +1 That's great, why didn't I think of that... So I guess that is how you can unpack the using-declarations for variadic templates. – Mikael Persson Dec 13 '12 at 21:14
  • It's perfectly fine to answer your own question (and even explicitly encouraged). It is profitable for future readers. Cf. http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/ . Also the solution you propose should be the accepted one imho. – Alexandre C. Dec 16 '12 at 16:59
3

The following code can resolve the ambiguity problem:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
  protected:
    void print_impl(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{
  public:
    template <typename U>
    void print(const U& u) { 
      PrintHelper<U>::print_impl(u); 
    };
};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

Which is not very nice because of the requirement that the type U (deduced on call) is exactly one of the types in the variadic type list. You might be able to fancy things up a bit to solve that issue. With a bit of template magic and Sfinae, you can probably solve this fairly easily (but it is surely not as neat and clean).

The ambiguity problem is not related to the use of template or variadic templates, of course, it is a straight-forward application of the member lookup rules (Section 10.2/2 of the standard), i.e., the often-called "member hiding rules". If you take this simpler non-template version, you will get the same ambiguity problem, but with a very simple solution to it:

struct IntPrinter {
  void print(const int& i) { std::cout << i << std::endl; };
};

struct StringPrinter {
  void print(const std::string& s) { std::cout << s << std::endl; };
};

struct IntStringPrinter : IntPrinter, StringPrinter {
  using IntPrinter::print;       // These using-statements will solve the problem
  using StringPrinter::print;    // by importing all 'print' functions to the same 
                                 // overload resolution level.
};

So, the problem here is really that the ambiguity arises before the compiler even tries to apply normal overload-resolution rules, because it first tries to figure out which branch of the inheritance to follow to find the member function(s), and then, it will resolve the overload at one inheritance level only. And the problem when using variadic templates is that there doesn't seem to be any way to unpack a set of "using" statements to import all the print functions up to the same inheritance level. If anyone knows of a way to unpack such using-statements, I'm all ears. You might have to fall back to pre-variadic-template solutions (like a general template with 10 arguments, and specialize for all ten versions).

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52