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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With