Experimental generalization of underlying types.

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.
This commit is contained in:
Anton Bachin 2015-06-11 20:36:52 -05:00
parent b037d8b5eb
commit 4314ad3fd3
4 changed files with 342 additions and 70 deletions

View File

@ -7,4 +7,4 @@ Thanks to:
Tony Van Eerd
Ben Alex
SlashLife (freenode)
Simon Stienen

256
enum.h
View File

@ -14,26 +14,43 @@
#ifdef BETTER_ENUMS_CONSTEXPR
# define BETTER_ENUMS__HAVE_CONSTEXPR
#else
# ifdef __GNUC__
# ifdef __clang__
# if __has_feature(cxx_constexpr)
#ifdef __GNUC__
# ifdef __clang__
# if __has_feature(cxx_constexpr)
# define BETTER_ENUMS__HAVE_CONSTEXPR
# endif
# if (__clang_major__ > 2) || \
(__clang_major__ == 2) && (__clang_minor__ >= 9)
# define BETTER_ENUMS__HAVE_LONG_LONG
# define BETTER_ENUMS__HAVE_NEW_CHAR_TYPES
# endif
# else
# if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L
# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6))
# define BETTER_ENUMS__HAVE_CONSTEXPR
# endif
# else
# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6))
# if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L
# define BETTER_ENUMS__HAVE_CONSTEXPR
# endif
# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3))
# define BETTER_ENUMS__HAVE_LONG_LONG
# endif
# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))
# define BETTER_ENUMS__HAVE_NEW_CHAR_TYPES
# endif
# endif
# endif
#endif
#ifdef _MSC_VER
# if _MSC_VER >= 1600
# define BETTER_ENUMS__HAVE_LONG_LONG
# endif
#endif
#ifdef BETTER_ENUMS_CONSTEXPR
# define BETTER_ENUMS__HAVE_CONSTEXPR
#endif
#ifdef BETTER_ENUMS_NO_CONSTEXPR
# if defined(BETTER_ENUMS__HAVE_CONSTEXPR)
# ifdef BETTER_ENUMS__HAVE_CONSTEXPR
# undef BETTER_ENUMS__HAVE_CONSTEXPR
# endif
#endif
@ -54,10 +71,6 @@
# define BETTER_ENUMS__NULLPTR NULL
#endif
#ifndef __GNUC__
# define strcasecmp stricmp
#endif
#ifdef BETTER_ENUMS_MACRO_FILE
@ -413,6 +426,56 @@ inline void _trim_names(const char * const *raw_names,
}
}
// This template is unfortunately necessary (?) due to the lack of <type_traits>
// in C++98. <With type_traits>, this template could be replaced with a
// combination of std::conditional and std::is_integral.
template <typename T>
struct representation { typedef typename T::integral_representation type; };
template <> struct representation<bool> { typedef bool type; };
template <> struct representation<char> { typedef char type; };
template <> struct representation<wchar_t> { typedef wchar_t type; };
template <> struct representation<signed char> { typedef signed char type; };
template <> struct representation<unsigned char>
{ typedef unsigned char type; };
template <> struct representation<short> { typedef short type; };
template <> struct representation<unsigned short>
{ typedef unsigned short type; };
template <> struct representation<int> { typedef int type; };
template <> struct representation<unsigned int> { typedef unsigned int type; };
template <> struct representation<long> { typedef long type; };
template <> struct representation<unsigned long>
{ typedef unsigned long type; };
#ifdef BETTER_ENUMS__HAVE_LONG_LONG
template <> struct representation<long long> { typedef long long type; };
template <> struct representation<unsigned long long>
{ typedef unsigned long long type; };
#endif
#ifdef BETTER_ENUMS__HAVE_NEW_CHAR_TYPES
template <> struct representation<char16_t> { typedef char16_t type; };
template <> struct representation<char32_t> { typedef char32_t type; };
#endif
template <typename T>
struct underlying_traits {
typedef typename representation<T>::type integral_representation;
BETTER_ENUMS__CONSTEXPR static integral_representation
to_integral(const T &v) { return (integral_representation)v; }
BETTER_ENUMS__CONSTEXPR static T
from_integral(integral_representation n) { return T(n); }
BETTER_ENUMS__CONSTEXPR static bool
are_equal(const T& u, const T& v) { return u == v; }
};
} // namespace better_enums
@ -469,35 +532,67 @@ constexpr const char *_final_ ## index = \
// TODO Convert integral to underlying only at the last possible moment, if the
// integral value is valid. This should prevent unexpected behavior. For this to
// work, the underlying values array must store the integral equivalents. That
// would also eliminate the need for "are_equal", but would increase overhead
// during iteration.
// TODO Choose the right return type semantics once the _values array
// representation is chosen: values if integral, const references if underlying.
#define BETTER_ENUMS__TYPE(SetUnderlyingType, SwitchType, GenerateSwitchType, \
GenerateStrings, ToStringConstexpr, \
DeclareInitialize, DefineInitialize, CallInitialize,\
Enum, Integral, ...) \
Enum, Underlying, ...) \
\
namespace better_enums { \
namespace _data_ ## Enum { \
\
BETTER_ENUMS__ID(GenerateSwitchType(Integral, __VA_ARGS__)) \
BETTER_ENUMS__ID(GenerateSwitchType(Underlying, __VA_ARGS__)) \
\
} \
} \
\
class Enum { \
private: \
typedef ::better_enums::optional<Enum> _optional; \
typedef ::better_enums::optional<std::size_t> _optional_index; \
typedef ::better_enums::optional<Enum> _optional; \
typedef ::better_enums::optional<std::size_t> _optional_index; \
typedef ::better_enums::underlying_traits<Underlying> _traits; \
\
public: \
enum _enumerated SetUnderlyingType(Integral) { __VA_ARGS__ }; \
typedef Integral _integral; \
typedef Underlying _underlying; \
typedef _traits::integral_representation _integral; \
\
BETTER_ENUMS__CONSTEXPR Enum(_enumerated value) : _value(value) { } \
enum _enumerated SetUnderlyingType(Underlying) { __VA_ARGS__ }; \
\
BETTER_ENUMS__CONSTEXPR Enum(_enumerated value) : \
_value(_traits::from_integral(value)) { } \
\
BETTER_ENUMS__CONSTEXPR operator SwitchType(Enum)() const \
{ \
return (SwitchType(Enum))_value; \
return (SwitchType(Enum))_traits::to_integral(_value); \
} \
\
_underlying& operator *() { return _value; } \
BETTER_ENUMS__CONSTEXPR const _underlying& operator *() const \
{ return _value; } \
\
_underlying* operator ->() { return &_value; } \
BETTER_ENUMS__CONSTEXPR const _underlying* operator ->() const \
{ return &_value; } \
\
_underlying _to_underlying() { return _value; } \
BETTER_ENUMS__CONSTEXPR const _underlying& _to_underlying() const \
{ return _value; } \
\
BETTER_ENUMS__CONSTEXPR static Enum \
_from_underlying(const _underlying &value); \
BETTER_ENUMS__CONSTEXPR static Enum \
_from_underlying_unchecked(const _underlying &value); \
BETTER_ENUMS__CONSTEXPR static _optional \
_from_underlying_nothrow(const _underlying &value); \
\
BETTER_ENUMS__CONSTEXPR _integral _to_integral() const; \
BETTER_ENUMS__CONSTEXPR static Enum _from_integral(_integral value); \
BETTER_ENUMS__CONSTEXPR static Enum \
@ -514,15 +609,15 @@ class Enum { \
BETTER_ENUMS__CONSTEXPR static _optional \
_from_string_nocase_nothrow(const char *name); \
\
BETTER_ENUMS__CONSTEXPR static bool _is_valid(_integral value); \
BETTER_ENUMS__CONSTEXPR static bool _is_valid(const _underlying &value); \
BETTER_ENUMS__CONSTEXPR static bool _is_valid(const char *name); \
BETTER_ENUMS__CONSTEXPR static bool _is_valid_nocase(const char *name); \
\
typedef ::better_enums::_Iterable<Enum> _value_iterable; \
typedef ::better_enums::_Iterable<const char*> _name_iterable; \
typedef ::better_enums::_Iterable<Enum> _value_iterable; \
typedef ::better_enums::_Iterable<const char*> _name_iterable; \
\
typedef _value_iterable::iterator _value_iterator; \
typedef _name_iterable::iterator _name_iterator; \
typedef _value_iterable::iterator _value_iterator; \
typedef _name_iterable::iterator _name_iterator; \
\
BETTER_ENUMS__CONSTEXPR static const std::size_t _size = \
BETTER_ENUMS__ID(BETTER_ENUMS__PP_COUNT(__VA_ARGS__)); \
@ -531,15 +626,18 @@ class Enum { \
BETTER_ENUMS__CONSTEXPR static _value_iterable _values(); \
ToStringConstexpr static _name_iterable _names(); \
\
_integral _value; \
_underlying _value; \
\
private: \
Enum() : _value(0) { } \
Enum() : _value(_traits::from_integral(0)) { } \
\
explicit BETTER_ENUMS__CONSTEXPR Enum(const _underlying &value) : \
_value(value) { } \
\
DeclareInitialize \
\
BETTER_ENUMS__CONSTEXPR static _optional_index \
_from_int_loop(_integral value, std::size_t index = 0); \
_from_value_loop(const _underlying &value, std::size_t index = 0); \
BETTER_ENUMS__CONSTEXPR static _optional_index \
_from_string_loop(const char *name, std::size_t index = 0); \
BETTER_ENUMS__CONSTEXPR static _optional_index \
@ -565,18 +663,40 @@ operator +(Enum::_enumerated enumerated) \
return (Enum)enumerated; \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_integral Enum::_to_integral() const \
BETTER_ENUMS__CONSTEXPR inline Enum \
Enum::_from_underlying(const _underlying &value) \
{ \
return _value; \
return \
::better_enums::_or_throw(_from_underlying_nothrow(value), \
"Enum::_from_underlying: invalid argument"); \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum \
Enum::_from_integral_unchecked(Enum::_integral value) \
Enum::_from_underlying_unchecked(const _underlying &value) \
{ \
return Enum(value); \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_optional \
Enum::_from_underlying_nothrow(const _underlying &value) \
{ \
return \
::better_enums::_map_index<Enum>(BETTER_ENUMS__NS(Enum)::value_array, \
_from_value_loop(value)); \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_integral Enum::_to_integral() const \
{ \
return _traits::to_integral(_value); \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum \
Enum::_from_integral_unchecked(_integral value) \
{ \
return (_enumerated)value; \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum Enum::_from_integral(Enum::_integral value)\
BETTER_ENUMS__CONSTEXPR inline Enum Enum::_from_integral(_integral value) \
{ \
return \
::better_enums::_or_throw(_from_integral_nothrow(value), \
@ -584,11 +704,9 @@ BETTER_ENUMS__CONSTEXPR inline Enum Enum::_from_integral(Enum::_integral value)\
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_optional \
Enum::_from_integral_nothrow(Enum::_integral value) \
Enum::_from_integral_nothrow(_integral value) \
{ \
return \
::better_enums::_map_index<Enum>(BETTER_ENUMS__NS(Enum)::value_array, \
_from_int_loop(value)); \
return _from_underlying_nothrow(_traits::from_integral(value)); \
} \
\
ToStringConstexpr inline const char* Enum::_to_string() const \
@ -597,7 +715,7 @@ ToStringConstexpr inline const char* Enum::_to_string() const \
::better_enums::_or_throw( \
::better_enums::_map_index<const char*>( \
BETTER_ENUMS__NS(Enum)::name_array(), \
_from_int_loop(CallInitialize(_value))), \
_from_value_loop(CallInitialize(_value))), \
"Enum::to_string: invalid enum value"); \
} \
\
@ -632,9 +750,9 @@ Enum::_from_string_nocase_nothrow(const char *name) \
_from_string_nocase_loop(name)); \
} \
\
BETTER_ENUMS__CONSTEXPR inline bool Enum::_is_valid(Enum::_integral value) \
BETTER_ENUMS__CONSTEXPR inline bool Enum::_is_valid(const _underlying &value) \
{ \
return _from_int_loop(value); \
return _from_value_loop(value); \
} \
\
BETTER_ENUMS__CONSTEXPR inline bool Enum::_is_valid(const char *name) \
@ -667,13 +785,14 @@ ToStringConstexpr inline Enum::_name_iterable Enum::_names() \
DefineInitialize(Enum) \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_optional_index \
Enum::_from_int_loop(Enum::_integral value, std::size_t index) \
Enum::_from_value_loop(const Enum::_underlying &value, std::size_t index) \
{ \
return \
index == _size ? _optional_index() : \
BETTER_ENUMS__NS(Enum)::value_array[index]._value == value ? \
_optional_index(index) : \
_from_int_loop(value, index + 1); \
_traits::are_equal( \
BETTER_ENUMS__NS(Enum)::value_array[index]._value, value) ? \
_optional_index(index) : \
_from_value_loop(value, index + 1); \
} \
\
BETTER_ENUMS__CONSTEXPR inline Enum::_optional_index \
@ -699,26 +818,23 @@ Enum::_from_string_nocase_loop(const char *name, std::size_t index) \
} \
\
BETTER_ENUMS__CONSTEXPR inline bool operator ==(const Enum &a, const Enum &b) \
{ return a._value == b._value; } \
{ \
return \
::better_enums::underlying_traits<Enum::_underlying> \
::are_equal(a._value, b._value); \
} \
\
BETTER_ENUMS__CONSTEXPR inline bool operator !=(const Enum &a, const Enum &b) \
{ return a._value != b._value; } \
BETTER_ENUMS__CONSTEXPR inline bool operator <(const Enum &a, const Enum &b) \
{ return a._value < b._value; } \
BETTER_ENUMS__CONSTEXPR inline bool operator <=(const Enum &a, const Enum &b) \
{ return a._value <= b._value; } \
BETTER_ENUMS__CONSTEXPR inline bool operator >(const Enum &a, const Enum &b) \
{ return a._value > b._value; } \
BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
{ return a._value >= b._value; }
{ return !(a == b); }
// C++98, C++11
#define BETTER_ENUMS__CXX98_UNDERLYING_TYPE(Integral)
#define BETTER_ENUMS__CXX98_UNDERLYING_TYPE(Underlying)
// C++11
#define BETTER_ENUMS__CXX11_UNDERLYING_TYPE(Integral) \
: Integral
#define BETTER_ENUMS__CXX11_UNDERLYING_TYPE(Underlying) \
: ::better_enums::underlying_traits<Underlying>::integral_representation
// C++98, C++11
#define BETTER_ENUMS__REGULAR_ENUM_SWITCH_TYPE(Type) \
@ -729,11 +845,13 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
BETTER_ENUMS__NS(Type)::EnumClassForSwitchStatements
// C++98, C++11
#define BETTER_ENUMS__REGULAR_ENUM_SWITCH_TYPE_GENERATE(Integral, ...)
#define BETTER_ENUMS__REGULAR_ENUM_SWITCH_TYPE_GENERATE(Underlying, ...)
// C++11
#define BETTER_ENUMS__ENUM_CLASS_SWITCH_TYPE_GENERATE(Integral, ...) \
enum class EnumClassForSwitchStatements : Integral { __VA_ARGS__ };
#define BETTER_ENUMS__ENUM_CLASS_SWITCH_TYPE_GENERATE(Underlying, ...) \
enum class EnumClassForSwitchStatements : \
::better_enums::underlying_traits<Underlying>::integral_representation \
{ __VA_ARGS__ };
// C++98
#define BETTER_ENUMS__CXX98_TRIM_STRINGS_ARRAYS(Enum, ...) \
@ -878,7 +996,7 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
BETTER_ENUMS__DO_CALL_INITIALIZE
#endif
#define ENUM(Enum, Integral, ...) \
#define ENUM(Enum, Underlying, ...) \
BETTER_ENUMS__ID(BETTER_ENUMS__TYPE( \
BETTER_ENUMS__CXX11_UNDERLYING_TYPE, \
BETTER_ENUMS__DEFAULT_SWITCH_TYPE, \
@ -888,9 +1006,9 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
BETTER_ENUMS__DEFAULT_DECLARE_INITIALIZE, \
BETTER_ENUMS__DEFAULT_DEFINE_INITIALIZE, \
BETTER_ENUMS__DEFAULT_CALL_INITIALIZE, \
Enum, Integral, __VA_ARGS__))
Enum, Underlying, __VA_ARGS__))
#define SLOW_ENUM(Enum, Integral, ...) \
#define SLOW_ENUM(Enum, Underlying, ...) \
BETTER_ENUMS__ID(BETTER_ENUMS__TYPE( \
BETTER_ENUMS__CXX11_UNDERLYING_TYPE, \
BETTER_ENUMS__DEFAULT_SWITCH_TYPE, \
@ -900,11 +1018,11 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
BETTER_ENUMS__DO_NOT_DECLARE_INITIALIZE, \
BETTER_ENUMS__DO_NOT_DEFINE_INITIALIZE, \
BETTER_ENUMS__DO_NOT_CALL_INITIALIZE, \
Enum, Integral, __VA_ARGS__))
Enum, Underlying, __VA_ARGS__))
#else
#define ENUM(Enum, Integral, ...) \
#define ENUM(Enum, Underlying, ...) \
BETTER_ENUMS__ID(BETTER_ENUMS__TYPE( \
BETTER_ENUMS__CXX98_UNDERLYING_TYPE, \
BETTER_ENUMS__DEFAULT_SWITCH_TYPE, \
@ -914,7 +1032,7 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
BETTER_ENUMS__DO_DECLARE_INITIALIZE, \
BETTER_ENUMS__DO_DEFINE_INITIALIZE, \
BETTER_ENUMS__DO_CALL_INITIALIZE, \
Enum, Integral, __VA_ARGS__))
Enum, Underlying, __VA_ARGS__))
#endif

