1

I want to create a pure virtual interface for every type in a variadic template. For example, a class:

overloads_interface<int,float,bool>

That defines the functions:

virtual void overload(const int& arg) = 0
virtual void overload(const float& arg) = 0
virtual void overload(const bool& arg) = 0

And if I add another type to the varaidic template, e.g

overloads_interface<int,float,bool, std::string>

It will automatically add the overload:

virtual void overload(const std::string& arg) = 0

Then, in order to instantiate this class, I must implement the functions, e.g:

class concrete : public overloads_interface<int,float,bool> {
public:
    virtual void overload(const int& arg) override { /**handle int**/ }
    virtual void overload(const float& arg) override { /**handle float**/ }
    virtual void overload(const bool& arg) override { /**handle bool**/ }
};

Here is my best attempt of creating this class:

template<typename... args>
class overloads_interface {

    using param_pack = std::tuple<args...>;

    static constexpr size_t num_args = sizeof... (args);

    template<size_t i = num_args - 1>
    struct crtp : public crtp<i - 1> {
        using arg = typename std::tuple_element<i, param_pack>::type;
        using super = crtp<i - 1>;

        using super::overload; // unhide overload from super class
        virtual void overload(const arg& arg) = 0;
    };

    template<>
    struct crtp<0> {
        using arg = typename std::tuple_element<0, param_pack>::type;
        virtual void overload(const arg& arg) = 0;
    };

};

But this fails to compile:

/main.cpp:21: error: explicit specialization in non-namespace scope ‘class overloads_interface<args>’
  template<>
           ^
/main.cpp:22: error: template parameters not deducible in partial specialization:
  struct crtp<0> {
         ^~~~~~~

And also has a nested class crtp that I don't really want.

How could I make this class?

Blue7
  • 1,750
  • 4
  • 31
  • 55

2 Answers2

8

Another way:

template <class T>
struct overloads_helper{
  virtual ~overloads_helper() = default;
  virtual void overload(const T&) = 0;
};

template <class... Args>
class overloads : public overloads_helper<Args>...{
};

Not sure which one is better, inheritance chain or multiple inheritance from interfaces...

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 2
    Damn it, wanted to do the same but a was a little bit too slow^^. I prefer this version since it seems clearer to me. One can also just hide the helper in an anonymous namespace, so it doesn't pollute the namespace. – n314159 Nov 27 '19 at 10:06
  • 1
    You get one vtbl pointer per base class and calls have to adjust the this pointer instead of only one vtbl and no adjustment in the single inheritance case. – AProgrammer Nov 27 '19 at 10:33
  • @AProgrammer I would think that Compiler can optimize the vtable lookups anyway, since you most probably won't access the inherited class through a `overloads`-pointer (and the functions are pure virtual anyway). It would maybe be also a good idea to add a `final` qualifier to the inherited class if applicable. More regarding this and optimizations, see [here](https://stackoverflow.com/questions/37414995/is-final-used-for-optimization-in-c) – n314159 Nov 27 '19 at 10:41
  • @AProgrammer I checked generated by g++ code. Even without optimization there is no difference in invocation of virtual methods. I think in case of multiple inheritance from pure interfaces there is no need for offsets, since base classes have no data and cannot be instantiated. – Konstantin Stupnik Nov 27 '19 at 10:45
  • @KonstantinStupnik [I see](https://godbolt.org/z/sPKu7-). What happens in this case is that gcc is compiling twice and putting entries in two vtables instead of adjusting offsets. (See the vtable for foo, and entries there for `foo::overload(long)`). – AProgrammer Nov 27 '19 at 12:58
4

I'm not sure why you're bothering with std::tuple at all, you can simply use partial specialization to unwind the pack:

template<typename... args>
struct overloads;

template<typename head, typename... args>
struct overloads<head, args...> : overloads<args...> {
    using overloads<args...>::overload; // unhide overload from super class
    virtual void overload(const head& arg) = 0;
};

template <typename head>
struct overloads<head> {
    virtual void overload(const head& arg) = 0;
};

See it live on Wandbox

Also note that your crtp is not actually a CRTP, that pattern is about classes of the form class A : Crtp<A> { };.

Quentin
  • 62,093
  • 7
  • 131
  • 191