C ++ dispatch table with templates

I have a submit table in C ++ code. It binds tags to functions that can handle these tags. In the first version, it takes functions that take two strings and return a string. Lines are serialized protobuffs.

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 a number of these converters, I realized that they often exceed half the pattern: error checking, deserialization, serialization, etc. Therefore, I wrote a quick template that greatly 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() might look like this:

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

This is very nice, but now I have broken the dispatch table, because each animal processor has a different signature, because Dog and a Cat are proto-bufs, but are not connected otherwise. I do not know how to make a send table without resorting to a long fragment of if ... else if ....

What I want is such a card:

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

Then my function using a dispatch table that 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() , which takes strings and then implements an instance of this class for each of my conversion functions. operator() calls some function that is defined only in derived classes. But now I have lost any gain in readability or conciseness that I could find.

Any suggestions?

Update

Following the line of reasoning suggested by @felix, I wrote the following:

 #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 is that the template arguments must, of course, be known at compile time. This is great when I use const string from the ontology of strings, but it fails if the strings go through the database, and therefore the search is performed dynamically based on the value, not the object.

+5
source share
2 answers

You still want map<string, function<string(const string&, const string&)>> , you just want to fill it in different ways:

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

Now you need a function template that returns a converter that you can use with any of the ProcessTwoDogs , ProcessTwoCats and what you have.

 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; }; } 
+2
source

My answer does not help, it will ultimately lead to reflection (which is completely unnecessary). I did not understand this until I saw your update.

Original answer:

I am not sure if I fully understood your question.

The solution is to use a member function template and a template template template.

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

And if you do not need the tag validation, you can remove the variable-chlen__imya__i_i_i_i_i_i_i_i_i_s_s_s_s_s_s_1_1_1_1_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_2_1_2_2_2_1_2_2_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1

The Processor class has a member function template that is explicitly specified for Cat and Dog .

Informally, when calling process on any instance of Processor with a Cat argument type, the compiler discovers that the process function template has an explicit creation for the Cat type. Then, instead of the implicit instance of the member function, the compiler will use the explicit, aka Cat Processor::process(const Cat &, const Cat &) const .

0
source

Source: https://habr.com/ru/post/1264085/


All Articles