Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2021 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
| 18 | |
| 19 | #include <cstddef> |
| 20 | #include <limits> |
| 21 | #include <optional> |
| 22 | #include <string_view> |
| 23 | #include <type_traits> |
| 24 | #include <utility> |
| 25 | |
| 26 | #include <ftl/string.h> |
| 27 | |
| 28 | // Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the |
| 29 | // compiler-generated string literal for the signature of this function. The function is defined in |
| 30 | // the global namespace with a short name and inferred return type to reduce bloat in the read-only |
| 31 | // data segment. |
| 32 | template <typename E, E V> |
| 33 | constexpr auto ftl_enum() { |
| 34 | static_assert(std::is_enum_v<E>); |
| 35 | |
| 36 | using R = std::optional<std::string_view>; |
| 37 | using namespace std::literals; |
| 38 | |
| 39 | // The "pretty" signature has the following format: |
| 40 | // |
| 41 | // auto ftl_enum() [E = android::test::Enum, V = android::test::Enum::kValue] |
| 42 | // |
| 43 | std::string_view view = __PRETTY_FUNCTION__; |
| 44 | const auto template_begin = view.rfind('['); |
| 45 | const auto template_end = view.rfind(']'); |
| 46 | if (template_begin == view.npos || template_end == view.npos) return R{}; |
| 47 | |
| 48 | // Extract the template parameters without the enclosing brackets. Example (cont'd): |
| 49 | // |
| 50 | // E = android::test::Enum, V = android::test::Enum::kValue |
| 51 | // |
| 52 | view = view.substr(template_begin + 1, template_end - template_begin - 1); |
| 53 | const auto value_begin = view.rfind("V = "sv); |
| 54 | if (value_begin == view.npos) return R{}; |
| 55 | |
| 56 | // Example (cont'd): |
| 57 | // |
| 58 | // V = android::test::Enum::kValue |
| 59 | // |
| 60 | view = view.substr(value_begin); |
| 61 | const auto name_begin = view.rfind("::"sv); |
| 62 | if (name_begin == view.npos) return R{}; |
| 63 | |
| 64 | // Chop off the leading "::". |
| 65 | const auto name = view.substr(name_begin + 2); |
| 66 | |
| 67 | // A value that is not enumerated has the format "Enum)42". |
| 68 | return name.find(')') == view.npos ? R{name} : R{}; |
| 69 | } |
| 70 | |
| 71 | namespace android::ftl { |
| 72 | |
| 73 | // Trait for determining whether a type is specifically a scoped enum or not. By definition, a |
| 74 | // scoped enum is one that is not implicitly convertible to its underlying type. |
| 75 | // |
| 76 | // TODO: Replace with std::is_scoped_enum in C++23. |
| 77 | // |
| 78 | template <typename T, bool = std::is_enum_v<T>> |
| 79 | struct is_scoped_enum : std::false_type {}; |
| 80 | |
| 81 | template <typename T> |
| 82 | struct is_scoped_enum<T, true> : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> { |
| 83 | }; |
| 84 | |
| 85 | template <typename T> |
| 86 | inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value; |
| 87 | |
| 88 | // Shorthand for casting an enumerator to its integral value. |
| 89 | // |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 90 | // TODO: Replace with std::to_underlying in C++23. |
| 91 | // |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 92 | // enum class E { A, B, C }; |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 93 | // static_assert(ftl::to_underlying(E::B) == 1); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 94 | // |
Dominik Laskowski | dfeded7 | 2022-11-15 16:53:53 -0500 | [diff] [blame] | 95 | template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>> |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 96 | constexpr auto to_underlying(E v) { |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 97 | return static_cast<std::underlying_type_t<E>>(v); |
| 98 | } |
| 99 | |
| 100 | // Traits for retrieving an enum's range. An enum specifies its range by defining enumerators named |
| 101 | // ftl_first and ftl_last. If omitted, ftl_first defaults to 0, whereas ftl_last defaults to N - 1 |
| 102 | // where N is the bit width of the underlying type, but only if that type is unsigned, assuming the |
| 103 | // enumerators are flags. Also, note that unscoped enums must define both bounds, as casting out-of- |
| 104 | // range values results in undefined behavior if the underlying type is not fixed. |
| 105 | // |
| 106 | // enum class E { A, B, C, F = 5, ftl_last = F }; |
| 107 | // |
| 108 | // static_assert(ftl::enum_begin_v<E> == E::A); |
| 109 | // static_assert(ftl::enum_last_v<E> == E::F); |
| 110 | // static_assert(ftl::enum_size_v<E> == 6); |
| 111 | // |
| 112 | // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; |
| 113 | // |
| 114 | // static_assert(ftl::enum_begin_v<F> == F{0}); |
| 115 | // static_assert(ftl::enum_last_v<F> == F{15}); |
| 116 | // static_assert(ftl::enum_size_v<F> == 16); |
| 117 | // |
| 118 | template <typename E, typename = void> |
| 119 | struct enum_begin { |
| 120 | static_assert(is_scoped_enum_v<E>, "Missing ftl_first enumerator"); |
| 121 | static constexpr E value{0}; |
| 122 | }; |
| 123 | |
| 124 | template <typename E> |
| 125 | struct enum_begin<E, std::void_t<decltype(E::ftl_first)>> { |
| 126 | static constexpr E value = E::ftl_first; |
| 127 | }; |
| 128 | |
| 129 | template <typename E> |
| 130 | inline constexpr E enum_begin_v = enum_begin<E>::value; |
| 131 | |
| 132 | template <typename E, typename = void> |
| 133 | struct enum_end { |
| 134 | using U = std::underlying_type_t<E>; |
| 135 | static_assert(is_scoped_enum_v<E> && std::is_unsigned_v<U>, "Missing ftl_last enumerator"); |
| 136 | |
| 137 | static constexpr E value{std::numeric_limits<U>::digits}; |
| 138 | }; |
| 139 | |
| 140 | template <typename E> |
| 141 | struct enum_end<E, std::void_t<decltype(E::ftl_last)>> { |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 142 | static constexpr E value = E{to_underlying(E::ftl_last) + 1}; |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 143 | }; |
| 144 | |
| 145 | template <typename E> |
| 146 | inline constexpr E enum_end_v = enum_end<E>::value; |
| 147 | |
| 148 | template <typename E> |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 149 | inline constexpr E enum_last_v = E{to_underlying(enum_end_v<E>) - 1}; |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 150 | |
| 151 | template <typename E> |
| 152 | struct enum_size { |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 153 | static constexpr auto kBegin = to_underlying(enum_begin_v<E>); |
| 154 | static constexpr auto kEnd = to_underlying(enum_end_v<E>); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 155 | static_assert(kBegin < kEnd, "Invalid range"); |
| 156 | |
| 157 | static constexpr std::size_t value = kEnd - kBegin; |
| 158 | static_assert(value <= 64, "Excessive range size"); |
| 159 | }; |
| 160 | |
| 161 | template <typename E> |
| 162 | inline constexpr std::size_t enum_size_v = enum_size<E>::value; |
| 163 | |
| 164 | namespace details { |
| 165 | |
| 166 | template <auto V> |
| 167 | struct Identity { |
| 168 | static constexpr auto value = V; |
| 169 | }; |
| 170 | |
| 171 | template <typename E> |
| 172 | using make_enum_sequence = std::make_integer_sequence<std::underlying_type_t<E>, enum_size_v<E>>; |
| 173 | |
| 174 | template <typename E, template <E> class = Identity, typename = make_enum_sequence<E>> |
| 175 | struct EnumRange; |
| 176 | |
| 177 | template <typename E, template <E> class F, typename T, T... Vs> |
| 178 | struct EnumRange<E, F, std::integer_sequence<T, Vs...>> { |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 179 | static constexpr auto kBegin = to_underlying(enum_begin_v<E>); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 180 | static constexpr auto kSize = enum_size_v<E>; |
| 181 | |
| 182 | using R = decltype(F<E{}>::value); |
| 183 | const R values[kSize] = {F<static_cast<E>(Vs + kBegin)>::value...}; |
| 184 | |
| 185 | constexpr const auto* begin() const { return values; } |
| 186 | constexpr const auto* end() const { return values + kSize; } |
| 187 | }; |
| 188 | |
| 189 | template <auto V> |
| 190 | struct EnumName { |
| 191 | static constexpr auto value = ftl_enum<decltype(V), V>(); |
| 192 | }; |
| 193 | |
| 194 | template <auto I> |
| 195 | struct FlagName { |
| 196 | using E = decltype(I); |
| 197 | using U = std::underlying_type_t<E>; |
| 198 | |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 199 | static constexpr E V{U{1} << to_underlying(I)}; |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 200 | static constexpr auto value = ftl_enum<E, V>(); |
| 201 | }; |
| 202 | |
| 203 | } // namespace details |
| 204 | |
| 205 | // Returns an iterable over the range of an enum. |
| 206 | // |
| 207 | // enum class E { A, B, C, F = 5, ftl_last = F }; |
| 208 | // |
| 209 | // std::string string; |
| 210 | // for (E v : ftl::enum_range<E>()) { |
| 211 | // string += ftl::enum_name(v).value_or("?"); |
| 212 | // } |
| 213 | // |
| 214 | // assert(string == "ABC??F"); |
| 215 | // |
| 216 | template <typename E> |
| 217 | constexpr auto enum_range() { |
| 218 | return details::EnumRange<E>{}; |
| 219 | } |
| 220 | |
| 221 | // Returns a stringified enumerator at compile time. |
| 222 | // |
| 223 | // enum class E { A, B, C }; |
| 224 | // static_assert(ftl::enum_name<E::B>() == "B"); |
| 225 | // |
| 226 | template <auto V> |
| 227 | constexpr std::string_view enum_name() { |
| 228 | constexpr auto kName = ftl_enum<decltype(V), V>(); |
| 229 | static_assert(kName, "Unknown enumerator"); |
| 230 | return *kName; |
| 231 | } |
| 232 | |
| 233 | // Returns a stringified enumerator, possibly at compile time. |
| 234 | // |
| 235 | // enum class E { A, B, C, F = 5, ftl_last = F }; |
| 236 | // |
| 237 | // static_assert(ftl::enum_name(E::C).value_or("?") == "C"); |
| 238 | // static_assert(ftl::enum_name(E{3}).value_or("?") == "?"); |
| 239 | // |
| 240 | template <typename E> |
| 241 | constexpr std::optional<std::string_view> enum_name(E v) { |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 242 | const auto value = to_underlying(v); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 243 | |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 244 | constexpr auto kBegin = to_underlying(enum_begin_v<E>); |
| 245 | constexpr auto kLast = to_underlying(enum_last_v<E>); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 246 | if (value < kBegin || value > kLast) return {}; |
| 247 | |
| 248 | constexpr auto kRange = details::EnumRange<E, details::EnumName>{}; |
| 249 | return kRange.values[value - kBegin]; |
| 250 | } |
| 251 | |
| 252 | // Returns a stringified flag enumerator, possibly at compile time. |
| 253 | // |
| 254 | // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; |
| 255 | // |
| 256 | // static_assert(ftl::flag_name(F::Z).value_or("?") == "Z"); |
| 257 | // static_assert(ftl::flag_name(F{0b111}).value_or("?") == "?"); |
| 258 | // |
| 259 | template <typename E> |
| 260 | constexpr std::optional<std::string_view> flag_name(E v) { |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 261 | const auto value = to_underlying(v); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 262 | |
| 263 | // TODO: Replace with std::popcount and std::countr_zero in C++20. |
Dominik Laskowski | a7fe7eb | 2022-02-16 10:44:28 -0800 | [diff] [blame] | 264 | if (__builtin_popcountll(value) != 1) return {}; |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 265 | |
| 266 | constexpr auto kRange = details::EnumRange<E, details::FlagName>{}; |
Dominik Laskowski | a7fe7eb | 2022-02-16 10:44:28 -0800 | [diff] [blame] | 267 | return kRange.values[__builtin_ctzll(value)]; |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 268 | } |
| 269 | |
| 270 | // Returns a stringified enumerator, or its integral value if not named. |
| 271 | // |
| 272 | // enum class E { A, B, C, F = 5, ftl_last = F }; |
| 273 | // |
| 274 | // assert(ftl::enum_string(E::C) == "C"); |
| 275 | // assert(ftl::enum_string(E{3}) == "3"); |
| 276 | // |
| 277 | template <typename E> |
| 278 | inline std::string enum_string(E v) { |
| 279 | if (const auto name = enum_name(v)) { |
| 280 | return std::string(*name); |
| 281 | } |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 282 | return to_string(to_underlying(v)); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 283 | } |
| 284 | |
| 285 | // Returns a stringified flag enumerator, or its integral value if not named. |
| 286 | // |
| 287 | // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; |
| 288 | // |
| 289 | // assert(ftl::flag_string(F::Z) == "Z"); |
| 290 | // assert(ftl::flag_string(F{7}) == "0b111"); |
| 291 | // |
| 292 | template <typename E> |
| 293 | inline std::string flag_string(E v) { |
| 294 | if (const auto name = flag_name(v)) { |
| 295 | return std::string(*name); |
| 296 | } |
| 297 | constexpr auto radix = sizeof(E) == 1 ? Radix::kBin : Radix::kHex; |
Dominik Laskowski | 04667b7 | 2021-12-15 13:14:54 -0800 | [diff] [blame] | 298 | return to_string(to_underlying(v), radix); |
Dominik Laskowski | 7578845 | 2021-02-09 18:51:25 -0800 | [diff] [blame] | 299 | } |
| 300 | |
| 301 | } // namespace android::ftl |