C++ ain't Java! Every language has its own techniques which are a good fit with the language. Don't try to mimic a perfectly fine construct of one language in the same (but broken) construct in a different language.
Here is how I would solve your issue in C++:
// Define the actual enumeration
enum class [[nodiscard]] Vehicle : unsigned char
{
CAR,
MOTORCYCLE,
SIZE [[maybe_unused]]
};
// Convert your enumeration to a string (view)
#include <cassert>
#include <string_view>
[[nodiscard]] constexpr auto to_string(Vehicle v) noexcept -> std::string_view {
assert(v != Vehicle::SIZE);
switch (v) {
case Vehicle::CAR:
return "Car";
case Vehicle::MOTORCYCLE:
return "Motorcycle";
}
}
To use it, you can do something like:
for (unsigned char c = 0; c < static_cast<unsigned char>(Vehicle::SIZE); ++c)
std::cout << to_string(static_cast<Vehicle>(c)) << std::endl;
Writing this every time is a bit cumbersome, however, you could write your own template class that helps with iterating over it. For example:
#include <type_traits>
// The generic stuff you only write once
// Assumes you don't assign any values to your enumeration by hand + it ends on
// 'SIZE' (unless a second argument was passed if a different name was used)
template <typename TEnumeration, TEnumeration TSize = TEnumeration::SIZE>
class [[nodiscard]] EnumRange final {
using type = std::underlying_type_t<TEnumeration>;
public:
// The iterator that can be used to loop through all values
//
class [[nodiscard]] Iterator final {
TEnumeration value{static_cast<TEnumeration>(0)};
public:
constexpr Iterator() noexcept = default;
constexpr Iterator(TEnumeration e) noexcept : value{e} {}
constexpr auto operator*() const noexcept -> TEnumeration { return value; }
constexpr auto operator-> () const & noexcept -> const TEnumeration* {
return &value;
}
constexpr auto operator++() & noexcept -> Iterator {
value = static_cast<TEnumeration>(1 + static_cast<type>(value));
return *this;
}
[[nodiscard]] constexpr auto operator==(Iterator i) -> bool { return i.value == value; }
[[nodiscard]] constexpr auto operator!=(Iterator i) -> bool { return i.value != value; }
};
constexpr auto begin() const noexcept -> Iterator { return Iterator{}; }
constexpr auto cbegin() const noexcept -> Iterator { return Iterator{}; }
constexpr auto end() const noexcept -> Iterator { return Iterator{TSize}; }
constexpr auto cend() const noexcept -> Iterator { return Iterator{TSize}; }
[[nodiscard]] constexpr auto size() const noexcept -> type {
return static_cast<type>(TSize);
}
};
The usage:
#include <iostream>
int main(int, char**) {
auto range = EnumRange<Vehicle>{};
std::cout << static_cast<int>(range.size()) << std::endl;
for (auto v : range) std::cout << to_string(v) << std::endl;
}
As you saw in the first test code, you can go from a numeric value to an enumeration by using static_cast
. However, it assumes that you have some value that is valid for the enumeration. With the same assumptions of the range, we can write our own checked variant:
#include <stdexcept>
#include <type_traits>
template <typename TEnumeration, TEnumeration TSize = TEnumeration::SIZE>
[[nodiscard]] constexpr auto checked_enum_cast(
std::underlying_type_t<TEnumeration> numeric_value) noexcept(false)
-> TEnumeration {
using type = std::underlying_type_t<TEnumeration>;
if constexpr (std::is_signed_v<type>)
if (numeric_value < 0) throw std::out_of_range{"Negative value"};
if (numeric_value >= static_cast<type>(TSize)) throw std::out_of_range{"Value too large"};
return static_cast<TEnumeration>(numeric_value);
}
To use this, you can write:
try {
std::cout << to_string(checked_enum_cast<Vehicle>(1)) << std::endl;
std::cout << to_string(checked_enum_cast<Vehicle>(2)) << std::endl;
} catch (const std::out_of_range& e) {
std::cout << e.what() << std::endl;
}
Note: If one would live in an exception-free world, one could return std::nullopt
and change the return type to std::optional<TEnumeration>
instead.
All code combined + execution at Compiler Explorer
Please note that the iterator can be refined, however, I ain't an expert in the details. (and for looping, it doesn't matter, if you ever want to use it for an algorithm it could)