Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ dispatch table with templated functions

Tags:

c++

I have a dispatch table in some C++ code. It maps tags to functions that can process those tags. In the first version, it takes functions that accept two strings and return a string. The strings are serialised protobufs.

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

Here the converter functions look like this

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

After implementing quite a number of these converters, I realised they were often over half boilerplate: error checking, deserialisation, serialisation, etc. So I wrote a quick template that vastly simplifies my code:

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;
}

And now convert_proto() can look like this:

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

This is very nice, but now I've broken the dispatch table, because each animal processor has a different signature, since a Dog and a Cat are both protobufs but otherwise are unrelated. I don't know how to make a dispatch table without resorting to a long bit of if ... else if .....

What I want is a map like this:

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

Then my function that uses the dispatch table, which currently says something like

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

becomes

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

Of course, one way would be to define an abstract base class with operator() that takes strings, and then implement an instance of that class for each of my conversion function. The operator() calls some function that is only defined in the derived classes. But now I've lost any gain in readability or conciseness I might have found.

Any suggestions?

Update

Following a line of reasoning proposed by @felix, I wrote this:

#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.
}

The problem in the above is that the template arguments must of course be known at compile time. That's fine when I'm using const string's from an ontology of strings, but fails if the strings pass through a database and so the lookup is dynamic based on value rather than object.

like image 756
jma Avatar asked Feb 10 '17 15:02

jma


1 Answers

You still want map<string, function<string(const string&, const string&)>>, you just want to populate it differently:

using converter = function<string(const string&, const string&)>;

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

Now you want a function template returning a converter that you can use with any of ProcessTwoDogs, ProcessTwoCats and what have you.

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;
       };
}
like image 148
n. 1.8e9-where's-my-share m. Avatar answered Oct 21 '22 01:10

n. 1.8e9-where's-my-share m.