It is possible to define some map class usable for constexpr objects. It is then possible to define your mapping in a natural way as follows
constexpr CTMap<tags,const char*,2> enum_table
{{{
{tags::TAG2, "val2" },
{tags::TAG1, "val1" }
}}};
where CTMap is defined by
template <class Key, class Value, int N>
struct CTMap
{
using KV = std::pair<Key,Value>;
std::array<KV,N> pairs;
constexpr Value at (Key key) const {
for (int i=0; i<N; i++) {
if (std::get<0>(pairs[i])==key) { return std::get<1>(pairs[i]); }
}
throw std::out_of_range ("key not found");
}
template<std::size_t I>
constexpr auto as_array() const {
return [this] <std::size_t...Is> (std::index_sequence<Is...>) {
return std::array<std::tuple_element_t<I,KV>,N> {std::get<I>(pairs[Is])...} ;
} (std::make_index_sequence<N>());
}
constexpr auto keys () const { return as_array<0>(); }
constexpr auto values() const { return as_array<1>(); }
};
Even if one defines such a map by a list of pairs, it is possible to retrieve a constexpr array holding the keys with the CTMap::keys method (the same for the values)
static_assert (std::is_same_v<decltype(enum_table.keys()),std::array<tags,2>>);
static_assert (std::size(enum_table.keys())==2);
static_assert (enum_table.keys()[0] == tags::TAG2);
static_assert (enum_table.keys()[1] == tags::TAG1);
Demo
Note that this has limitations since the lookup in CTMap::at is done in linear time and therefore should be used for a rather low number of map entries.
Update
I forgot the point that the OP wanted to reorder the entries. This can be done by getting the indexes permutation of the keys and then use it while computing the keys and values arrays
constexpr auto getOrderedIndexes() const {
// We get the keys as a std::array
auto keys = [this] <std::size_t...Is> (std::index_sequence<Is...>) {
return std::array<Key,N> {std::get<0>(pairs[Is])...} ;
} (std::make_index_sequence<N>());
// We look for the indexes permutation that sorts the keys.
std::array<std::size_t,N> indexes = {};
std::iota (std::begin(indexes), std::end(indexes),0);
std::ranges::sort(indexes, [&keys] (auto i, auto j) { return keys[i]<keys[j]; });
return indexes;
}
template<std::size_t I>
constexpr auto as_array() const {
return [this] <std::size_t...Is> (std::index_sequence<Is...>) {
// We use the indexes permutation here.
return std::array<std::tuple_element_t<I,KV>,N> {std::get<I>(pairs[getOrderedIndexes()[Is]])...} ;
} (std::make_index_sequence<N>());
}
With the reordering, we have now for instance
static_assert (std::is_same_v<decltype(enum_table.keys()),std::array<tags,2>>);
static_assert (std::size(enum_table.keys())==2);
static_assert (enum_table.keys()[0] == tags::TAG1);
static_assert (enum_table.keys()[1] == tags::TAG2);
Demo
std::arrayalso do? Because that can be returned. And it should not have any overhead compared to a raw array.vals_okandvalsrelated? Is there supposed to be a lookup invals_oksomehow? Can't you just verify that there areNUM_TAGSelements invals_ok?static_assert(std::size(vals_ok) == NUM_TAGS);rvalat the same places as you have them invals_ok.vals_okthat just depends on position the string "val2" would now be associated with the wrong tag. OP wants to explicitly assign strings to tags so that this doesn't happen