Home > Blockchain >  From boiler plate code to template implementation
From boiler plate code to template implementation

Time:07-01

I am implementing a finite state machine where all possible states are stored within a std::tuple.

This is a minimum compiling example of the problem I am facing and its godbolt link https://godbolt.org/z/7ToKc3T3W:

#include <tuple>
#include <stdio.h>

struct state1 {};
struct state2 {};
struct state3 {};
struct state4 {};

std::tuple<state1, state2, state3, state4> states;

template<size_t Index>
void transit_to()
{
    auto state = std::get<Index>(states);
    //Do some other actions over state....
}

void transit_to(size_t index)
{
    if (index == 0) return transit_to<0>();
    if (index == 1) return transit_to<1>();
    if (index == 2) return transit_to<2>();
    if (index == 3) return transit_to<3>();
}

int main()
{
    for(int i=0; i<=3;   i)
        transit_to(i);
}

In my case, I would like to change the void transit_to(size_t index) implementation to some template construction where all the boiler plate code can be simplified in case I add new states during development.

The only constraints are:

  1. Use C 17 or below (sorry, no c 20 fancy features please).

  2. Do not change interface (do not propose accessing by type or so on things).

CodePudding user response:

If you only want to avoid adding another line like

if (index == 4) return transit_to<4>();

when you add a new state, e.g. struct state5, you may implement transit_to(std::size_t) like this:

// Helper function: Change state if I == idx.
// Return true, if the state was changed.
template <std::size_t I>
bool transit_if_idx(std::size_t idx) {
    bool ok{false};
    if (idx == I) {
        transit_to<I>();
        ok = true;
    }
    return ok;
}

template <std::size_t... Is>
bool transit_to_impl(std::size_t idx, std::index_sequence<Is...>) {
    return (transit_if_idx<Is>(idx) || ...);
}

void transit_to(std::size_t index) {
    constexpr static auto tupleSize = std::tuple_size_v<decltype(states)>;
    [[maybe_unused]] auto const indexValid =
        transit_to_impl(index, std::make_index_sequence<tupleSize>{});

    assert(indexValid); // Check if index actually referred to a valid state
}

CodePudding user response:

I don't see how you could get away from the translation from a runtime value to a specific instance of a function template, but you could make a map to simplify it:

#include <unordered_map>

void transit_to(size_t index) {
    static const std::unordered_map<size_t, void (*)()> tmap{
        {0, transit_to<0>},
        {1, transit_to<1>},
        {2, transit_to<2>},
        {3, transit_to<3>},
    };

    if (auto it = tmap.find(index); it != tmap.end()) it->second();
}

Building on paolo's nice answer, you could avoid having to populate the map manually:

#include <unordered_map>

std::tuple<state1, state2, state3, state4> states;

template <size_t Index>
void transit_to() {
    auto state = std::get<Index>(states);
}

template<std::size_t... I>
void populate_map(std::unordered_map<size_t, void(*)()>& m,
                  std::index_sequence<I...>) {
    ((m[I] = transit_to<I>), ...);
};

void transit_to(size_t index) {
    static const auto tmap = []{
        std::unordered_map<size_t, void(*)()> rv;
        populate_map(rv, std::make_index_sequence<
                             std::tuple_size_v<decltype(states)>>{});
        return rv;
    }();

    if (auto it = tmap.find(index); it != tmap.end()) it->second();
}
  • Related