From e49f0cf3bac8135465bb1258b72065f738650e18 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Feb 2021 17:03:19 +0100 Subject: [PATCH] [libcalamares] Document NamedEnum in much more detail --- src/libcalamares/utils/NamedEnum.h | 155 ++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 15 deletions(-) diff --git a/src/libcalamares/utils/NamedEnum.h b/src/libcalamares/utils/NamedEnum.h index cf56a26f2..1d839ddc4 100644 --- a/src/libcalamares/utils/NamedEnum.h +++ b/src/libcalamares/utils/NamedEnum.h @@ -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` template provides support for naming + * values of an enum. */ #ifndef UTILS_NAMEDENUM_H @@ -27,7 +29,100 @@ #include #include -/** @brief Type for collecting parts of a named enum. */ +/** @brief Type for collecting parts of a named enum. + * + * The `NamedEnumTable` 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` 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& milkshakeSizeNames() + * { + * static NamedEnumTable 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 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. */ 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). */ 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 )