Search code examples
c++inheritanceoverloadingc++17variadic-templates

Building a variadic template class with a set of function overloads based on the template arguments?


Motivation: I need to build a base class for a C++17 library that will have overloads for a virtual function based on types that the user identifies at compile time. Basically, when a particular overload of the function is called on the base class, I want to make sure the correct version is called in the derived class. My initial instinct was to build a virtual template function, but of course C++ cannot allow this because the compiler will not know what versions of it to put in the virtual function table. Since I will know all of the types at compile time, however, is it possible to make the base class itself a variadic template and use the template arguments to build the set of overloaded version of the virtual function that would be needed?

Fold expressions are right out because they can't be used to declare functions. Recursive templates seem promising, but I worry that having a long chain of inheritance for the base class will create a performance hit. Here's the working code that I have:

template <typename... Ts> class MyClass;

template <typename T, typename... Ts>
struct MyClass<T, Ts...> : public MyClass<Ts...> {
  using MyClass<Ts...>::MyFunction;
  virtual bool MyFunction(T in) { return true; }
};

template <>
struct MyClass<> {
  virtual bool MyFunction(...) { return false; }
};

Should this technique be sufficient? Or does anyone have other ideas on how I might accomplish this goal?

My other ideas include:

  • Cap the number of template arguments that can be handled and enable-if each overload based on whether the argument list is long enough to include it. Downside: this technique would be an arbitrary limit on the number of types.

  • Use a variadic macro to build the class. Downside: This technique would be confusing and inelegant.

  • Create a function to assign a type to an ID number in the base class, pass it to the derived class as a void * with its id, and translate it back at that point to make the appropriate overloaded call. Downside: this technique would be tricky to keep type safe and to minimize the amount of work the end-user needs to do.

Right now I'm leaning toward implementing the first of these alternatives and doing some performance testing to compare it to my working version, but would love if there is something cleaner that I'm missing.


Solution

  • Your implementation is right for C++14.

    C++17 allows variadic using, to avoid recursion:

    template <typename T>
    struct MyClassImpl
    {
        virtual ~MyClassImpl() = default;
        virtual bool MyFunction(T in) = 0; // or { return true; }
    };
    
    template <typename... Ts>
    struct MyClass : public MyClassImpl<Ts>...
    {
        using MyClassImpl<Ts>::MyFunction...;
    };
    

    Demo