Data Driven Programs in C ++

I'm not sure what to call this question, because the problem itself is looking for a construct that I do not know its name about.

The problem is that I am dealing with programs whose control flow is highly data dependent.

For example, I created a MIPS simulator that implemented a list of more than 50 instructions, each of which is implemented on its own, and everything is controlled by a huge case of switching

switch (function){ //Function is an int, each function (eg SLL) is case 0: //associated with one if (state->debug_level > 0){ fprintf(state->debug_out, "SLL\n"); } step_err = SLL(state, rs, rt, rd, sa); break; case 2: if (state->debug_level > 0){ fprintf(state->debug_out, "SRL\n"); } step_err = SRL(state, rs, rt, rd, sa); break; case 3: if (state->debug_level > 0){ fprintf(state->debug_out, "SRA\n"); } // 

I was told that this could be implemented using function pointers, but for this I am looking for a way to match data of any type, for example a string with other data, for example an integer. I know the cards, but I would not like to push away every pair. I'm looking for some kind of array, such as syntax, which I think will look earlier than it might look something like this:

 ΒΏtype? function_codes[]{ 0, "SLL"; 2, "SRL"; 3, "SRA"; ... } 

I am not looking for a solution to this problem, but a general approach to implementing a fast relationship between data and using it to change the flow of control.

EDIT AFTER ANSWERS What I was really looking for, but I didn’t really know the map, but, in particular, its initialization syntax similar to an array (see Accepted answer). It was used with function pointers, performed the required work.

+5
source share
6 answers

As you may have guessed, function pointers are actually a good way to do this. Since you indicate that you do not want to use a map, you can implement a send function based on integer functions using an array of function pointers. Please note that since I do not know the signature type of your MIPS functions (SLL, SRL, etc.), I used stub type names.

 typedef ret_t (*mips_func)(arg1_t, arg2_t, arg3_t, arg4_t, arg5_t); mips_func function_codes[] = { &SLL, &SRL, &SRA, ... }; //...Later, in the part of your code that used to contain the big switch statement step_err = (*function_codes[function])(state, rs, rt, rd, sa); 

The &SLL syntax gets a pointer to the SLL function, which I assume is already in scope because you can call it directly from the switch statement.

Note that this assumes that the numeric codes for functions are a continuous sequence of integers from 0 to [max code value]. If some numeric codes are not used, you need to either leave explicit spaces in your array (by placing the NULL pointer in one or more entries), or use std::map<int, mips_func> so that you can use arbitrary non-continuous integer values ​​in as keys to the function. Fortunately, using a map still does not require the push_back each element, since C ++ now has initializer lists. The same code using the Card will look like this:

 typedef ret_t (*mips_func)(arg1_t, arg2_t, arg3_t, arg4_t, arg5_t); std::map<int, mips_func> function_codes = { {0, &SLL}, {2, &SRL}, {4, &SRA}, ... }; //Using the Map looks exactly the same, due to its overloaded operator[] step_err = (*function_codes[function])(state, rs, rt, rd, sa); 
+4
source

For simplicity, you can use associative containers. If order is important, use std::map or std::unordered_map otherwise.

And you can use syntax similar to what you want

 std::map<size_t, std::string> codes_map = decltype(codes_map) { { 0, "val1" }, { 1, "val2" } }; 
+3
source

You can group the data as static members with the same name in the structures, and then use the templates to access them in general:

 struct A { auto call() const { return "((1))"; }; static const char * name; }; struct B { auto call() const { return "{{2}}"; }; static const char * name; }; struct C { auto call() const { return "<<3>>"; }; static const char * name; }; // nb these `T...` have: `sizeof(T) == ... == sizeof(empty_struct)` const char * A::name = "A"; const char * B::name = "B"; const char * C::name = "C"; 

boost::variant (and std::variant will be implemented soon) implements a unified connection type that provides a very clean and efficient way to use these structures as values:

 #include <cstdio> #include <vector> #include <boost/variant.hpp> int main() { std::vector<boost::variant<A, B, C>> letters{A{}, B{}, C{}, B{}, A{}}; auto visitor = [](auto x) { std::printf("%s(): %s\n", x.name, x.call()); }; for (auto var : letters) { boost::apply_visitor(visitor, var); } } 

Demo

+1
source

If you have only a small number of indexes for support, from 0 to 50, you will get maximum performance if you put pointers to objects in an array, not a map.

The syntax is also short:

 #include <iostream> #include <functional> static void f0() { std::cout << "f0\n"; } static void f1() { std::cout << "f1\n"; } void main() { std::function<void()> f[2] = { f0, f1 }; f[0](); // prints "f0" f[1](); // prints "f1" } 

Or, if you prefer classes over functions:

 #include "stdafx.h" #include <iostream> class myfunc { public: virtual void run() abstract; virtual ~myfunc() {} }; class f0 : public myfunc { public: virtual void run() { std::cout << "f0\n"; } }; class f1 : public myfunc { public: virtual void run() { std::cout << "f1\n"; } }; void main() { myfunc* f[2] = { new f0(), new f1() }; f[0]->run(); // prints "f0" f[1]->run(); // prints "f1" for (int i = 0; i < sizeof(f) / sizeof(f[0]); ++i) delete f[i]; } 
0
source

It seems that you have two problems: a flow control problem (sending) and a map problem (implementation note). I get that the program flow is non-static and unrecognizable at compile time ... but is it also static? For static maps, I get a lot of mileage using the traits-ish method to create a compile time display. Heres a quick example binding a file suffix to Objective-C enum constants:

 namespace objc { namespace image { template <std::size_t N> inline constexpr std::size_t static_strlen(char const (&)[N]) { return N; } template <NSBitmapImageFileType t> struct suffix_t; #define DEFINE_SUFFIX(endstring, nstype) \ template <> \ struct suffix_t<nstype> { \ static constexpr std::size_t N = static_strlen(endstring); \ static constexpr char const str[N] = endstring; \ static constexpr NSBitmapImageFileType type = nstype; \ }; DEFINE_SUFFIX("tiff", NSTIFFFileType); DEFINE_SUFFIX("bmp", NSBMPFileType); DEFINE_SUFFIX("gif", NSGIFFileType); DEFINE_SUFFIX("jpg", NSJPEGFileType); DEFINE_SUFFIX("png", NSPNGFileType); DEFINE_SUFFIX("jp2", NSJPEG2000FileType); template <NSBitmapImageFileType nstype> char const* suffix_value = suffix_t<nstype>::str; } } 

... look how it works? the nice part is that with it there are no service resources at run time, that if your card is static, you can use something like this.

For dynamic flow control and scheduling function pointers work; this is what happens automatically if you use polymorphic classes and virtual functions, but it looks like you already have an architecture that cannot be redone with such highly modernistic architectural concepts. I like C ++ 11 lambdas as they solve as 90% of my problems in this arena. Perhaps you can make an effort (I will correct my answer)!

0
source

Given some definitions

 #include <iostream> #include <iterator> #include <algorithm> #include <stdexcept> #include <map> using namespace std; struct state{ int debug_level = 1; const char* debug_out = "%s"; } s; // some functions to call void SLL(state& s, int, int, int, int){ cout << "SLL"; } void SLR(state& s, int, int, int, int){ cout << "SLR"; } void SLT(state& s, int, int, int, int){ cout << "SLT"; } 

You can use the card

 auto mappedname2fn = map<string, delctype(SLL)*>{ {"SLL", SLL}, {"SLR", SLR} }; // call a map function mappedname2fn["SLR"](s, 1, 2, 3, 4); 

If you don't need a map, you can use a pre-sorted array for binary search

Here's a binary search for an array of names, pairs of functions

 template<typename P, int N, typename ...T> auto callFn(P(&a)[N], string val, T&&... params){ auto it = lower_bound(a, a+N, make_pair(val, nullptr), [](auto& p1, auto& p2){return p1.first < p2.first;}); if(it==(a+N) || val<it->first) throw logic_error("not found"); return it->second(forward<T>(params)...); } 

So you can configure the array and use it: -

 // array sorted in alphabetical order for binary search to work pair<string, decltype(SLL)*> name2fn[] = { {"SLL", SLL}, {"SLR", SLR}, {"SLT", SLT} }; void callFn(string name, state& s, int a, int b, int c, int d){ try{ callFn(name2fn, name, s, a, b, c, d); } catch(exception& e){ cout << e.what(); } } // call it callFn("SLL", s, 1, 2, 3, 4); 
0
source

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


All Articles