类模板:为什么我不能将单一方法专用于空类型?



我有一个类模板:

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

现在我想调用没有参数的信号(意思是void参数)。我假设我可以将整个班级专门化void并且它会编译。但是类中有很多代码,我不想复制它。我只想专注于任何必要的事情。所以我尝试添加:

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

并得到一个错误:

错误 C2244:"信号::调用":无法匹配函数 现有声明的定义

为什么?

相同的代码适用于除 void 以外的任何类型。

此模板的专用化:

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

将:

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

这是非法的,因为你不能有一个空对象。

执行所需操作的一种方法是使用重载来声明两个调用方法,并使用一些模板技巧(我相信这个称为 SFINAE)仅允许基于您的类模板参数提供正确的重载:

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

你可以在这里找到更多关于enable_if用法的信息:std::enable_if 有条件地编译成员函数,为什么我应该在函数签名中避免 std::enable_if。

那么C++11个可变参数模板呢:

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);
    // ...
}

它可以很好地扩展 0、1 或更多参数。

如果你会尝试声明一个变量

void a;

您还将收到编译错误。

问题是编译器在这里期望某种类型而不是 Argument

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

这里不将 void 视为一种类型。

最新更新