Is there a way to automatically (and at compile time) define a "product enum class" (i.e. an enum class
that takes on values in the product space between two given enum classes)?
So if I have
enum class Color { RED, GREEN };
enum class Shape { CIRCLE, TRIANGLE };
then I want my compiler to make me a type
enum class ComboResult { RED_CIRCLE, RED_TRIANGLE, GREEN_CIRCLE, GREEN_TRIANGLE };
without me having to type all the possibilities myself.
The motivation would be like a switch statement that can exhaustively search every element in the product space.
Since primary motive is switch-case statements, I make 2 assumptions:
Number of enumerators is small
Compile-time evaluation is needed
With that in mind:
#include <type_traits>
#include <concepts>
#include <ranges>
#include <functional>
#include <stdexecpt>
template<typename enm>
concept reflectable_enum =
std::is_enum_v<enm>
and requires(enm ev) {
{std::to_underlying(ev)}
-> std::unsigned_integral;
{max_enum_value(ev)}
-> std::same_as<enm>;
};
template<reflectable_enum ... es>
constexpr auto cartesian_enum_product(es const ... evs)
{
using int_t = std::common_type_t<std::underlying_type_t<es>...>;
std::array values {static_cast<int_t>(evs)...};
constexpr auto static cum_cs = [](auto evs){
std::array maxcs
{ 1
, static_cast<int_t>
(max_enum_value(evs))
...};
for( auto&& [a,b]
: maxcs
| std::views::adjacent<2>){
b*=a;
if((~(int_t{})/b)<a)
throw std::logic_error
{"large enums"};
};
return maxcs;
}(es{}...);
auto const result = std::ranges::fold_left(
std::views::zip_transform
( std::multiplies<>{}
, values, cum_cs )
, int_t{}
, std::plus<>{} )
);
enum class result_t : int_t{};
return static_cast<result_t>(result);
};
The rnumerations need some restriction and metadata for the task. I restricted them to unsigned enums for simpler illustration; otherwise a min_enum_value
function would be involved too. Since the function is declared constexpr
, and definition can actually produce compile time constants, it can be used in core constant expression like case
lables:
enum X:unsigned{x0, xm};
enum Y:unsigned{y0, ym};
//Meta data:
consteval max_enum_value(X){return xm;};
consteval max_enum_value(Y){return ym;};
constexpr auto x0_y0 //for SO syntax highlighter
= cartesian_enum_product(X::x0, Y::y0);
switch(cartesian_enum_product(x,y)){
default:
return default_result(x,y);
case x0_y0:
return result_x0_y0();
};
In the above snippet, I had to define the case value outside the switch
, to get the syntax highlighter to work, but it should eork with direct function call too.
I tried to avoid C++26 reflection, because it's too new and needs more study.
Edit: the original simple product was wrong. Now it's more complex.