这与 previous question 有关因为它是同一个系统的一部分,但这是一个不同的问题。
我正在开发一个内部消息传递系统,该系统旨在向消费者发送消息 ( struct
s)。
当项目要使用消息系统时,它会定义一组消息(enum class
)、数据类型(struct
)以及这些实体之间的关系:
template <MessageType E> struct expected_type;
template <> struct expected_type<MessageType::TypeA> { using type = Foo; };
template <> struct expected_type<MessageType::TypeB> { using type = Bar; };
template <> struct expected_type<MessageType::TypeM> { using type = Foo; };
请注意,不同类型的消息可能使用相同的数据类型。
我在上一个问题中讨论了发送这些消息的代码。有一个模板化方法可以发送任何消息,并使用上面的模板定义维护类型安全。它工作得很好。
我的问题是关于消息接收类的。有一个基类,它实现了如下方法:
ReceiveMessageTypeA(const Foo & data) { /* Some default action */ };
ReceiveMessageTypeB(const Bar & data) { /* Some default action */ };
ReceiveMessageTypeM(const Foo & data) { /* Some default action */ };
然后它实现了一个单一的消息处理函数,像这样:
bool ProcessMessage(MessageType msgType, void * data) {
switch (msgType) {
case TypeA:
ReceiveMessageTypeA(data);
break;
case TypeB:
ReceiveMessageTypeB(data);
break;
// Repeat for all supported message types
default:
// error handling
break;
}
}
当需要一个消息接收者时,这个基类被扩展,并且所需的ReceiveMessageTypeX
方法得到实现。如果那个特定的接收者不关心消息类型,相应的函数就不会实现,而是使用基类的默认值。
旁注:忽略我正在传递 void *
的事实而不是具体的类型。中间还有一些代码可以处理所有这些,但这不是相关的细节。
该方法的问题在于添加了新的消息类型。以及必须定义 enum
, struct
, 和 expected_type<>
特化,必须修改基类以添加新的 ReceiveMessageTypeX
默认方法,以及 ProcessMessage
中的 switch 语句必须更新功能。
我想避免手动修改基类。具体来说,我想使用存储在 expected_type
中的信息做繁重的工作,避免重复。
这是我尝试的解决方案:
在基类中,定义一个方法:
template <MessageType msgType>
bool Receive(expected_type<msgType>::type data) {
// Default implementation. Print "Message not supported", or something
}
然后,子类可以只实现他们关心的特化:
template<> Receive<MessageType::TypeA>(const Foo & data) { /* Some processing */ }
// Don't care about TypeB
template<> Receive<MessageType::TypeM>(const Foo & data) { /* Some processing */ }
我认为这解决了部分问题;我不需要在基类中定义新方法。
但我不知道如何去掉 switch 语句。我希望能够做到这一点:
bool ProcessMessage(MessageType msgType, void * data) {
Receive<msgType>(data);
}
这当然不行,因为模板不是那样工作的。
我想到的事情:
- 从
expected_type
生成 switch 语句结构。我不知道该怎么做。 - 维护某种函数指针映射,并调用所需的指针。问题是我不知道如何在不重复
expected_type
中的数据的情况下初始化 map 。 ,我不想这样做。 - 定义
expected_type
使用宏,然后玩预处理器游戏将该数据也按摩到 switch 语句中。这可能是可行的,但我尽量避免使用宏。
因此,总而言之,我希望能够根据运行时值调用不同的模板特化。这对我来说似乎是矛盾的,但我希望有人能给我指出一个有用的方向。即使这告诉我这不是一个好主意。
我可以改变expected_type
如果需要,只要它不破坏我的 Send
方法(参见我的 other question )。
最佳答案
您对 expected_type
和 Receive
模板的想法是正确的;只需一步即可完成所有工作。
首先,我们需要提供一些方法来枚举 MessageType:
enum class MessageType {
_FIRST = 0,
TypeA = _FIRST,
TypeB,
TypeM = 100,
_LAST
};
然后我们可以在编译时枚举 MessageType 并生成调度函数(使用 SFINAE 跳过 expected_types
中未定义的值):
// this overload works when expected_types has a specialization for this value of E
template<MessageType E> void processMessageHelper(MessageType msgType, void * data, typename expected_type<E>::type*) {
if (msgType == E) Receive<E>(*(expected_type<E>::type*)data);
else processMessageHelper<(MessageType)((int)E + 1)>(msgType, data, nullptr);
}
template<MessageType E> void processMessageHelper(MessageType msgType, void * data, bool) {
processMessageHelper<(MessageType)((int)E + 1)>(msgType, data, nullptr);
}
template<> void processMessageHelper<MessageType::_LAST>(MessageType msgType, void * data, bool) {
std::cout << "Unexpected message type\n";
}
void ProcessMessage(MessageType msgType, void * data) {
processMessageHelper<MessageType::_FIRST>(msgType, data, nullptr);
}
关于c++ - 根据运行时值调用不同的模板函数特化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39842853/