0

I am building a system in which mathematical computations are described as a graph. Each node of the graph is either very simple function (e.g. x -> x^2) which does "double in double out" or it is an arbitrary complex functions with possibly non primitive data types involved. The structure of the graph and all its elements are known at compile time and the structure wont change during execution.

Current program execution order inside main() is as follows: All nodes are initialized as objects, they are connected with each other via (special) connect() function, first nodes of the are manually activated and further computation takes place.

The problem I am struggling with is the part of the program where I transfer the result of one node to its children.

Firstly I send void* of the result of the children and then statically casted it to the right data type inside the child. The thought on this point that there will always be right data type hidden behind void*, because I previously checked if two nodes are "connectable". It turned out that the program worked (in build mode) but it didn't compile at all in release mode. (Nowadays I think I could use reinterpret_cast to avoid the problem with release, but still, I wasn't proud about that code.)

My second attempt was using an external function called friend void propagate(...) which was invoked from inside of a concrete node. I restricted myself of transmitting any data types as in first attempt to only using double, std::vector<double> and later maybe a matrix. Inside of propagate() I am using a simple look-up table together with a switch do decide which of (for the selected data type) specialized transmitting functions to invoke. This attempt works fine and the code is much cleaner but I am restricted to those data types. Even for a simple bool as a parameter I would need to make it all somehow a template or copy the code.

In further investigation I came over key words as data flow programming, reactive... didn't got far there yet.

Well, I hope I could clarify my struggles. I do not share my code because I think my entire solution does have mangles I am open to hear better approaches which would lead me to implement the system in scalable and elegant way.

EDIT: Just thinking, but maybe if I exactly want this kind of behavior as described, without of usage of inheritance or templates, maybe I need to used some dynamic typed language such as Python to make the data transfer easily done?

  • 2
    It looks like you try to invent inheritance which exist in `c++` out of the box. Look at this answer https://stackoverflow.com/questions/318064/how-do-you-declare-an-interface-in-c – Tarek Dakhran Mar 02 '20 at 20:10
  • 2
    `void*` in C++ is usually a sign that your design is wrong. Take @TarekDakhran 's advice and investigate inheritance, but you might be able to get away without it. – user4581301 Mar 02 '20 at 20:19
  • @TarekDakhran If I got it right, you purpose to implement the graph as inheritance chain, even using multiple inheritance if a node has multiple input parameters to take? In this approach I would make only the result of the node public to be used by it's children. But what if I no longer want to do directed acyclic graphs but maybe want to introduce closed loop system, then there is no way to use inheritance, as far as I see. – user12700880 Mar 02 '20 at 20:22
  • You mentioned that all information about types is available at compile time. It means that for every node you know the constructor signature. `NodeTypeX(SomeArg Y, SomeArg Z);` now you can make all `NodeTypeSomeType` children of `Base` which has pure `std::unique_ptr evaluate() const;` method. Then just override evaluate for every child type. – Tarek Dakhran Mar 02 '20 at 20:33
  • @TarekDakhran This sounds very much as I implemented the system using composite pattern. I agree that this would work if I will do normal inheritance, but the thing what I maybe should have mentioned before, is that exactly the purpose of the systems is being not hard coded and being using some 3rd party description of how the computational graph should look like (using xml description for example). So the idea is that I throw in a xml, and a header with some "special" functions, witch are not included inside std::math or so and the program compiles to .dll for computing this special graph. – user12700880 Mar 02 '20 at 20:56
  • @user4581301 What would be your approach to avoid inheritance in described case? – user12700880 Mar 02 '20 at 21:10
  • Depends on how much the behaviour changes from one case to another. It there are a small number of cases and little variation in the data required to represent the cases, one class and a few `switch` statements may be all you need. – user4581301 Mar 02 '20 at 21:21
  • @user4581301 Thank you for the reply. This sounds good to me for the beginning, but I am worrying about the unknown cases and hoping for universality of data transfer. – user12700880 Mar 02 '20 at 21:30
  • Universality with inheritance is just as hard. You need to keep generating new classes to handle new cases. It sounds like what you are really looking for is a way to abstract the data into one (or a small number) generic format. – user4581301 Mar 02 '20 at 21:31
  • @user4581301 Yes, I indeed asked exactly about that in my prev post https://stackoverflow.com/q/60434788/12700880 I tried to squash data types as much as possible. My other attempt used variadic templates and look up tables for dynamic rewriting elements of tuples... this was an fight against type safety. – user12700880 Mar 02 '20 at 21:39

0 Answers0