Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Enumeration.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Common_Enumeration_h_
5#define _Stroika_Foundation_Common_Enumeration_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <array>
10#include <optional>
11#include <type_traits>
12#include <utility>
13#include <vector>
14
15#include "Stroika/Foundation/Common/Common.h"
16
17/**
18 * \file
19 *
20 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
21 *
22 * TODO:
23 *
24 * @todo http://stroika-bugs.sophists.com/browse/STK-549 - RequireItemsOrderedByEnumValue and
25 * static_cast usage confusion.
26 *
27 * @todo I tried using EnumNames<> as an alias for initializer_list, but then I couldn't add the
28 * GetNames () method. I tried subclassing, but then I ran into lifetime issues. I tried aggregation,
29 * but this has the same lifetime issues with subclassing std::initializer_list. In the end I had
30 * to copy. That maybe a poor tradeoff. The only reason for not using aliases was to add
31 * the Peek/GetName methods, but those could have been global functions? Hmmm.
32 */
33
35
36#ifndef qCANNOT_FIGURE_OUT_HOW_TO_INIT_STD_ARRAY_FROM_STD_INITIALIZER_
37#define qCANNOT_FIGURE_OUT_HOW_TO_INIT_STD_ARRAY_FROM_STD_INITIALIZER_ 1
38#endif
39
40 /**
41 * \brief Increment the given enumeration safely, without a bunch of casts.
42 *
43 * \pre ENUM uses Stroika_Define_Enum_Bounds() to define eSTART, eEND
44 * \pre e >= typename ENUM::eSTART and e < typename ENUM::eEND
45 */
46 template <typename ENUM>
47 constexpr ENUM Inc (ENUM e);
48
49 /**
50 * \brief Cast the given enum to an int (like static_cast<int>()) - but check range.
51 *
52 * \pre ENUM uses Stroika_Define_Enum_Bounds() to define eSTART, eEND
53 * \pre e >= typename ENUM::eSTART and e < typename ENUM::eEND
54 *
55 * This function is handy since class enum's cannot be automatically promoted to integers.
56 *
57 * \note This function could have been called Ord () - to be more like Pascal.
58 *
59 * @todo See if there is some better way for this.
60 */
61 template <typename ENUM>
62 constexpr underlying_type_t<ENUM> ToInt (ENUM e);
63
64 /**
65 * \brief return the distance spanned by an enum, e.g. for use in an array
66 *
67 * \par Example Usage
68 * \code
69 * enum class Priority {
70 * a, b, c
71 * Stroika_Define_Enum_Bounds(a, c)
72 * };
73 * unsigned int eltsWithPriority[GetDistanceSpanned<Priority> ()];
74 * \endcode
75 *
76 */
77 template <typename ENUM>
78 constexpr make_unsigned_t<underlying_type_t<ENUM>> GetDistanceSpanned (ENUM e);
79
80 /**
81 * \brief Cast the given int to the given ENUM type - (like static_cast<int>()) - but check range.
82 *
83 * \pre ENUM uses Stroika_Define_Enum_Bounds() to define eSTART, eEND
84 * \pre e >= typename ENUM::eSTART and e < typename ENUM::eEND
85 *
86 * This function is handy since class enum's cannot be automatically promoted to integers.
87 */
88 template <typename ENUM>
89 constexpr ENUM ToEnum (underlying_type_t<ENUM> e);
90
91 /**
92 * \brief offset of given enum from ENUM::eSTART
93 *
94 * \pre ENUM uses Stroika_Define_Enum_Bounds() to define eSTART, eEND
95 * \pre e >= typename ENUM::eSTART and e < typename ENUM::eEND
96 *
97 * @todo See if there is some better way for this.
98 */
99 template <typename ENUM>
100 constexpr make_unsigned_t<underlying_type_t<ENUM>> OffsetFromStart (ENUM e);
101
102 /**
103 * \def Stroika_Define_Enum_Bounds
104 *
105 * Define meta information on enums using standardized names, so you can generically
106 * write things like:
107 * for (auto i = X::eSTART; i != X::eEND; i = Inc (i));
108 */
109#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM) \
110 eSTART = FIRST_ITEM, eEND = LAST_ITEM + 1, eLAST = LAST_ITEM, eCOUNT = eEND - eSTART,
111
112 /**
113 * Check if T is an enum class, with Stroika_Define_Enum_Bounds called on it to specify bounds.
114 */
115 template <typename T>
116 concept IBoundedEnum = is_enum_v<T> && requires (T t) {
117 T::eSTART;
118 T::eEND;
119 T::eLAST;
120 T::eCOUNT;
121 };
122
123 /**
124 */
125 template <typename ENUM_TYPE>
126 using EnumName = pair<ENUM_TYPE, const wchar_t*>;
127
128 /**
129 * The purpose of this class is to capture meta-information about enumerations, principally the purpose
130 * of serialization, and or debugging printouts of data (e.g. DbgTrace).
131 *
132 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
133 * This class fully supports multiple readers, but it is not designed to support update while ongoing access
134 * is going on.
135 *
136 * \note Important requirement on type ENUM_TYPE - it must use the Stroika_Define_Enum_Bounds() macro
137 * or otherwise define eSTART,eEND,eCOUNT,eLAST
138 *
139 * \note Important requirement on type ENUM_TYPE - it must be 'densely packed' - that is - have no gaps.
140 * it need not start at any particular number, but the number of enumerators must equal eCOUNT,
141 * and a name and enumerator must be provided for each value from eSTART to eEND.
142 * (future versions MAY lift that requirement).
143 * \pre RequireItemsOrderedByEnumValue_
144 */
145 template <typename ENUM_TYPE>
146 class EnumNames {
147 public:
148 DISABLE_COMPILER_MSC_WARNING_START (4996);
149 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
150 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
151 static_assert (is_enum_v<decltype (ENUM_TYPE::eCOUNT)>,
152 "Missing eCOUNT - typically Use Stroika_Define_Enum_Bounds inside the enum");
153 static_assert (is_enum_v<decltype (ENUM_TYPE::eSTART)>,
154 "Missing eSTART - typically Use Stroika_Define_Enum_Bounds inside the enum");
155 static_assert (is_enum_v<decltype (ENUM_TYPE::eEND)>, "Missing eEND - typically Use Stroika_Define_Enum_Bounds inside the enum");
156
157 private:
158 using EnumNamesHolderType_ = array<EnumName<ENUM_TYPE>, static_cast<size_t> (ENUM_TYPE::eCOUNT)>;
159
160 public:
161 using BasicArrayInitializer = array<EnumName<ENUM_TYPE>, static_cast<size_t> (ENUM_TYPE::eCOUNT)>;
162
163 DISABLE_COMPILER_MSC_WARNING_END (4996);
164 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
165 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
166
167 public:
168 using const_iterator = typename EnumNamesHolderType_::const_iterator;
169
170 public:
171 using value_type = typename EnumNamesHolderType_::value_type;
172
173 public:
174 /**
175 */
176#if qCANNOT_FIGURE_OUT_HOW_TO_INIT_STD_ARRAY_FROM_STD_INITIALIZER_
177 EnumNames () = default; //hack to allow CTOR EnumNames (const initializer_list<EnumName<ENUM_TYPE>>& origEnumNames)
178#else
179 EnumNames () = delete;
180#endif
181
182 constexpr EnumNames (EnumNames&& src) = default;
183 constexpr EnumNames (const EnumNames& src) = default;
184 constexpr EnumNames (const BasicArrayInitializer& init);
185 EnumNames (const initializer_list<EnumName<ENUM_TYPE>>& origEnumNames);
186 template <size_t N>
187 constexpr EnumNames (const EnumName<ENUM_TYPE> origEnumNames[N]);
188
189 public:
190 /**
191 */
192 nonvirtual EnumNames& operator= (EnumNames&& rhs) = default;
193 nonvirtual EnumNames& operator= (const EnumNames& rhs) = default;
194
195 public:
196 /**
197 */
198 explicit operator initializer_list<EnumName<ENUM_TYPE>> () const;
199
200 public:
201 /**
202 */
203 nonvirtual const_iterator begin () const;
204
205 public:
206 /**
207 */
208 nonvirtual const_iterator end () const;
209
210 public:
211 /**
212 */
213 nonvirtual constexpr size_t size () const;
214
215 public:
216 /**
217 * The argument 'e' must be a valid enumerator entry. If its 'eEND' this function will return nullptr;
218 * Otherwise it will return a valid const wchar_t* pointer to that enumerators name.
219 *
220 * @see GetName ();
221 */
222 nonvirtual constexpr const wchar_t* PeekName (ENUM_TYPE e) const;
223
224 public:
225 /**
226 * The argument 'e' must be a valid enumerator entry between eSTART, and eEND (not including eEND).
227 * It returns a pointer to a valid const wchar_t* string of the name of the enumerator.
228 *
229 * \pre ENUM_TYPE::eStart <= e and e < ENUM_TYPE::eEnd
230 *
231 * @see PeekName ();
232 */
233 nonvirtual const wchar_t* GetName (ENUM_TYPE e) const;
234
235 public:
236 /**
237 * Returns nullopt if not found.
238 */
239 nonvirtual optional<ENUM_TYPE> PeekValue (const wchar_t* name) const;
240
241 public:
242 /**
243 * The /1 overload requires the name is present, and asserts if not found.
244 * The /2 overload throws the argument exceptio iff the name is not found.
245 */
246 nonvirtual ENUM_TYPE GetValue (const wchar_t* name) const;
247 template <typename NOT_FOUND_EXCEPTION>
248 nonvirtual ENUM_TYPE GetValue (const wchar_t* name, const NOT_FOUND_EXCEPTION& notFoundException) const;
249
250 private:
251 nonvirtual constexpr void RequireItemsOrderedByEnumValue_ () const;
252
253 private:
254 // SHOULD BE ABLE TO USE CONST HERE qCANNOT_FIGURE_OUT_HOW_TO_INIT_STD_ARRAY_FROM_STD_INITIALIZER_
255 // but then one (std::initializer_list) CTOR doesn't compile
256 //const EnumNamesHolderType_ fEnumNames_;
257 EnumNamesHolderType_ fEnumNames_;
258 };
259
260 /**
261 * Use DefaultNames<> to register the EnumNames<> mapping in a common place that can be used by
262 * other templates to automatically lookup enum names.
263 *
264 * \par Example Usage
265 * \code
266 * enum class Priority {
267 * a, b, c
268 * Stroika_Define_Enum_Bounds(a, c)
269 * };
270 *
271 * // this template specialization must be located in the Stroika::Common namespace
272 * namespace Stroika::Foundation::Common {
273 * template<>
274 * constexpr EnumNames<Priority> DefaultNames<Priority>::k{{{
275 * {Priority::a, L"Debug"},
276 * {Priority::b, L"Info"},
277 * {Priority::eNotice, L"Notice"},
278 * }}};
279 * }
280 *
281 * // Then use
282 * const wchar_t* aStr = DefaultNames<Priority>{}.PeekName (Priority::a);
283 * Priority p = DefaultNames<Priority>{}.GetValue (L"invalid", Execution::Exception<> (L"OutOfRange")); // this will throw - out of range
284 * \endcode
285 *
286 */
287 template <typename ENUM_TYPE>
288 struct DefaultNames : EnumNames<ENUM_TYPE> {
289 static const EnumNames<ENUM_TYPE> k;
290 constexpr DefaultNames ();
291 };
292
293}
294
295/*
296 ********************************************************************************
297 ***************************** Implementation Details ***************************
298 ********************************************************************************
299 */
300#include "Enumeration.inl"
301
302#endif /*_Stroika_Foundation_Common_Enumeration_h_*/
constexpr make_unsigned_t< underlying_type_t< ENUM > > OffsetFromStart(ENUM e)
offset of given enum from ENUM::eSTART
constexpr make_unsigned_t< underlying_type_t< ENUM > > GetDistanceSpanned(ENUM e)
return the distance spanned by an enum, e.g. for use in an array
constexpr ENUM ToEnum(underlying_type_t< ENUM > e)
Cast the given int to the given ENUM type - (like static_cast<int>()) - but check range.
constexpr ENUM Inc(ENUM e)
Increment the given enumeration safely, without a bunch of casts.