Search code examples
c++templatesc++17stdtuple

From boiler plate code to template implementation


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).


Solution

  • 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
    }