|
|
|
@ -11,11 +11,13 @@
|
|
|
|
|
|
|
|
|
|
/** @brief Support for "named" enumerations
|
|
|
|
|
*
|
|
|
|
|
* For tables which map string names to enum values, provide a NamedEnumTable
|
|
|
|
|
* which hangs on to an initializer_list of pairs of names and values.
|
|
|
|
|
* This table can be used with find() to map names to values, or
|
|
|
|
|
* values to names. A convenience function smash() is provided to help
|
|
|
|
|
* in printing integer (underlying) values of an enum.
|
|
|
|
|
* When a string needs to be one specific string out of a set of
|
|
|
|
|
* alternatives -- one "name" from an enumerated set -- then it
|
|
|
|
|
* is useful to have an **enum type** for the enumeration so that
|
|
|
|
|
* C++ code can work with the (strong) type of the enum, while
|
|
|
|
|
* the string can be used for human-readable interaction.
|
|
|
|
|
* The `NamedEnumTable<E>` template provides support for naming
|
|
|
|
|
* values of an enum.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifndef UTILS_NAMEDENUM_H
|
|
|
|
@ -27,7 +29,100 @@
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
/** @brief Type for collecting parts of a named enum. */
|
|
|
|
|
/** @brief Type for collecting parts of a named enum.
|
|
|
|
|
*
|
|
|
|
|
* The `NamedEnumTable<E>` template provides support for naming
|
|
|
|
|
* values of an enum. It supports mapping strings to enum values
|
|
|
|
|
* and mapping enum values to strings.
|
|
|
|
|
*
|
|
|
|
|
* ## Example
|
|
|
|
|
*
|
|
|
|
|
* Suppose we have code where there are three alternatives; it is
|
|
|
|
|
* useful to have a strong type to make the alternatives visible
|
|
|
|
|
* in that code, so the compiler can help check:
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* enum class MilkshakeSize { None, Small, Large };
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* In a switch() statement, the compiler will check that all kinds
|
|
|
|
|
* of milkshakes are dealt with; we can pass a MilkshakeSize to
|
|
|
|
|
* a function and rest assured that nobody will call that function
|
|
|
|
|
* with a silly value, like `1`.
|
|
|
|
|
*
|
|
|
|
|
* There is no relation between the C++ identifiers used, and
|
|
|
|
|
* any I/O related to that enumeration. In other words,
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* std::cout << MilkshakeSize::Small;
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* Will **not** print out "Small", or "small", or 1. It won't even
|
|
|
|
|
* compile, because there is no mapping of the enum values to
|
|
|
|
|
* something that can be output.
|
|
|
|
|
*
|
|
|
|
|
* By making a `NamedEnumTable<MilkshakeSize>` we can define a mapping
|
|
|
|
|
* between strings (names) and enum values, so that we can easily
|
|
|
|
|
* output the human-readable name, and also take string input
|
|
|
|
|
* and convert it to an enum value. Suppose we have a function
|
|
|
|
|
* `milkshakeSizeNames()` that returns a reference to such a table,
|
|
|
|
|
* then we can use `find()` to map enums-to-names and names-to-enums.
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* const auto& names = milkshakeSizeNames();
|
|
|
|
|
* MilkshakeSize sz{ MilkshakeSize::Large };
|
|
|
|
|
* std::cout << names.find(sz); // Probably "large"
|
|
|
|
|
*
|
|
|
|
|
* bool ok;
|
|
|
|
|
* sz = names.find( "small", ok ); // Probably MilkshakeSize::Small
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* ## Usage
|
|
|
|
|
*
|
|
|
|
|
* It is recommended to use a static const declaration for the table;
|
|
|
|
|
* typical use will define a function that returns a reference to
|
|
|
|
|
* the table, for shared use.
|
|
|
|
|
*
|
|
|
|
|
* The constructor for a table takes an initializer_list; each element
|
|
|
|
|
* of the initializer_list is a **pair** consisting of a name and
|
|
|
|
|
* an associated enum value. The names should be QStrings. For each enum
|
|
|
|
|
* value that is listed, the canonical name should come **first** in the
|
|
|
|
|
* table, so that printing the enum values gives the canonical result.
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* static const NamedEnumTable<MilkshakeSize>& milkshakeSizeNames()
|
|
|
|
|
* {
|
|
|
|
|
* static NamedEnumTable<MilkshakeSize> n { // Initializer list for n
|
|
|
|
|
* { "large", MilkshakeSize::Large }, // One pair of name-and-value
|
|
|
|
|
* { "small", MilkshakeSize::Small },
|
|
|
|
|
* { "big", MilkshakeSize::Large }
|
|
|
|
|
* };
|
|
|
|
|
* return n;
|
|
|
|
|
* }
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* The function `eNames()`, above, returns a reference to a name table
|
|
|
|
|
* for the enum (presumably an enum class) `E`. It is possible to have
|
|
|
|
|
* more than one table for an enum, or to make the table locally,
|
|
|
|
|
* but **usually** you want one definitive table of names and values.
|
|
|
|
|
* The `eNames()` function gives you that definitive table. In Calamres
|
|
|
|
|
* code, such functions are usually named after the underlying enum.
|
|
|
|
|
*
|
|
|
|
|
* Using this particular table, looking up "large" will return `MilkshakeSize::Large`,
|
|
|
|
|
* looking up "big" will **also** return `MilkshakeSize::Large`, looking up "derp"
|
|
|
|
|
* will return `MilkshakeSize::Large` (because that is the first value in the table)
|
|
|
|
|
* but will set the boolean `ok` parameter to false. Conversely, looking
|
|
|
|
|
* up `MilkshakeSize::Large` will return "large" (never "big").
|
|
|
|
|
*
|
|
|
|
|
* Note that this particular table does **not** name MilkshakeSize::None,
|
|
|
|
|
* so it is probably wrong: you can't get a string for that enum
|
|
|
|
|
* value, and no string will map to MilkshakeSize::None either.
|
|
|
|
|
* In general, tables should cover all of the enum values.
|
|
|
|
|
*
|
|
|
|
|
* Passing an empty initializer_list to the constructor is supported,
|
|
|
|
|
* but will cause UB if the table is ever used for looking up a string.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
template < typename T >
|
|
|
|
|
struct NamedEnumTable
|
|
|
|
|
{
|
|
|
|
@ -43,7 +138,9 @@ struct NamedEnumTable
|
|
|
|
|
* Use braced-initialisation for NamedEnum, and remember that the
|
|
|
|
|
* elements of the list are **pairs**, e.g.
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* static const NamedEnumTable<Colors> c{ {"red", Colors::Red } };
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
NamedEnumTable( const std::initializer_list< pair_t >& v )
|
|
|
|
|
: table( v )
|
|
|
|
@ -55,10 +152,12 @@ struct NamedEnumTable
|
|
|
|
|
*
|
|
|
|
|
* Searches case-insensitively.
|
|
|
|
|
*
|
|
|
|
|
* If the name @p s is not found, @p ok is set to false and
|
|
|
|
|
* If the name @p s is not found, @p ok is set to @c false and
|
|
|
|
|
* the first enum value in the table is returned. Otherwise,
|
|
|
|
|
* @p ok is set to true and the corresponding value is returned.
|
|
|
|
|
*
|
|
|
|
|
* @p ok is set to @c true and the corresponding value is returned.
|
|
|
|
|
* Use the output value of @p ok to determine if the lookup was
|
|
|
|
|
* successful: there is otherwise no sensible way to distinguish
|
|
|
|
|
* found-the-name-of-the-first-item from not-found.
|
|
|
|
|
*/
|
|
|
|
|
enum_t find( const string_t& s, bool& ok ) const
|
|
|
|
|
{
|
|
|
|
@ -75,11 +174,17 @@ struct NamedEnumTable
|
|
|
|
|
return table.begin()->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief Find a value @p s in the table.
|
|
|
|
|
/** @brief Find a value @p s in the table and return its name.
|
|
|
|
|
*
|
|
|
|
|
* If the value @p s is not found, @p ok is set to false and
|
|
|
|
|
* an empty string is returned. Otherwise, @p is set to true
|
|
|
|
|
* and the corresponding name is returned.
|
|
|
|
|
* If @p s is an enum value in the table, return the corresponding
|
|
|
|
|
* name (the first name with that value, if there are aliases)
|
|
|
|
|
* and set @p ok to @c true.
|
|
|
|
|
*
|
|
|
|
|
* If the value @p s is not found, @p ok is set to @c false and
|
|
|
|
|
* an empty string is returned. This indicates that the table does
|
|
|
|
|
* not cover all of the values * in `enum_t` (and @p s is one
|
|
|
|
|
* of them), **or** that the passed-in value of @p s is
|
|
|
|
|
* not a legal value, e.g. via a static_cast<enum_t>.
|
|
|
|
|
*/
|
|
|
|
|
string_t find( enum_t s, bool& ok ) const
|
|
|
|
|
{
|
|
|
|
@ -98,7 +203,10 @@ struct NamedEnumTable
|
|
|
|
|
|
|
|
|
|
/** @brief Find a value @p s in the table and return its name.
|
|
|
|
|
*
|
|
|
|
|
* Returns emptry string if the value is not found.
|
|
|
|
|
* Returns an empty string if the value @p s is not found (this
|
|
|
|
|
* indicates that the table does not cover all of the values
|
|
|
|
|
* in `enum_t`, **or** that the passed-in value of @p s is
|
|
|
|
|
* not a legal value, e.g. via a static_cast<enum_t>).
|
|
|
|
|
*/
|
|
|
|
|
string_t find( enum_t s ) const
|
|
|
|
|
{
|
|
|
|
@ -107,7 +215,24 @@ struct NamedEnumTable
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** @brief Smashes an enum value to its underlying type. */
|
|
|
|
|
/** @brief Smashes an enum value to its underlying type.
|
|
|
|
|
*
|
|
|
|
|
* While an enum **class** is not an integral type, and its values can't be
|
|
|
|
|
* printed or treated like an integer (like an old-style enum can),
|
|
|
|
|
* the underlying type **is** integral. This template function
|
|
|
|
|
* returns the value of an enum value, in its underlying type.
|
|
|
|
|
* This can be useful for debugging purposes, e.g.
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* MilkshakeSize sz{ MilkshakeSize::None };
|
|
|
|
|
* std::cout << milkshakeSizeNames().find( sz ) << smash( sz );
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* This will print both the name and the underlying integer for the
|
|
|
|
|
* value; assuming the table from the example is used, there is
|
|
|
|
|
* no name for MilkshakeSize::None, so it will print an empty string,
|
|
|
|
|
* followed by the integral representation -- probably a 0.
|
|
|
|
|
*/
|
|
|
|
|
template < typename E >
|
|
|
|
|
constexpr typename std::underlying_type< E >::type
|
|
|
|
|
smash( const E e )
|
|
|
|
|