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

jma*_*jma 6 c++

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

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

转换器功能在这里看起来像这样

string ProcessTwoDogs(const string& dog_1_str, const string& dog_2_str);
Run Code Online (Sandbox Code Playgroud)

在实现了相当多的这些转换器后,我意识到它们经常超过一半样板:错误检查,反序列化,序列化等等.所以我写了一个快速模板,大大简化了我的代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在convert_proto()看起来像这样:

Dog ProcessTwoDogs(const Dog& dog_1, const Dog& dog_2) { ... }
Run Code Online (Sandbox Code Playgroud)

这是非常好的,但现在我已经打破了调度表,因为每个动物处理器都有不同的签名,因为a Dog和a Cat都是protobufs,但在其他方面是不相关的.我不知道如何制作一个调度表而不需要花费很多时间if ... else if .....

我想要的是这样的地图:

// Doesn't compile.
map<string, 
   template<typename ProtoT>function<ProtoT(const ProtoT&, const ProtoT&)>>
Run Code Online (Sandbox Code Playgroud)

然后我的函数使用调度表,目前说的类似于

auto processor = the_map.at(tag);
string new_string = processor(string_1, string_2);
Run Code Online (Sandbox Code Playgroud)

auto processor = the_map.at(tag);
string new_string = ConvertProtobufs(string_1, string_2, processor);
Run Code Online (Sandbox Code Playgroud)

当然,一种方法是定义一个带有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.
}
Run Code Online (Sandbox Code Playgroud)

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

n. *_* m. 2

你仍然想要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) },
    ...
};
Run Code Online (Sandbox Code Playgroud)

现在您需要一个返回转换器的函数模板,您可以将其与任何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;
       };
}
Run Code Online (Sandbox Code Playgroud)