Is your if an object state?
published at 11.11.2020 08:48 by Jens Weller
Save to Instapaper Pocket
So as I was writing my code for my current project, I had this function where a bunch of ifs queried certain properties to decide what version of the code should apply to the element handed to that function.
And at first, this seemed like an obvious way to do it, but once I realized that this is called on a hot path, it seemed a bit odd to have series of if-else on a centra point of that hot path. So I thought about way to change this.
The actual case is a function visiting its neighbor cells, A cell can have 3, 5 or 8 Neigbors, and in the rectangle generally 9 different cases exist. All of these cases are corner cases, except the common case. So even with the most common case first, there is the case of going through all other cases to reach the last, least likely corner case. The cells are stored in vector of T, so that not each cell is directly next its neighbors. The cell it self does not change during its life time, so that the actual position of the cell is part of its state.
Which means that the state of the object needs to be determined once, and not in every step of a simulation using this grid. Hence there still is a function with all the ifs, but it is called once per object, not once per step. Though the question comes to mind then, how to represent this state in code. 3 options seem viable:
- a classical OOP hierarchy
- an enum
- a variant with tag types
The OOP solution is too much boiler plate code to even think about implementing it for my self. The enum seems nice and might be also an option worth exploring, though I went with variant this time:
namespace neighbor_category { struct topleft{}; struct top{}; struct topright{}; struct left{}; struct right{}; struct bottomleft{}; struct bottom{}; struct bottomright{}; struct center{}; using neighborcat = std::variant<std::monostate,center,topleft,top,topright,left,right,bottomleft,bottom,bottomright>; }
As of the current state of the code I also have to add std::monostate to the variant, so that the uninitiated state is monostate and not an otherwise valid type. As the default type of a default constructed std::variant is the first type in its typelist.
With that done, there is now one function to classify a grid cell, which then gets saved in the object. I could tweak it to do this in the constructor, but this would complicate my code a little.
template<class T> neighbor_category::neighborcat categorizeNeighbor(size_t index,std::vector<T>& vec,size_t width) { if(index > width && index < vec.size() - (width + 1) && index % width != 0 && index % width != width - 1) return neighbor_category::center{}; else if(...
}
In a different function I then call std::visit with an overloaded struct to handle the visiting of the different neighbor grid cells.
std::visit(overloaded { [&](neighbor_category::center) { f(vec[index + 1]);//right f(vec[index - 1]);//left f(vec[index - width]);//top f(vec[index - width -1]);//topleft f(vec[index - width +1]);//topright f(vec[index + width - 1]);//bottomleft f(vec[index + width]);//bottom f(vec[index + width + 1]);//bottomright },... }
The code is nicely reusable for any kind of grid, while the calling code handles the logic it needs to execute based on the neighbors.
auto count_alive =[&neighbors_alive](auto t){if(t.getLocalstate() == GridPixel::state::filled)neighbors_alive++;}; std::unordered_map<size_t,int> alive_neighbormap; for(size_t i = 0;i < anchors.size();++i) { visitNeighbors(anchors[i].getNeighborcategory(),i,anchors,sw,count_alive); alive_neighbormap.insert({i,neighbors_alive}); neighbors_alive = 0; }
Which in my case is simply to count the neighbors of a certain state and cache that value to then in the next state apply it to the grid with out changing the state of the grid while doing so.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!