Question

I have a class template:

template <typename Argument> class Signal
{
    void invoke(Argument arg) {}
};

Now I want to invoke signals without parameter (meaning void parameter). I assume I could specialize the whole class for void and it would compile. But there's a lot of code in the class, I don't want to duplicate it. I want to only specialize whatever neccessary. So I try adding:

// void specialization
template<> 
void Signal<void>::invoke()
{

}

And get an error:

error C2244: 'Signal::invoke' : unable to match function definition to an existing declaration

Why is that?

The same code works for any type other than void.

Was it helpful?

Solution

The specialization for this template:

template <typename Argument> class Signal
{
    void invoke(Argument arg) {}
};

would be:

template<> 
void Signal<void>::invoke(void arg)
{

}

which is illegal because you can't have a void object.

One way of doing what you want is to use overloading to declare both invoke methods, and use some templating tricks (I believe this one is called SFINAE) to only allow the correct overload available based on your class template argument:

template <typename Argument> class Signal
{
public:
    static constexpr bool IsVoid = is_same<Argument, void>::value;

    template <typename T = Argument, typename = typename std::enable_if< !IsVoid && is_same<Argument, T>::value>::type >
    void invoke(T arg) {
        // only available for non-void types
    }

    template <typename T = Argument, typename = typename std::enable_if< IsVoid >::type >
    void invoke() {
        // only available for void class specialization
    }
}


Signal<void> sv;
Signal<int> si;

sv.invoke(); // OK
si.invoke(1); // OK

sv.invoke(1); // NOT OK
sv.invoke("s"); // NOT OK
si.invoke(); // NOT OK
si.invoke("s"); // NOT OK

You can find more about the usage of enable_if here: std::enable_if to conditionally compile a member function, Why should I avoid std::enable_if in function signatures.

OTHER TIPS

What about C++11 variadic templates :

template<typename ...Args>
struct Signal {

typedef std::function<void(Args...)> slot_type;

std::list<slot_type> listeners;

template<typename Callable>
void connect(Callable callable) {
    listeners.emplace_back(callable);
}

void invoke(Args... args) {
    for(slot_type& slot : listeners)
        slot(args...);
}

};

void show_me(const std::string& data, int id) {
    std::cout << "hello " << data << " : " << id << std::endl;
}

int main() {
    Signal<std::string, int> s;
    s.connect(show_me);
    s.invoke("world", 42);
    // ...
}

It scales well with 0, 1 or more args.

If yo will try to declare a variable

void a;

you also will receive a compile error.

The problem is that compiler expects some type instead of Argument here

template <typename Argument> class Signal
{
    void invoke(Argument arg) {}
};

and void is not treated as a type here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top