154
test/cxxtest/underlying.h Normal file
View File

@ -0,0 +1,154 @@
#include <cxxtest/TestSuite.h>
#include <enum.h>
// This test uses internal constants declared by feature testing macros in
// enum.h. They are not part of the public interface, but are used here to avoid
// duplication of definitions for C++11 and C++98.
//
// BETTER_ENUMS__CONSTEXPR is a synonym for constexpr when available, and
// othewise expands to nothing.
// Using a type trait.
struct html_color_1 {
unsigned char r, g, b;
BETTER_ENUMS__CONSTEXPR
html_color_1(unsigned char _r, unsigned char _g, unsigned char _b) :
r(_r), g(_g), b(_b) { }
};
// In C++11, can simply to struct ::better_enums::underlying_traits below.
namespace better_enums {
template <>
struct underlying_traits<html_color_1> {
typedef unsigned int integral_representation;
BETTER_ENUMS__CONSTEXPR static html_color_1 from_integral(unsigned int i)
{ return html_color_1(i >> 16 & 0xff, i >> 8 & 0xff, i & 0xff); }
BETTER_ENUMS__CONSTEXPR static unsigned int to_integral(html_color_1 v)
{ return (unsigned int)v.r << 16 | (unsigned int)v.g << 8 | v.b; }
BETTER_ENUMS__CONSTEXPR static bool
are_equal(const html_color_1 &u, const html_color_1 &v)
{ return u.r == v.r && u.g == v.g && u.b == v.b; }
};
}
ENUM(HtmlColor1, html_color_1,
darksalmon = 0xc47451, purplemimosa = 0x9e7bff, slimegreen = 0xbce954)
// Using member definitions.
struct html_color_2 {
unsigned char r, g, b;
BETTER_ENUMS__CONSTEXPR
html_color_2(unsigned char _r, unsigned char _g, unsigned char _b) :
r(_r), g(_g), b(_b) { }
typedef unsigned int integral_representation;
explicit BETTER_ENUMS__CONSTEXPR html_color_2(unsigned int i) :
r(i >> 16 & 0xff), g(i >> 8 & 0xff), b(i & 0xff) { }
BETTER_ENUMS__CONSTEXPR operator unsigned int() const
{ return (unsigned int)r << 16 | (unsigned int)g << 8 | b; }
};
// Initializers are not supported before GCC 5.1, even with a literal type and
// constexpr conversion to integer.
#ifdef BETTER_ENUMS__HAVE_CONSTEXPR
# ifdef __GNUC__
# ifdef __clang__
# define SUPPORT_INITIALIZER
# else
# if __GNUC__ >= 5
# define SUPPORT_INITIALIZER
# endif
# endif
# endif
#endif
#ifdef SUPPORT_INITIALIZER
ENUM(HtmlColor2, html_color_2,
darksalmon = 0xc47451, purplemimosa = 0x9e7bff, slimegreen = 0xbce954,
celeste = html_color_2(0x50, 0xeb, 0xec))
#else
ENUM(HtmlColor2, html_color_2,
darksalmon = 0xc47451, purplemimosa = 0x9e7bff, slimegreen = 0xbce954)
#endif
class UnderlyingTypeTests : public CxxTest::TestSuite {
public:
void test_basics_and_operators()
{
HtmlColor1 color_1 = HtmlColor1::darksalmon;
TS_ASSERT_EQUALS(color_1->r, 0xc4);
TS_ASSERT_EQUALS(color_1->g, 0x74);
TS_ASSERT_EQUALS(color_1->b, 0x51);
TS_ASSERT_EQUALS(sizeof(HtmlColor1), sizeof(html_color_1));
HtmlColor2 color_2 = HtmlColor2::purplemimosa;
TS_ASSERT_EQUALS(color_2->r, 0x9e);
TS_ASSERT_EQUALS(color_2->g, 0x7b);
TS_ASSERT_EQUALS(color_2->b, 0xff);
TS_ASSERT_EQUALS(sizeof(HtmlColor2), sizeof(html_color_2));
#ifdef SUPPORT_INITIALIZER
HtmlColor2 color_3 = HtmlColor2::celeste;
TS_ASSERT_EQUALS(color_3->r, 0x50);
TS_ASSERT_EQUALS(color_3->g, 0xeb);
TS_ASSERT_EQUALS(color_3->b, 0xec);
#endif
color_1->r = 0;
const HtmlColor1 const_color = HtmlColor1::slimegreen;
TS_ASSERT_EQUALS(const_color->r, 0xbc);
html_color_1 &color_1_reference = *color_1;
const html_color_1 &const_color_reference = *const_color;
TS_ASSERT_EQUALS(color_1_reference.r, 0);
TS_ASSERT_EQUALS(const_color_reference.r, 0xbc);
}
void test_conversion_functions()
{
HtmlColor2 color_1 = HtmlColor2::darksalmon;
html_color_2 color_1_underlying = color_1._to_underlying();
TS_ASSERT_EQUALS(color_1_underlying.r, color_1->r);
TS_ASSERT_EQUALS(color_1_underlying.g, color_1->g);
TS_ASSERT_EQUALS(color_1_underlying.b, color_1->b);
color_1_underlying = html_color_2(HtmlColor2::slimegreen);
color_1 = HtmlColor2::_from_underlying(color_1_underlying);
TS_ASSERT_EQUALS(color_1->r, color_1_underlying.r);
TS_ASSERT_EQUALS(color_1->g, color_1_underlying.g);
TS_ASSERT_EQUALS(color_1->b, color_1_underlying.b);
color_1_underlying = html_color_2(1, 2, 3);
color_1 = HtmlColor2::_from_underlying_unchecked(color_1_underlying);
TS_ASSERT_EQUALS(color_1->r, color_1_underlying.r);
TS_ASSERT_EQUALS(color_1->g, color_1_underlying.g);
TS_ASSERT_EQUALS(color_1->b, color_1_underlying.b);
}
};