6

In C++ is it possible to define multiple methods based of the number of template parameters provided? Similar to how variadic functions work?

With functions I can do

template <class ...Args>
struct VariadicFunctionCallback {
    typedef std::function<void(std::shared_ptr<Args>...)> variadic;
};

But what I want to know is if I could do something similar but to create multiple functions instead of multiple arguments

template <class ...FunctionArg>
class Example {
     void Function(FunctionArg)...
}

Which would then allow me to do something like

template <>
class Example<int, float> {
    void Function(int i) {
        ...
    }

    void Function(float f) {
        ...
    }
}

And if this is possible what are the advantages over my current setup which is like

template<class EventType>
class EventHandler {
public:
    void HandleEvent(const std::shared_ptr<EventType>& event) {
    }
};

class ExampleEvent : public Event<ExampleEvent> {

};

class ExampleHandler : public EventHandler<ExampleHandler>, EventHandler<Events::ShutdownEvent> {
public:
    void HandleEvent(const std::shared_ptr<ExampleEvent> &event);

    void HandleEvent(const std::shared_ptr<Events::ShutdownEvent> &event);
};

--Edit-- I ended up with a mix if the two solutions. It is probably not the best and I will continue to play around with and improve it overtime.

template <class EventType>
class BaseEventHandler {
public:
    EventIdentifier GetIdentifier() const {
        return EventType::GetIdentifier();
    }

    virtual void HandleEvent(const std::shared_ptr<EventType> &event) = 0;
};

template<class EventType, class ...EventTypes>
class EventHandler: public BaseEventHandler<EventTypes>... {

};

Which then allows me to do

class EventListener: public EventHandler<ShutdownEvent, MousePosEvent, WindowCloseRequestEvent> {
    void HandleEvent(const std::shared_ptr<ShutdownEvent> &event);
    void HandleEvent(const std::shared_ptr<MousePosEvent> &event);
    void HandleEvent(const std::shared_ptr<WindowCloseRequestEvent> &event);
}
Cethric
  • 101
  • 1
  • 9

2 Answers2

8

I suppose you can make Example a sort of recursive self-inheritancing class; something as

    template <typename ...>
    struct Example
     {
       // dummy Function() to end the recursion
       void Function ()
        { }
     };

    template <typename T0, typename ... Ts>
    struct Example<T0, Ts...> : public Example<Ts...>
     {
       using Example<Ts...>::Function;

       void Function (T0 const &)
        { };
     };

So you can write

int main ()
 {
   Example<int, long, float>  e0;

   e0.Function(0);
   e0.Function(0L);
   e0.Function(0.0f);
 }

-- EDIT --

The OP ask

Could a specialisation then be preformed on top of this?

Do you mean something as follows?

template <typename ...>
struct Example
 {
   // dummy Function() to end the recursion
   void Function ()
    { }
 };

template <typename T0, typename ... Ts>
struct Example<T0, Ts...> : public Example<Ts...>
 {
   using Example<Ts...>::Function;

   void Function (T0 const &)
    { };
 };

template <typename ... Ts>
struct Example<float, Ts...> : public Example<Ts...>
 {
   void FunctionFloat (float const &)
    { };
 };

int main ()
 {
   Example<int, long, float>  e0;

   e0.Function(0);
   e0.Function(0L);
   e0.FunctionFloat(0.0f);
   //e0.Function(0.0f); // compilation error
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Could a specialisation then be preformed on top of this? – Cethric Apr 29 '18 at 02:45
  • Ie ` template<> struct Example: public Example { void Function (int i) {} void Function (long l) {} void Function (float f) {} } ` – Cethric Apr 29 '18 at 02:48
  • Is `using Example::Function;` needed? As far as I can tell public base functions can be accessed without namespace. – Kostas Apr 29 '18 at 02:50
  • @Cethric - yes; but I suggest something as in modified answer – max66 Apr 29 '18 at 02:51
  • @GillBates - Well... `Example::Function` isn't exactly an access through namespace. Yes: it's needed; try to comment it and you'll see some errors. You're right: usually, public base function can be accessed without that using. But when you define a method in derived class, you hide the method with same name inherited from base classes; also if they have a different signature. So, in this case, you have to use that `using` to make visible inherited methods. – max66 Apr 29 '18 at 02:56
  • @max66 that is not quite what I meant, however that is good to know. What I want to know is instead of doing: `void Function (T0 const &)` could I do `void Function (float const&)`? as shown in the third code block of my question? – Cethric Apr 29 '18 at 03:53
  • @Cethric - not sure to understand what you want but... yes, it's possible. – max66 Apr 29 '18 at 09:51
  • @max66 I don't understand why the compiler doesn't complain about multiple definitions of a function if I use it like `Example e;`. Wouldn't that essentially generate two methods with the same signature and the call become ambiguous? – qutab Nov 18 '18 at 21:46
  • @qutab - Yes and no. Yes: there are generated two methods with the same signature `Function(int const &)`; the first one in `Example` and the second one in `Example`. And no: the call doesn't become ambiguous; because `Example` inherit from `Example`; so the `Function(int const &)` method in `Example` hide the one on `Example`. The `using`, in this case, doesn't works because un-hide methods and members with same name; but a method (or member) in `Example` hide equally a method (member) with the same signature (of the same name). – max66 Nov 18 '18 at 22:23
3

This answer start's with max66's answer, more or less

We start with a class that uses recursive inheritance to implement our function. In my case, I chose operator(), and I used a variadic using declaration to bring all children operator() into scope:

namespace detail{
    template<class T, class... U>
    struct ExampleImpl : ExampleImpl<U>...{
        using ExampleImpl<U>::operator()...;
        void operator()(T _arg){/*...*/}
    };
}

Where my answer diverges from max66's is in that we'll use this ExampleImpl class to compose our Example class:

template<class... T>
class Example
{
public:
    template <class U>
    void Function(U arg)
    {
        impl(arg);
    }
    
    void Function(float arg) 
    {
        /*Your specialization code*/
    }
private:
    detail::ExampleImpl<T...> impl;
};

I do it this way for 2 reasons:

  1. This inheritance is an implementation-detail and something you'd want to hide from clients.
  2. *Now we can easily specialize our Function function for whatever type we want because we always have a choice in whether to call through to our instance of ExampleImpl or not.

Demo

If ExampleImpl needs to use member variables of your Example class, then you can either turn ExampleImpl into a full-fledged PIMPL class, or modify its constructor or operator() to accept additional arguments as a form of Dependency Injection

*You could just as easily perform full class specialization where float is one of the template parameters in the specialization, and define your own Function. Or you could use a form of tag dispatching to hide the float version unless it was in the list of template types.

Tag dispatch demonstration

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143