Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: use void as template argument

I have this minimal class to represent an event which client can subscribe to.
The event can have an data type associated to it, so when it is triggered by a publisher, an argument of that type would be passed to the client's callback:

template<typename Arg, typename Callback = function<void(const Arg&)>>
class Event
{
public:
    Event(Callback c) : mCallback(c){}
    
    void Trigger(const Arg& arg) {
        mCallback(arg);
    }

private:
    Callback mCallback;
};

Now I can create an Event<int> or any other concrete type, but it is really important to me to also allow "empty" event, which has no data associated with it: Event<void>

But sadly that doesn't work:

static void FooVoid() {
    cout << "Look ma, no args!" << endl;
}

static void FooInt(int a) {
    cout << "int arg " << a << endl;
}

int main()
{
    /* Compiles */
    Event<int> eInt(&FooInt);
    eInt.Trigger(42);

    /* Does not compile :(
    Event<void> eVoid(&FooVoid);
    eVoid.Trigger();
    */
    return 0;
}

Is there any way to achieve this desired API? How?

(P.S the solution should work on C++11)

like image 319
Avi Shukron Avatar asked May 07 '26 16:05

Avi Shukron


1 Answers

The quickest way of solving this without explicitly specializing for void is to use a parameter pack (added in C++11) for your template argument instead of a single type and using an empty parameter pack instead of void. A parameter pack can homogeneously hold any number of type, including 0 and 1. Then it can be used to generate the right types and member functions. You basically just have to add ... correctly near every use of Arg (link) :

#include <functional>
#include <iostream>

template<typename ... Arg>
class Event
{
public:
    using Callback = std::function<void(const Arg&...)>;

    Event(Callback c) : mCallback(c){}
    
    void Trigger(const Arg& ... arg) {
        mCallback(arg...);
    }

private:
    Callback mCallback;
};

static void FooVoid() {
    std::cout << "Look ma, no args!" << std::endl;
}

static void FooInt(int a) {
    std::cout << "int arg " << a << std::endl;
}

int main()
{
    /* Compiles */
    Event<int> eInt(&FooInt);
    eInt.Trigger(42);

    Event<> eVoid(&FooVoid);
    eVoid.Trigger();
    
    return 0;
}

This has the added benefit that you can use callbacks with more than one argument. If this isn't desirable you can add a static_assert to prevent it :

template<typename ... Arg>
class Event
{
public:
    using Callback = std::function<void(const Arg&...)>;
    static_assert(sizeof...(Arg) <= 1, "Too many arguments");

    Event(Callback c) : mCallback(c){}
    
    void Trigger(const Arg& ... arg) {
        mCallback(arg...);
    }

private:
    Callback mCallback;
};

Notice that this solution requires Event<> instead of Event<void>. You can solve that by adding a short specialization for Event<void> that uses Event<> (link) :

template<>
class Event<void> : public Event<>
{
    // Inherit constructors
    using Event<>::Event;
};
like image 56
François Andrieux Avatar answered May 10 '26 06:05

François Andrieux



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!