Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>. |
||
| 3 | * It is copyright by its individual contributors, as recorded in the |
||
| 4 | * project's Git history. See COPYING.txt at the top level for license |
||
| 5 | * terms and a link to the Git history. |
||
| 6 | */ |
||
| 7 | #pragma once |
||
| 8 | |||
| 9 | #include <cstddef> |
||
| 10 | #include <type_traits> |
||
| 11 | |||
| 12 | #ifndef DXX_VALPTRIDX_ENFORCE_STRICT_PI_SEPARATION |
||
| 13 | #ifdef NDEBUG |
||
| 14 | #define DXX_VALPTRIDX_ENFORCE_STRICT_PI_SEPARATION 0 |
||
| 15 | #else |
||
| 16 | #define DXX_VALPTRIDX_ENFORCE_STRICT_PI_SEPARATION 1 |
||
| 17 | #endif |
||
| 18 | #endif |
||
| 19 | |||
| 20 | #if DXX_VALPTRIDX_ENFORCE_STRICT_PI_SEPARATION |
||
| 21 | template <typename T> |
||
| 22 | struct strong_typedef; |
||
| 23 | #endif |
||
| 24 | |||
| 25 | /* Use a C++11 user-defined literal to convert a string literal into a |
||
| 26 | * type, so that it can be used as a template type parameter. |
||
| 27 | */ |
||
| 28 | template <typename T, T... v> |
||
| 29 | struct literal_as_type {}; |
||
| 30 | |||
| 31 | template <typename T, T... v> |
||
| 32 | constexpr literal_as_type<T, v...> operator""_literal_as_type(); |
||
| 33 | |||
| 34 | /* Given two types representing literals, return a type representing the |
||
| 35 | * concatenation of those literals. This function is never defined, and |
||
| 36 | * can only be used in unevaluated contexts. |
||
| 37 | */ |
||
| 38 | template <typename T, T... a, T... b> |
||
| 39 | constexpr literal_as_type<T, a..., b...> concatenate(literal_as_type<T, a...> &&, literal_as_type<T, b...> &&); |
||
| 40 | |||
| 41 | /* valptridx_specialized_types is never defined, but is specialized to |
||
| 42 | * define a typedef for a type-specific class suitable for use as a base |
||
| 43 | * of valptridx<T>. |
||
| 44 | */ |
||
| 45 | template <typename managed_type> |
||
| 46 | struct valptridx_specialized_types; |
||
| 47 | |||
| 48 | namespace valptridx_detail { |
||
| 49 | |||
| 50 | /* This type is never defined, but explicit specializations of it are |
||
| 51 | * defined to provide a mapping from literal_as_type<char, 'X'> to |
||
| 52 | * report_error_style::X, for each member X of report_error_style. |
||
| 53 | */ |
||
| 54 | template <typename> |
||
| 55 | struct literal_type_to_policy; |
||
| 56 | |||
| 57 | /* Given a C identifier, stringize it, then pass the string to |
||
| 58 | * operator""_literal_as_type() to produce a specialization of |
||
| 59 | * literal_as_type<...>, which can be used as a template type argument. |
||
| 60 | */ |
||
| 61 | #define DXX_VALPTRIDX_LITERAL_TO_TYPE2(A) #A##_literal_as_type |
||
| 62 | #define DXX_VALPTRIDX_LITERAL_TO_TYPE(A) decltype(DXX_VALPTRIDX_LITERAL_TO_TYPE2(A)) |
||
| 63 | |||
| 64 | /* Generate all the template parameters to one instantiation of |
||
| 65 | * error_style_dispatch. A macro is used due to the repeated occurrence |
||
| 66 | * of various boilerplate identifiers. |
||
| 67 | */ |
||
| 68 | #define DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(MC,TYPE) \ |
||
| 69 | DXX_VALPTRIDX_LITERAL_TO_TYPE(TYPE), \ |
||
| 70 | DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_##MC##_), DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_##MC##_##TYPE), \ |
||
| 71 | DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_), DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_##TYPE), \ |
||
| 72 | DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_##MC##_default), \ |
||
| 73 | DXX_VALPTRIDX_LITERAL_TO_TYPE(DXX_VALPTRIDX_REPORT_ERROR_STYLE_default) \ |
||
| 74 | |||
| 75 | class untyped_utilities |
||
| 76 | { |
||
| 77 | public: |
||
| 78 | /* Given a C identifier as PREFIX, and a C type identifier as TYPE, |
||
| 79 | * generate `concatenate(literal_as_type<PREFIX>, |
||
| 80 | * literal_as_type<TYPE>)` and `literal_as_type<PREFIX##TYPE>`. |
||
| 81 | * Compare them for type equality. If `PREFIX##TYPE` matches an |
||
| 82 | * active macro, it will be expanded to the value of the macro, but |
||
| 83 | * the concatenation of literal_as_type<PREFIX> and |
||
| 84 | * literal_as_type<TYPE> will not be expanded. This allows the |
||
| 85 | * template to detect whether `PREFIX##TYPE` is a defined macro |
||
| 86 | * (unless `PREFIX##TYPE` is defined as a macro that expands to its |
||
| 87 | * own name), and choose a branch of `std::conditional` accordingly. |
||
| 88 | * The true branch is chosen if `PREFIX##TYPE` is _not_ a macro, and |
||
| 89 | * is implemented by expanding to the third argument. The false |
||
| 90 | * branch is chosen if `PREFIX##TYPE` is a macro, and is implemented |
||
| 91 | * as a reference to `literal_type_to_policy`. |
||
| 92 | */ |
||
| 93 | #define DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_BRANCH(PREFIX,TYPE,BRANCH_TRUE,...) \ |
||
| 94 | typename std::conditional< \ |
||
| 95 | std::is_same< \ |
||
| 96 | decltype(concatenate( \ |
||
| 97 | std::declval<PREFIX>(), std::declval<TYPE>() \ |
||
| 98 | )), PREFIX##TYPE>::value, \ |
||
| 99 | BRANCH_TRUE, ## __VA_ARGS__, literal_type_to_policy<PREFIX##TYPE> \ |
||
| 100 | >::type |
||
| 101 | |||
| 102 | /* Given specializations of `literal_as_type`, find the most |
||
| 103 | * specific error-reporting style and evaluate to |
||
| 104 | * `literal_type_to_policy` specialized on that style. Consumers |
||
| 105 | * can then access `error_style_dispatch<...>::value` to obtain the |
||
| 106 | * chosen error-reporting style as an enum member of |
||
| 107 | * report_error_style. |
||
| 108 | */ |
||
| 109 | template <typename managed_type, |
||
| 110 | typename style_qualified_, typename style_qualified_managed_type, |
||
| 111 | typename style_default_, typename style_default_managed_type, |
||
| 112 | typename style_qualified_default, |
||
| 113 | typename style_default> |
||
| 114 | using error_style_dispatch = |
||
| 115 | DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_BRANCH(style_qualified_, managed_type, |
||
| 116 | DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_BRANCH(style_default_, managed_type, |
||
| 117 | typename std::conditional< |
||
| 118 | std::is_same< |
||
| 119 | decltype( |
||
| 120 | concatenate( |
||
| 121 | std::declval<style_qualified_>(), |
||
| 122 | "default"_literal_as_type |
||
| 123 | ) |
||
| 124 | ), |
||
| 125 | style_qualified_default |
||
| 126 | >::value, |
||
| 127 | literal_type_to_policy<style_default>, |
||
| 128 | literal_type_to_policy<style_qualified_default> |
||
| 129 | >::type |
||
| 130 | ) |
||
| 131 | ); |
||
| 132 | #undef DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_BRANCH |
||
| 133 | /* The style can be selected on a per-const-qualified-type basis at |
||
| 134 | * compile-time by defining a DXX_VALPTRIDX_REPORT_ERROR_STYLE_* |
||
| 135 | * macro. See the banner comment by the definition of |
||
| 136 | * `DXX_VALPTRIDX_REPORT_ERROR_STYLE_default` for details. |
||
| 137 | */ |
||
| 138 | enum class report_error_style |
||
| 139 | { |
||
| 140 | /* Do not report the error at all. This produces the smallest |
||
| 141 | * program, but the program will exhibit undefined behavior if |
||
| 142 | * an error condition occurs. The program may crash |
||
| 143 | * immediately, crash at a later stage when some other function |
||
| 144 | * becomes confused by the invalid data propagated by the |
||
| 145 | * undefined behavior, continue to run but behave incorrectly, |
||
| 146 | * or seem to work normally. |
||
| 147 | * |
||
| 148 | * This mode is not recommended for use in normal environments, |
||
| 149 | * but is included because it is the only mode which can |
||
| 150 | * completely optimize out the error detection code. |
||
| 151 | * |
||
| 152 | * Debugging may be very difficult, since the variables are not |
||
| 153 | * preserved and the undefined behavior may not halt the program |
||
| 154 | * until much later, if at all. |
||
| 155 | */ |
||
| 156 | undefined, |
||
| 157 | /* Report the error by executing an architecture-specific |
||
| 158 | * trapping instruction. No context is explicitly preserved. |
||
| 159 | * This produces the second smallest program. This is the |
||
| 160 | * smallest choice that prevents undefined behavior. |
||
| 161 | * |
||
| 162 | * This is recommended for production in constrained |
||
| 163 | * environments and for environments in which no debugging will |
||
| 164 | * be attempted. |
||
| 165 | * |
||
| 166 | * Debugging will require reading the surrounding code to find |
||
| 167 | * relevant variables. The variables might have been |
||
| 168 | * overwritten by the time of the trap. |
||
| 169 | */ |
||
| 170 | trap_terse, |
||
| 171 | /* Report the error by executing an architecture-specific |
||
| 172 | * trapping instruction. Context data is stored into |
||
| 173 | * registers/memory immediately before the trap. |
||
| 174 | * This produces a larger program than `trap_terse`, but smaller |
||
| 175 | * than `exception`. |
||
| 176 | * |
||
| 177 | * This is recommended for constrained environments where |
||
| 178 | * debugging is expected. |
||
| 179 | * |
||
| 180 | * Debugging should be easier because the relevant variables |
||
| 181 | * were kept alive and explicitly stored into registers/memory |
||
| 182 | * immediately before the trap. Although the compiler may |
||
| 183 | * generate code that will overwrite those locations before the |
||
| 184 | * trap executes, it has no reason to do so, and likely will not |
||
| 185 | * do so. |
||
| 186 | */ |
||
| 187 | trap_verbose, |
||
| 188 | /* Report the error by throwing an exception. This produces the |
||
| 189 | * largest program, but produces the most informative output in |
||
| 190 | * case of an error. |
||
| 191 | * |
||
| 192 | * This is recommended for environments where debugging is |
||
| 193 | * expected and the space overhead is acceptable. |
||
| 194 | * |
||
| 195 | * Debugging should be easier because the relevant variables are |
||
| 196 | * formatted into a string, which is passed to the exception |
||
| 197 | * constructor. The variables should also be visible in the |
||
| 198 | * call to the function which throws the exception. |
||
| 199 | */ |
||
| 200 | exception, |
||
| 201 | }; |
||
| 202 | |||
| 203 | #if DXX_VALPTRIDX_ENFORCE_STRICT_PI_SEPARATION |
||
| 204 | template <typename T> |
||
| 205 | using wrapper = strong_typedef<T>; |
||
| 206 | #else |
||
| 207 | template <typename T> |
||
| 208 | using wrapper = T; |
||
| 209 | #endif |
||
| 210 | |||
| 211 | protected: |
||
| 212 | /* These classes contain a static method `report` called when an |
||
| 213 | * error occurs. The report method implements the error reporting |
||
| 214 | * (if any) for this type of error. See `report_error_style` |
||
| 215 | * members for a description. |
||
| 216 | * |
||
| 217 | * Reporting as undefined and reporting as terse use a single helper |
||
| 218 | * for all error classes, since those methods do not depend on any |
||
| 219 | * error-type specific context data. |
||
| 220 | * |
||
| 221 | * Reporting as a trap with context is broken out on a per-error |
||
| 222 | * basis, but all types share the same implementation. |
||
| 223 | * |
||
| 224 | * Reporting as an exception is broken out and is not shared, so |
||
| 225 | * that the exception type reports the managed_type that failed the |
||
| 226 | * test. See `valptridx<T>` for the exception classes. |
||
| 227 | */ |
||
| 228 | class report_error_undefined; |
||
| 229 | class report_error_trap_terse; |
||
| 230 | class index_mismatch_trap_verbose; |
||
| 231 | class index_range_trap_verbose; |
||
| 232 | class null_pointer_trap_verbose; |
||
| 233 | |||
| 234 | /* This is a template switch to map each error reporting style to |
||
| 235 | * its corresponding class. Modes `undefined` and `trap_terse` map |
||
| 236 | * to one type each. Modes `trap_verbose` and `exception` map to |
||
| 237 | * varied types, so must be supplied as template arguments. |
||
| 238 | * |
||
| 239 | * Type `array_managed_type` and styles `report_const_error_mode`, |
||
| 240 | * `report_mutable_error_mode` are only used to to choose |
||
| 241 | * `report_error_mode`, which should never be specified by the |
||
| 242 | * caller. Style `report_error_mode` is then used as the key of the |
||
| 243 | * switch. |
||
| 244 | * |
||
| 245 | * This switch yields a type that is one of the four error report |
||
| 246 | * classes, or `void` if the input is invalid. |
||
| 247 | */ |
||
| 248 | template < |
||
| 249 | typename array_managed_type, |
||
| 250 | report_error_style report_const_error_mode, |
||
| 251 | report_error_style report_mutable_error_mode, |
||
| 252 | typename report_error_trap_verbose, |
||
| 253 | typename report_error_exception, |
||
| 254 | report_error_style report_error_mode = std::is_const<array_managed_type>::value ? report_const_error_mode : report_mutable_error_mode |
||
| 255 | > |
||
| 256 | using dispatch_mc_report_error_type = typename std::conditional< |
||
| 257 | report_error_mode == report_error_style::undefined, |
||
| 258 | report_error_undefined, |
||
| 259 | typename std::conditional< |
||
| 260 | report_error_mode == report_error_style::trap_terse, |
||
| 261 | report_error_trap_terse, |
||
| 262 | typename std::conditional< |
||
| 263 | report_error_mode == report_error_style::trap_verbose, |
||
| 264 | report_error_trap_verbose, |
||
| 265 | typename std::conditional< |
||
| 266 | report_error_mode == report_error_style::exception, |
||
| 267 | report_error_exception, |
||
| 268 | /* Error, no match - use void to force a |
||
| 269 | * compilation error when the caller tries to |
||
| 270 | * access a static method of the resulting type. |
||
| 271 | */ |
||
| 272 | void |
||
| 273 | >::type |
||
| 274 | >::type |
||
| 275 | >::type |
||
| 276 | >::type; |
||
| 277 | |||
| 278 | class allow_end_construction; |
||
| 279 | class allow_none_construction; |
||
| 280 | class assume_nothrow_index; |
||
| 281 | class rebind_policy; |
||
| 282 | }; |
||
| 283 | |||
| 284 | /* Map the four reporting styles from their `literal_as_type` |
||
| 285 | * representation to their `report_error_style` enumerated value. |
||
| 286 | */ |
||
| 287 | template <> |
||
| 288 | struct literal_type_to_policy<decltype("undefined"_literal_as_type)> : std::integral_constant<untyped_utilities::report_error_style, untyped_utilities::report_error_style::undefined> |
||
| 289 | { |
||
| 290 | }; |
||
| 291 | |||
| 292 | template <> |
||
| 293 | struct literal_type_to_policy<decltype("trap_terse"_literal_as_type)> : std::integral_constant<untyped_utilities::report_error_style, untyped_utilities::report_error_style::trap_terse> |
||
| 294 | { |
||
| 295 | }; |
||
| 296 | |||
| 297 | template <> |
||
| 298 | struct literal_type_to_policy<decltype("trap_verbose"_literal_as_type)> : std::integral_constant<untyped_utilities::report_error_style, untyped_utilities::report_error_style::trap_verbose> |
||
| 299 | { |
||
| 300 | }; |
||
| 301 | |||
| 302 | template <> |
||
| 303 | struct literal_type_to_policy<decltype("exception"_literal_as_type)> : std::integral_constant<untyped_utilities::report_error_style, untyped_utilities::report_error_style::exception> |
||
| 304 | { |
||
| 305 | }; |
||
| 306 | |||
| 307 | template < |
||
| 308 | typename INTEGRAL_TYPE, |
||
| 309 | std::size_t array_size_value, |
||
| 310 | untyped_utilities::report_error_style report_const_error_value, |
||
| 311 | untyped_utilities::report_error_style report_mutable_error_value |
||
| 312 | > |
||
| 313 | class specialized_type_parameters : public untyped_utilities |
||
| 314 | { |
||
| 315 | public: |
||
| 316 | using integral_type = INTEGRAL_TYPE; |
||
| 317 | static constexpr std::integral_constant<std::size_t, array_size_value> array_size{}; |
||
| 318 | using report_error_uses_exception = std::integral_constant<bool, |
||
| 319 | report_const_error_value == untyped_utilities::report_error_style::exception || |
||
| 320 | report_mutable_error_value == untyped_utilities::report_error_style::exception |
||
| 321 | >; |
||
| 322 | template <typename array_managed_type, typename report_error_exception> |
||
| 323 | using dispatch_index_mismatch_error = typename untyped_utilities::template dispatch_mc_report_error_type< |
||
| 324 | array_managed_type, |
||
| 325 | report_const_error_value, |
||
| 326 | report_mutable_error_value, |
||
| 327 | typename untyped_utilities::index_mismatch_trap_verbose, |
||
| 328 | report_error_exception |
||
| 329 | >; |
||
| 330 | template <typename array_managed_type, typename report_error_exception> |
||
| 331 | using dispatch_index_range_error = typename untyped_utilities::template dispatch_mc_report_error_type< |
||
| 332 | array_managed_type, |
||
| 333 | report_const_error_value, |
||
| 334 | report_mutable_error_value, |
||
| 335 | typename untyped_utilities::index_range_trap_verbose, |
||
| 336 | report_error_exception |
||
| 337 | >; |
||
| 338 | template <typename array_managed_type, typename report_error_exception> |
||
| 339 | using dispatch_null_pointer_error = typename untyped_utilities::template dispatch_mc_report_error_type< |
||
| 340 | array_managed_type, |
||
| 341 | report_const_error_value, |
||
| 342 | report_mutable_error_value, |
||
| 343 | typename untyped_utilities::null_pointer_trap_verbose, |
||
| 344 | report_error_exception |
||
| 345 | >; |
||
| 346 | }; |
||
| 347 | |||
| 348 | } |
||
| 349 | |||
| 350 | /* If not otherwise defined, set the default reporting style for all |
||
| 351 | * valptridx errors. The macro value must be equal to the suffix of one |
||
| 352 | * of the four members of `report_error_style`. |
||
| 353 | * |
||
| 354 | * For finer control, valptridx inspects four values and picks the first |
||
| 355 | * defined value. Undefined styles are ignored and later values are |
||
| 356 | * searched. Invalid values produce a compile-time error. |
||
| 357 | * |
||
| 358 | * For const inputs, the four values are: |
||
| 359 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_<TYPE> |
||
| 360 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_<TYPE> |
||
| 361 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default |
||
| 362 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_default |
||
| 363 | * |
||
| 364 | * For mutable inputs, the four values are: |
||
| 365 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_<TYPE> |
||
| 366 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_<TYPE> |
||
| 367 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default |
||
| 368 | * - DXX_VALPTRIDX_REPORT_ERROR_STYLE_default |
||
| 369 | * |
||
| 370 | * In all cases, <TYPE> is the second argument passed to |
||
| 371 | * DXX_VALPTRIDX_DECLARE_SUBTYPE. This is normally the |
||
| 372 | * non-namespace-qualified name of the type, such as `segment`, |
||
| 373 | * `object`, or `wall`. Namespace qualification is not permitted since |
||
| 374 | * colon is not valid in a macro name. |
||
| 375 | * |
||
| 376 | * For example, to make all const lookups use `trap_terse`, mutable |
||
| 377 | * segment use `trap_verbose`, and all others retain default, define two |
||
| 378 | * macros: |
||
| 379 | * |
||
| 380 | * #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default trap_terse |
||
| 381 | * #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_segment trap_verbose |
||
| 382 | */ |
||
| 383 | #ifndef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default |
||
| 384 | #define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default exception |
||
| 385 | #endif |
||
| 386 | |||
| 387 | #define DXX_VALPTRIDX_DECLARE_SUBTYPE(NS_TYPE,MANAGED_TYPE,INTEGRAL_TYPE,ARRAY_SIZE_VALUE) \ |
||
| 388 | template <> \ |
||
| 389 | struct valptridx_specialized_types<NS_TYPE MANAGED_TYPE> { \ |
||
| 390 | using type = valptridx_detail::specialized_type_parameters< \ |
||
| 391 | INTEGRAL_TYPE, \ |
||
| 392 | ARRAY_SIZE_VALUE, \ |
||
| 393 | valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, MANAGED_TYPE)>::value, \ |
||
| 394 | valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, MANAGED_TYPE)>::value \ |
||
| 395 | >; \ |
||
| 396 | } |