从锅炉板代码到模板实现



我正在实现一个有限状态机,其中所有可能的状态都存储在一个std::tuple中。

这是我所面临的问题的最小编译示例及其godbolt链接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);
}

在我的情况下,我想改变void transit_to(size_t index)实现到一些模板结构,所有的锅炉板代码可以简化,以防我在开发过程中添加新的状态。

唯一的约束是:

  1. 使用c++ 17或以下版本(对不起,请不要使用c++20的花哨功能)

  2. 不要改变接口(不要建议按类型访问之类的东西)

如果您只是想避免再添加像

这样的行
if (index == 4) return transit_to<4>();

当你添加一个新的状态,例如struct state5,你可以这样实现transit_to(std::size_t):

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

我不知道如何摆脱从运行时值到函数模板的特定实例的转换,但是您可以制作一个映射来简化它:

#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();
}

基于paolo的回答,您可以避免手动填充地图:

#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();
}

我认为把函数的指针放在数组中可以使这个很好,干净和快速。它甚至是不可知论的事实,有一个std::tuple

using States = std::tuple<state1, state2, state3, state4>;
template<typename State>
void transit_to()
{
std::cout << __PRETTY_FUNCTION__ << 'n';
}
template<typename>
struct state_array;
template<template<typename...> class States, typename...Ts>
struct state_array<States<Ts...>>
{
static constexpr std::array transitions { &transit_to<Ts>... };
};

void transit_to(size_t index)
{
state_array<States>::transitions[index]();
}

https://godbolt.org/z/Exfn8ben1

相关内容

  • 没有找到相关文章

最新更新