mirror of
https://github.com/aantron/better-enums.git
synced 2025-12-07 17:26:45 +08:00
With this change, the underlying type can be a non-integral type that provides conversions to and from an integral type. See the test at test/cxxtest/underlying.h for some examples - though they are more verbose than strictly necessary, for testing needs. Move constructors in underlying types are not supported. It has been difficult so far to get constexpr code not to select the move constructor, which is generally not constexpr, for various operations.
300 lines
11 KiB
C++
300 lines
11 KiB
C++
#include <stdexcept>
|
|
#include <cxxtest/TestSuite.h>
|
|
#include <enum.h>
|
|
|
|
#define static_assert_1(e) static_assert(e, #e)
|
|
|
|
#ifdef BETTER_ENUMS_STRICT_CONVERSION
|
|
# define STRICT 1
|
|
#else
|
|
# define STRICT 0
|
|
#endif
|
|
|
|
|
|
|
|
ENUM(Channel, short, Red, Green, Blue)
|
|
ENUM(Depth, short, HighColor = 40, TrueColor = 20)
|
|
ENUM(Compression, short, None, Huffman, Default = Huffman)
|
|
|
|
|
|
|
|
namespace test {
|
|
|
|
ENUM(Namespaced, short, One, Two)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using _ENUM_HAVE_CONSTEXPR as a proxy for C++11 support. This should be
|
|
// changed to be more precise in the future.
|
|
#ifdef _ENUM_HAVE_CONSTEXPR
|
|
|
|
#include <type_traits>
|
|
|
|
// Type properties.
|
|
static_assert_1(std::is_class<Channel>());
|
|
static_assert_1(std::is_standard_layout<Channel>());
|
|
static_assert_1(std::is_literal_type<Channel>());
|
|
|
|
|
|
|
|
// Member type properties and identities.
|
|
static_assert_1(std::is_integral<Channel::_integral>());
|
|
static_assert_1(std::is_enum<Channel::_enumerated>());
|
|
|
|
static_assert_1((std::is_same<short, Channel::_integral>()));
|
|
static_assert_1((std::is_same<
|
|
short, std::underlying_type<Channel::_enumerated>::type>()));
|
|
|
|
static_assert_1(!(std::is_same<int, Channel::_integral>()));
|
|
static_assert_1(!(std::is_same<
|
|
int, std::underlying_type<Channel::_enumerated>::type>()));
|
|
|
|
static_assert_1(sizeof(Channel) == sizeof(short));
|
|
static_assert_1(alignof(Channel) == alignof(short));
|
|
|
|
static_assert_1((std::is_same<decltype(Channel::Red), Channel::_enumerated>()));
|
|
|
|
|
|
|
|
// Supported constructors.
|
|
|
|
#ifdef __clang__
|
|
static_assert_1(std::is_trivially_copyable<Channel>());
|
|
#endif
|
|
|
|
static_assert_1((std::is_constructible<Channel, Channel::_enumerated>()));
|
|
static_assert_1(!(std::is_constructible<Channel, Channel::_integral>()));
|
|
static_assert_1(!(std::is_constructible<Channel, Depth>()));
|
|
|
|
// Commented out temporarily due to GCC 4.7- bug.
|
|
// static_assert_1(!std::is_default_constructible<Channel>());
|
|
|
|
|
|
|
|
// Intended implicit conversions.
|
|
static_assert_1((std::is_convertible<Channel::_enumerated, Channel>()));
|
|
|
|
// Regrettable implicit conversions.
|
|
static_assert_1((std::is_convertible<decltype(Channel::Red),
|
|
Channel::_integral>()));
|
|
|
|
// Disallowed implicit conversions.
|
|
static_assert_1(!(std::is_convertible<Channel::_integral, Channel>()));
|
|
static_assert_1(!(std::is_convertible<Depth, Channel>()));
|
|
static_assert_1(!(std::is_convertible<Channel, Depth>()));
|
|
|
|
// Controllable implicit conversions.
|
|
static_assert_1(
|
|
(std::is_convertible<Channel, Channel::_integral>() != STRICT));
|
|
static_assert_1(
|
|
(std::is_convertible<Channel, Channel::_enumerated>() != STRICT));
|
|
static_assert_1(
|
|
(std::is_convertible<decltype(+Channel::Red), Channel::_integral>()
|
|
!= STRICT));
|
|
static_assert_1(
|
|
(std::is_convertible<decltype(Channel::_values()[0]), Channel::_integral>()
|
|
!= STRICT));
|
|
|
|
|
|
|
|
static_assert_1((+Depth::HighColor)._to_integral() == 40);
|
|
static_assert_1(Depth::_from_integral(40) == +Depth::HighColor);
|
|
static_assert_1(Depth::_from_integral_unchecked(40) == +Depth::HighColor);
|
|
static_assert_1(Depth::_from_integral_nothrow(40));
|
|
static_assert_1(*Depth::_from_integral_nothrow(40) == +Depth::HighColor);
|
|
static_assert_1(Depth::_is_valid(40));
|
|
|
|
static_assert_1(Channel::_from_string("Green") == +Channel::Green);
|
|
static_assert_1(Channel::_from_string_nocase("green") == +Channel::Green);
|
|
static_assert_1(*Channel::_from_string_nothrow("Green") == +Channel::Green);
|
|
static_assert_1(
|
|
*Channel::_from_string_nocase_nothrow("green") == +Channel::Green);
|
|
static_assert_1(Channel::_is_valid("Green"));
|
|
static_assert_1(Channel::_is_valid_nocase("green"));
|
|
|
|
static_assert_1(Channel::_size == 3);
|
|
static_assert_1(Channel::_values().size() == 3);
|
|
static_assert_1(*Channel::_values().begin() == +Channel::Red);
|
|
static_assert_1(*(Channel::_values().end() - 1) == +Channel::Blue);
|
|
static_assert_1(Channel::_values()[1] == +Channel::Green);
|
|
|
|
#ifdef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
|
|
|
constexpr bool same_string(const char *r, const char *s, size_t index = 0)
|
|
{
|
|
return
|
|
r[index] == '\0' ? s[index] == '\0' :
|
|
s[index] == '\0' ? false :
|
|
r[index] != s[index] ? false :
|
|
same_string(r, s, index + 1);
|
|
}
|
|
|
|
static_assert_1(same_string((+Depth::HighColor)._to_string(), "HighColor"));
|
|
static_assert_1(Depth::_names().size() == 2);
|
|
static_assert_1(same_string(*Depth::_names().begin(), "HighColor"));
|
|
static_assert_1(same_string(*(Depth::_names().end() - 1), "TrueColor"));
|
|
static_assert_1(same_string(Depth::_names()[0], "HighColor"));
|
|
|
|
#endif // #ifdef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
|
|
|
#endif // #ifdef _ENUM_HAVE_CONSTEXPR
|
|
|
|
|
|
|
|
// Run-time testing.
|
|
class EnumTests : public CxxTest::TestSuite {
|
|
public:
|
|
void test_constant_values()
|
|
{
|
|
TS_ASSERT_EQUALS((+Channel::Red)._to_integral(), 0);
|
|
TS_ASSERT_EQUALS((+Channel::Green)._to_integral(), 1);
|
|
TS_ASSERT_EQUALS((+Channel::Blue)._to_integral(), 2);
|
|
TS_ASSERT_EQUALS((+Depth::HighColor)._to_integral(), 40);
|
|
TS_ASSERT_EQUALS((+Depth::TrueColor)._to_integral(), 20);
|
|
}
|
|
|
|
void test_integral_conversions()
|
|
{
|
|
TS_ASSERT_EQUALS(Channel::_from_integral(1), +Channel::Green);
|
|
TS_ASSERT_DIFFERS(Channel::_from_integral(1), +Channel::Blue);
|
|
TS_ASSERT_EQUALS(Channel::_from_integral_unchecked(1), +Channel::Green);
|
|
TS_ASSERT_DIFFERS(Channel::_from_integral_unchecked(1), +Channel::Blue);
|
|
|
|
TS_ASSERT_THROWS(Channel::_from_integral(3), std::runtime_error);
|
|
TS_ASSERT_THROWS_NOTHING(Channel::_from_integral_unchecked(3));
|
|
|
|
better_enums::optional<Channel> maybe_channel =
|
|
Channel::_from_integral_nothrow(2);
|
|
TS_ASSERT(maybe_channel);
|
|
TS_ASSERT_EQUALS(*maybe_channel, +Channel::Blue);
|
|
TS_ASSERT(!Channel::_from_integral_nothrow(3));
|
|
|
|
TS_ASSERT(Channel::_is_valid((Channel::_integral)0));
|
|
TS_ASSERT(Channel::_is_valid(1));
|
|
TS_ASSERT(Channel::_is_valid(2));
|
|
TS_ASSERT(!Channel::_is_valid(3));
|
|
}
|
|
|
|
void test_string_conversions()
|
|
{
|
|
TS_ASSERT_EQUALS(strcmp((+Channel::Green)._to_string(), "Green"), 0);
|
|
TS_ASSERT_EQUALS(strcmp((+Depth::HighColor)._to_string(),
|
|
"HighColor"), 0);
|
|
|
|
TS_ASSERT_EQUALS(Channel::_from_string("Green"), +Channel::Green);
|
|
TS_ASSERT_DIFFERS(Channel::_from_string("Green"), +Channel::Blue);
|
|
TS_ASSERT_EQUALS(Channel::_from_string("Blue"), +Channel::Blue);
|
|
TS_ASSERT_DIFFERS(Channel::_from_string("Blue"), +Channel::Green);
|
|
TS_ASSERT_THROWS(Channel::_from_string("green"), std::runtime_error);
|
|
|
|
better_enums::optional<Channel> maybe_channel =
|
|
Channel::_from_string_nothrow("Green");
|
|
TS_ASSERT(maybe_channel);
|
|
TS_ASSERT_EQUALS(*maybe_channel, +Channel::Green);
|
|
TS_ASSERT(!Channel::_from_string_nothrow("green"));
|
|
|
|
TS_ASSERT_EQUALS(Channel::_from_string_nocase("green"),
|
|
+Channel::Green);
|
|
TS_ASSERT_DIFFERS(Channel::_from_string_nocase("green"),
|
|
+Channel::Blue);
|
|
TS_ASSERT_EQUALS(Channel::_from_string_nocase("blue"),
|
|
+Channel::Blue);
|
|
TS_ASSERT_DIFFERS(Channel::_from_string_nocase("blue"),
|
|
+Channel::Green);
|
|
TS_ASSERT_THROWS(Channel::_from_string_nocase("a"), std::runtime_error);
|
|
|
|
maybe_channel = Channel::_from_string_nocase_nothrow("green");
|
|
TS_ASSERT(maybe_channel);
|
|
TS_ASSERT_EQUALS(*maybe_channel, +Channel::Green);
|
|
TS_ASSERT(!Channel::_from_string_nocase_nothrow("greeen"));
|
|
|
|
TS_ASSERT(Channel::_is_valid("Green"));
|
|
TS_ASSERT(!Channel::_is_valid("green"));
|
|
TS_ASSERT(Channel::_is_valid_nocase("green"));
|
|
TS_ASSERT(!Channel::_is_valid_nocase("greeen"));
|
|
}
|
|
|
|
void test_value_iterable()
|
|
{
|
|
TS_ASSERT_EQUALS(Channel::_size, 3);
|
|
TS_ASSERT_EQUALS(Depth::_size, 2);
|
|
TS_ASSERT_EQUALS(Channel::_values().size(), Channel::_size);
|
|
TS_ASSERT_EQUALS(*Channel::_values().begin(), +Channel::Red);
|
|
TS_ASSERT_EQUALS(*(Channel::_values().begin() + 1), +Channel::Green);
|
|
TS_ASSERT_EQUALS(*(Channel::_values().begin() + 2), +Channel::Blue);
|
|
TS_ASSERT_EQUALS(Channel::_values()[1], +Channel::Green);
|
|
TS_ASSERT_EQUALS(Channel::_values()[2], +Channel::Blue);
|
|
|
|
Channel::_value_iterator value_iterator = Channel::_values().begin();
|
|
TS_ASSERT_EQUALS(*value_iterator, +Channel::Red);
|
|
TS_ASSERT_DIFFERS(value_iterator, Channel::_values().end());
|
|
|
|
++value_iterator;
|
|
TS_ASSERT_EQUALS(*value_iterator, +Channel::Green);
|
|
TS_ASSERT_DIFFERS(value_iterator, Channel::_values().end());
|
|
|
|
++value_iterator;
|
|
TS_ASSERT_EQUALS(*value_iterator, +Channel::Blue);
|
|
TS_ASSERT_DIFFERS(value_iterator, Channel::_values().end());
|
|
|
|
++value_iterator;
|
|
TS_ASSERT_EQUALS(value_iterator, Channel::_values().end());
|
|
}
|
|
|
|
void test_name_iterable()
|
|
{
|
|
TS_ASSERT_EQUALS(Channel::_names().size(), Channel::_size);
|
|
TS_ASSERT_EQUALS(strcmp(*Channel::_names().begin(), "Red"), 0);
|
|
TS_ASSERT_EQUALS(strcmp(Channel::_names()[0], "Red"), 0);
|
|
TS_ASSERT_EQUALS(strcmp(Depth::_names()[0], "HighColor"), 0);
|
|
|
|
Channel::_name_iterator name_iterator = Channel::_names().begin();
|
|
TS_ASSERT_EQUALS(strcmp(*name_iterator, "Red"), 0);
|
|
TS_ASSERT_DIFFERS(name_iterator, Channel::_names().end());
|
|
|
|
++name_iterator;
|
|
TS_ASSERT_EQUALS(strcmp(*name_iterator, "Green"), 0);
|
|
TS_ASSERT_DIFFERS(name_iterator, Channel::_names().end());
|
|
|
|
++name_iterator;
|
|
TS_ASSERT_EQUALS(strcmp(*name_iterator, "Blue"), 0);
|
|
TS_ASSERT_DIFFERS(name_iterator, Channel::_names().end());
|
|
|
|
++name_iterator;
|
|
TS_ASSERT_EQUALS(name_iterator, Channel::_names().end());
|
|
}
|
|
|
|
void test_type_name()
|
|
{
|
|
TS_ASSERT_EQUALS(strcmp(Channel::_name(), "Channel"), 0);
|
|
TS_ASSERT_EQUALS(strcmp(Depth::_name(), "Depth"), 0);
|
|
TS_ASSERT_EQUALS(strcmp(Compression::_name(), "Compression"), 0);
|
|
}
|
|
|
|
void test_alias()
|
|
{
|
|
TS_ASSERT_EQUALS(Compression::Default, Compression::Huffman);
|
|
}
|
|
|
|
void test_namespaced()
|
|
{
|
|
TS_ASSERT_EQUALS((+test::Namespaced::One)._to_integral(), 0);
|
|
TS_ASSERT_EQUALS(test::Namespaced::_from_integral(0),
|
|
+test::Namespaced::One);
|
|
TS_ASSERT_EQUALS(strcmp((+test::Namespaced::One)._to_string(), "One"),
|
|
0);
|
|
TS_ASSERT_EQUALS(test::Namespaced::_from_string("Two"),
|
|
+test::Namespaced::Two);
|
|
|
|
TS_ASSERT_EQUALS(test::Namespaced::_values()[0],
|
|
+test::Namespaced::One);
|
|
TS_ASSERT_EQUALS(strcmp(test::Namespaced::_names()[0], "One"), 0);
|
|
|
|
TS_ASSERT_EQUALS(*test::Namespaced::_values().begin(),
|
|
+test::Namespaced::One);
|
|
TS_ASSERT_EQUALS(strcmp(*test::Namespaced::_names().begin(), "One"), 0);
|
|
}
|
|
};
|