C++ std::random and enum class

Viewed 274

I've got a fussy question regarding more modern C++ "preferred" styles.

Say I want to use the contents of std::random in order to select a value from an enum class. How can I finagle that? We're talking some pretty basic stuff here, where the first selection works just fine, but Visual Studio scolds me (rightly) for using bare enums:

enum Direction:uint8_t {
    Left, Right
};

std::uniform_int_distribution<uint8_t> direction(Direction::Left, Direction::Right);

// and so on...

I was surprised enum-base works for a classic enum. But, simply adding the word class into the mix causes the whole shebang to fail to compile.

enum class Direction:uint8_t {
    Left, Right
};

std::uniform_int_distribution<uint8_t> direction(Direction::Left, Direction::Right);

// Severity Code    Description Project File    Line    Suppression State
// Error    C2338   invalid template argument for uniform_int_distribution:
//    N4659 29.6.1.1 [rand.req.genl]/1e requires one of short, int, long, long long, unsigned short, 
//    unsigned int, unsigned long, or unsigned long long 
//    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30133\
//    include\random    1863    

And so on. =)

I can certainly swallow my pride and principle for the sake of solving this problem, but I wanted to abide by modern standards.

I'd be tickled pink if someone had some clever workaround or other best-practice-style-thing when one wants to shuffle an enum like this.

1 Answers

Both enum and enum class are rather problematic: they don't know their size, they can't be iterated etc. As a replacement, you can use std::variant with dummy types:

struct Left {};
struct Right {};
using Direction = std::variant<Left, Right>;

template<typename V, typename E, std::size_t idx = 0>
auto constexpr cast = [] { // written once, works for any "enum"
    if constexpr (std::is_same_v<std::variant_alternative_t<idx, V>, E>) return idx;
    else return cast<V, E, idx + 1>;
}();

std::uniform_int_distribution direction{cast<Direction, Left>, cast<Direction, Right>};

As a bonus, now you can get the number of elements in your "enums":

static_assert(std::variant_size_v<Direction> == 2);

And you can even iterate over their enumerators:

template<typename F, typename... Es> // written once, works for any "enum"
constexpr void for_each(F f, std::variant<Es...> const&) {
    (..., (void)f(Es{})); // void cast to ignore possible operator,() overloading
}

int main() {
    for_each([](auto enumerator) {
        std::cout << cast<Direction, decltype(enumerator)> << ' ';
    }, Direction{});
}
Related