首页 > 解决方案 > C++:使用 void 作为模板参数

问题描述

我有这个最小的类来表示客户端可以订阅的事件。
该事件可以具有与之关联的数据类型,因此当它被发布者触发时,该类型的参数将被传递给客户端的回调:

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;
};

现在我可以创建一个Event<int>或任何其他具体类型,但对我来说,允许“空”事件非常重要,它没有与之关联的数据:Event<void>

但遗憾的是,这不起作用:

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;
}

有什么方法可以实现这个所需的 API?如何?

(PS 解决方案应该适用于 C++11)

标签: c++c++11templatescallbacktemplate-meta-programming

解决方案


解决此问题而无需明确专门化的最快方法void是使用参数包(在 C++11 中添加)作为模板参数而不是单一类型,并使用空参数包而不是void. 一个参数包可以同构地保存任意数量的类型,包括 0 和 1。然后它可以用来生成正确的类型和成员函数。您基本上只需要在( link... )的每次使用附近正确添加:Arg

#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;
}

这有一个额外的好处,您可以使用带有多个参数的回调。如果这不是可取的,您可以添加一个static_assert来防止它:

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;
};

请注意,此解决方案需要Event<>而不是Event<void>. Event<void>您可以通过为该用途Event<>链接)添加一个简短的专业化来解决这个问题:

template<>
class Event<void> : public Event<>
{
    // Inherit constructors
    using Event<>::Event;
};

推荐阅读