我有一个类模板:
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 视为一种类型。