带有模板化函数的C++调度表

C++ dispatch table with templated functions

本文关键字:C++ 调度表 函数      更新时间:2023-10-16

我在一些C++代码中有一个调度表。它将标记映射到可以处理这些标记的函数。在第一个版本中,它采用接受两个字符串并返回一个字符串的函数。字符串是串行化的protobuf。

map<string, function<string(const string& serialised_1,
const string& serialised_2)>> converters = {
...
{ 'dog', ProcessTwoDogs },
{ 'cat', ProcessTwoCats },
...
};

这里的转换器功能看起来像这个

string ProcessTwoDogs(const string& dog_1_str, const string& dog_2_str);

在实现了大量这样的转换器后,我意识到它们通常都是半个样板:错误检查、去序列化、串行化等。所以我写了一个快速模板,极大地简化了我的代码:

template <typename ProtoT>
std::string ConvertProtos(
const std::string& proto_str_a,
const std::string& proto_str_b,
std::function<ProtoT(const ProtoT&, const ProtoT&)> convert_proto) {
ProtoT proto_a = ...;
ProtoT probo_b = ...;
// and various error checks.
ProtoT proto_out = convert_proto(proto_a, proto_b);
// some more checks, and serialise to proto_out_str.
return proto_out_str;
}

现在convert_proto()可以是这样的:

Dog ProcessTwoDogs(const Dog& dog_1, const Dog& dog_2) { ... }

这很好,但现在我打破了调度表,因为每个动物处理器都有不同的签名,因为DogCat都是原蟾蜍,但在其他方面是不相关的。我不知道如何在不使用长的if ... else if ....的情况下制作调度表。

我想要的是这样的地图:

// Doesn't compile.
map<string, 
template<typename ProtoT>function<ProtoT(const ProtoT&, const ProtoT&)>>

然后我的函数使用调度表,它现在说的是类似的东西

auto processor = the_map.at(tag);
string new_string = processor(string_1, string_2);

成为

auto processor = the_map.at(tag);
string new_string = ConvertProtobufs(string_1, string_2, processor);

当然,一种方法是用operator()定义一个接受字符串的抽象基类,然后为我的每个转换函数实现该类的实例。operator()调用一些只在派生类中定义的函数。但现在我在可读性或简洁性方面失去了任何优势。

有什么建议吗?

更新

根据@felix提出的一系列推理,我写下了以下内容:

#include <functional>
#include <iostream>
#include <map>
#include <string>
using std::cout;
using std::function;
using std::endl;
using std::map;
using std::string;
struct Dog {
void operator()() { cout << "I am a dog." << endl; }
};
struct Cat {
void operator()() { cout << "I am a cat." << endl; }
};
string cat = string("cat");
string dog = string("dog");
template<string& s> void fn() { cout << "I am lost" << endl; }
template<> void fn<dog>() { Dog dog; dog(); }
template<> void fn<cat>() { Cat cat; cat(); }
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
fn<dog>();
fn<cat>();
// Oops, it all falls apart here:
string dog1("dog");
fn<dog1>();   // Doesn't compile, and a dog is not a dog1.
}

上面的问题是,模板参数当然必须在编译时已知。当我使用字符串本体中的const字符串时,这很好,但如果字符串通过数据库,则会失败,因此查找是基于值而非对象的动态查找。

您仍然想要map<string, function<string(const string&, const string&)>>,您只想以不同的方式填充它:

using converter = function<string(const string&, const string&)>;
map<string, converter> converters = {
...
{ "dog", convert_protos(ProcessTwoDogs) },
{ "cat", convert_protos(ProcessTwoCats) },
...
};

现在,您需要一个返回转换器的函数模板,该转换器可以与ProcessTwoDogsProcessTwoCats中的任何一个一起使用。

template <typename P_Res, typename P_A, typename P_B> 
converter convert_protos(P_Res (*processor)(P_A, P_B)) {
return [](const string& s_a, const string& s_b) -> string
{
// some error checks
P_A p_a = deserialize<P_A>(s_a);
P_B p_b = deserialize<P_B>(s_a);
// some more error checks
P_Res p_res = processor(p_a, p_b);
// yet more checks
string s_res = serialize(p_res);
// last final checks
return s_res;
};
}

我的回答没有帮助,它最终会导致反思(这完全没有必要)。直到看到你的更新,我才意识到这一点。

原来的答案是:

我不确定我是否完全理解你的问题。

一种解决方案是使用成员函数模板和函数模板显式实例化。

#include <cassert>
#include <iostream>
#include <map>
#include <string>
class Dog {};
class Cat {};
struct Processor {
template <class T>
T process(const T &, const T &) const;
const std::string tag_;
};
template <>
Dog Processor::process(const Dog &, const Dog &) const {
std::cout << "dog" << std::endl;
return Dog{};
}
template <>
Cat Processor::process(const Cat &, const Cat &) const {
assert(tag_ == "cat");
std::cout << "cat" << std::endl;
return Cat{};
}
int main() {
std::map<std::string, Processor> mapa =
{{"dog", Processor{"dog"}},
{"cat", Processor{"cat"}} };
mapa["cat"].process(Cat{}, Cat{});
return 0;
}

如果您不需要任何标记检查,您可以删除成员变量tag_并使processor成为一个静态类,但这将使map实际上不再需要。

Processor有一个成员函数模板,它是为CatDog显式实例化的。

非正式地,当在参数类型为CatProcessor的任何实例上调用process时,编译器将发现函数模板process具有类型Cat的显式实例化。然后,编译器将使用显式函数,即Cat Processor::process(const Cat &, const Cat &) const,而不是隐式实例化成员函数。