blob: 2c86e2e4c9e8fe0a53913dec29863f737e041b18 [file] [log] [blame]
Dominik Laskowski75788452021-02-09 18:51:25 -08001/*
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
Ady Abrahamaa68d0a2023-09-22 17:28:07 -070028// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as
29// std::optional<std::string_view> by parsing the compiler-generated string literal for the
30// signature of this function. The function is defined in the global namespace with a short name
31// and inferred return type to reduce bloat in the read-only data segment.
32template <bool S, typename E, E V>
33constexpr auto ftl_enum_builder() {
Dominik Laskowski75788452021-02-09 18:51:25 -080034 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);
Ady Abrahamaa68d0a2023-09-22 17:28:07 -070061 const auto pos = S ? view.rfind("::"sv) - 2 : view.npos;
62
63 const auto name_begin = view.rfind("::"sv, pos);
Dominik Laskowski75788452021-02-09 18:51:25 -080064 if (name_begin == view.npos) return R{};
65
66 // Chop off the leading "::".
67 const auto name = view.substr(name_begin + 2);
68
69 // A value that is not enumerated has the format "Enum)42".
70 return name.find(')') == view.npos ? R{name} : R{};
71}
72
Ady Abrahamaa68d0a2023-09-22 17:28:07 -070073// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
74template <typename E, E V>
75constexpr auto ftl_enum() {
76 return ftl_enum_builder<false, E, V>();
77}
78
79// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
80template <typename E, E V>
81constexpr auto ftl_enum_full() {
82 return ftl_enum_builder<true, E, V>();
83}
84
Dominik Laskowski75788452021-02-09 18:51:25 -080085namespace android::ftl {
86
87// Trait for determining whether a type is specifically a scoped enum or not. By definition, a
88// scoped enum is one that is not implicitly convertible to its underlying type.
89//
90// TODO: Replace with std::is_scoped_enum in C++23.
91//
92template <typename T, bool = std::is_enum_v<T>>
93struct is_scoped_enum : std::false_type {};
94
95template <typename T>
96struct is_scoped_enum<T, true> : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> {
97};
98
99template <typename T>
100inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;
101
102// Shorthand for casting an enumerator to its integral value.
103//
Dominik Laskowski04667b72021-12-15 13:14:54 -0800104// TODO: Replace with std::to_underlying in C++23.
105//
Dominik Laskowski75788452021-02-09 18:51:25 -0800106// enum class E { A, B, C };
Dominik Laskowski04667b72021-12-15 13:14:54 -0800107// static_assert(ftl::to_underlying(E::B) == 1);
Dominik Laskowski75788452021-02-09 18:51:25 -0800108//
Dominik Laskowskidfeded72022-11-15 16:53:53 -0500109template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
Dominik Laskowski04667b72021-12-15 13:14:54 -0800110constexpr auto to_underlying(E v) {
Dominik Laskowski75788452021-02-09 18:51:25 -0800111 return static_cast<std::underlying_type_t<E>>(v);
112}
113
114// Traits for retrieving an enum's range. An enum specifies its range by defining enumerators named
115// ftl_first and ftl_last. If omitted, ftl_first defaults to 0, whereas ftl_last defaults to N - 1
116// where N is the bit width of the underlying type, but only if that type is unsigned, assuming the
117// enumerators are flags. Also, note that unscoped enums must define both bounds, as casting out-of-
118// range values results in undefined behavior if the underlying type is not fixed.
119//
120// enum class E { A, B, C, F = 5, ftl_last = F };
121//
122// static_assert(ftl::enum_begin_v<E> == E::A);
123// static_assert(ftl::enum_last_v<E> == E::F);
124// static_assert(ftl::enum_size_v<E> == 6);
125//
126// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
127//
128// static_assert(ftl::enum_begin_v<F> == F{0});
129// static_assert(ftl::enum_last_v<F> == F{15});
130// static_assert(ftl::enum_size_v<F> == 16);
131//
132template <typename E, typename = void>
133struct enum_begin {
134 static_assert(is_scoped_enum_v<E>, "Missing ftl_first enumerator");
135 static constexpr E value{0};
136};
137
138template <typename E>
139struct enum_begin<E, std::void_t<decltype(E::ftl_first)>> {
140 static constexpr E value = E::ftl_first;
141};
142
143template <typename E>
144inline constexpr E enum_begin_v = enum_begin<E>::value;
145
146template <typename E, typename = void>
147struct enum_end {
148 using U = std::underlying_type_t<E>;
149 static_assert(is_scoped_enum_v<E> && std::is_unsigned_v<U>, "Missing ftl_last enumerator");
150
151 static constexpr E value{std::numeric_limits<U>::digits};
152};
153
154template <typename E>
155struct enum_end<E, std::void_t<decltype(E::ftl_last)>> {
Dominik Laskowski04667b72021-12-15 13:14:54 -0800156 static constexpr E value = E{to_underlying(E::ftl_last) + 1};
Dominik Laskowski75788452021-02-09 18:51:25 -0800157};
158
159template <typename E>
160inline constexpr E enum_end_v = enum_end<E>::value;
161
162template <typename E>
Dominik Laskowski04667b72021-12-15 13:14:54 -0800163inline constexpr E enum_last_v = E{to_underlying(enum_end_v<E>) - 1};
Dominik Laskowski75788452021-02-09 18:51:25 -0800164
165template <typename E>
166struct enum_size {
Dominik Laskowski04667b72021-12-15 13:14:54 -0800167 static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
168 static constexpr auto kEnd = to_underlying(enum_end_v<E>);
Dominik Laskowski75788452021-02-09 18:51:25 -0800169 static_assert(kBegin < kEnd, "Invalid range");
170
171 static constexpr std::size_t value = kEnd - kBegin;
172 static_assert(value <= 64, "Excessive range size");
173};
174
175template <typename E>
176inline constexpr std::size_t enum_size_v = enum_size<E>::value;
177
178namespace details {
179
180template <auto V>
181struct Identity {
182 static constexpr auto value = V;
183};
184
185template <typename E>
186using make_enum_sequence = std::make_integer_sequence<std::underlying_type_t<E>, enum_size_v<E>>;
187
188template <typename E, template <E> class = Identity, typename = make_enum_sequence<E>>
189struct EnumRange;
190
191template <typename E, template <E> class F, typename T, T... Vs>
192struct EnumRange<E, F, std::integer_sequence<T, Vs...>> {
Dominik Laskowski04667b72021-12-15 13:14:54 -0800193 static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
Dominik Laskowski75788452021-02-09 18:51:25 -0800194 static constexpr auto kSize = enum_size_v<E>;
195
196 using R = decltype(F<E{}>::value);
197 const R values[kSize] = {F<static_cast<E>(Vs + kBegin)>::value...};
198
199 constexpr const auto* begin() const { return values; }
200 constexpr const auto* end() const { return values + kSize; }
201};
202
203template <auto V>
204struct EnumName {
205 static constexpr auto value = ftl_enum<decltype(V), V>();
206};
207
Ady Abrahamaa68d0a2023-09-22 17:28:07 -0700208template <auto V>
209struct EnumNameFull {
210 static constexpr auto value = ftl_enum_full<decltype(V), V>();
211};
212
Dominik Laskowski75788452021-02-09 18:51:25 -0800213template <auto I>
214struct FlagName {
215 using E = decltype(I);
216 using U = std::underlying_type_t<E>;
217
Dominik Laskowski04667b72021-12-15 13:14:54 -0800218 static constexpr E V{U{1} << to_underlying(I)};
Dominik Laskowski75788452021-02-09 18:51:25 -0800219 static constexpr auto value = ftl_enum<E, V>();
220};
221
222} // namespace details
223
224// Returns an iterable over the range of an enum.
225//
226// enum class E { A, B, C, F = 5, ftl_last = F };
227//
228// std::string string;
229// for (E v : ftl::enum_range<E>()) {
230// string += ftl::enum_name(v).value_or("?");
231// }
232//
233// assert(string == "ABC??F");
234//
235template <typename E>
236constexpr auto enum_range() {
237 return details::EnumRange<E>{};
238}
239
240// Returns a stringified enumerator at compile time.
241//
242// enum class E { A, B, C };
243// static_assert(ftl::enum_name<E::B>() == "B");
244//
245template <auto V>
246constexpr std::string_view enum_name() {
247 constexpr auto kName = ftl_enum<decltype(V), V>();
248 static_assert(kName, "Unknown enumerator");
249 return *kName;
250}
251
Ady Abrahamaa68d0a2023-09-22 17:28:07 -0700252// Returns a stringified enumerator with class at compile time.
253//
254// enum class E { A, B, C };
255// static_assert(ftl::enum_name<E::B>() == "E::B");
256//
257template <auto V>
258constexpr std::string_view enum_name_full() {
259 constexpr auto kName = ftl_enum_full<decltype(V), V>();
260 static_assert(kName, "Unknown enumerator");
261 return *kName;
262}
263
Dominik Laskowski75788452021-02-09 18:51:25 -0800264// Returns a stringified enumerator, possibly at compile time.
265//
266// enum class E { A, B, C, F = 5, ftl_last = F };
267//
268// static_assert(ftl::enum_name(E::C).value_or("?") == "C");
269// static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
270//
271template <typename E>
272constexpr std::optional<std::string_view> enum_name(E v) {
Dominik Laskowski04667b72021-12-15 13:14:54 -0800273 const auto value = to_underlying(v);
Dominik Laskowski75788452021-02-09 18:51:25 -0800274
Dominik Laskowski04667b72021-12-15 13:14:54 -0800275 constexpr auto kBegin = to_underlying(enum_begin_v<E>);
276 constexpr auto kLast = to_underlying(enum_last_v<E>);
Dominik Laskowski75788452021-02-09 18:51:25 -0800277 if (value < kBegin || value > kLast) return {};
278
279 constexpr auto kRange = details::EnumRange<E, details::EnumName>{};
280 return kRange.values[value - kBegin];
281}
282
Ady Abrahamaa68d0a2023-09-22 17:28:07 -0700283// Returns a stringified enumerator with class, possibly at compile time.
284//
285// enum class E { A, B, C, F = 5, ftl_last = F };
286//
287// static_assert(ftl::enum_name(E::C).value_or("?") == "E::C");
288// static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
289//
290template <typename E>
291constexpr std::optional<std::string_view> enum_name_full(E v) {
292 const auto value = to_underlying(v);
293
294 constexpr auto kBegin = to_underlying(enum_begin_v<E>);
295 constexpr auto kLast = to_underlying(enum_last_v<E>);
296 if (value < kBegin || value > kLast) return {};
297
298 constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{};
299 return kRange.values[value - kBegin];
300}
301
Dominik Laskowski75788452021-02-09 18:51:25 -0800302// Returns a stringified flag enumerator, possibly at compile time.
303//
304// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
305//
306// static_assert(ftl::flag_name(F::Z).value_or("?") == "Z");
307// static_assert(ftl::flag_name(F{0b111}).value_or("?") == "?");
308//
309template <typename E>
310constexpr std::optional<std::string_view> flag_name(E v) {
Dominik Laskowski04667b72021-12-15 13:14:54 -0800311 const auto value = to_underlying(v);
Dominik Laskowski75788452021-02-09 18:51:25 -0800312
313 // TODO: Replace with std::popcount and std::countr_zero in C++20.
Dominik Laskowskia7fe7eb2022-02-16 10:44:28 -0800314 if (__builtin_popcountll(value) != 1) return {};
Dominik Laskowski75788452021-02-09 18:51:25 -0800315
316 constexpr auto kRange = details::EnumRange<E, details::FlagName>{};
Dominik Laskowskia7fe7eb2022-02-16 10:44:28 -0800317 return kRange.values[__builtin_ctzll(value)];
Dominik Laskowski75788452021-02-09 18:51:25 -0800318}
319
320// Returns a stringified enumerator, or its integral value if not named.
321//
322// enum class E { A, B, C, F = 5, ftl_last = F };
323//
324// assert(ftl::enum_string(E::C) == "C");
325// assert(ftl::enum_string(E{3}) == "3");
326//
327template <typename E>
328inline std::string enum_string(E v) {
329 if (const auto name = enum_name(v)) {
330 return std::string(*name);
331 }
Dominik Laskowski04667b72021-12-15 13:14:54 -0800332 return to_string(to_underlying(v));
Dominik Laskowski75788452021-02-09 18:51:25 -0800333}
334
Ady Abrahamaa68d0a2023-09-22 17:28:07 -0700335// Returns a stringified enumerator with class, or its integral value if not named.
336//
337// enum class E { A, B, C, F = 5, ftl_last = F };
338//
339// assert(ftl::enum_string(E::C) == "E::C");
340// assert(ftl::enum_string(E{3}) == "3");
341//
342template <typename E>
343inline std::string enum_string_full(E v) {
344 if (const auto name = enum_name_full(v)) {
345 return std::string(*name);
346 }
347 return to_string(to_underlying(v));
348}
349
Dominik Laskowski75788452021-02-09 18:51:25 -0800350// Returns a stringified flag enumerator, or its integral value if not named.
351//
352// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
353//
354// assert(ftl::flag_string(F::Z) == "Z");
355// assert(ftl::flag_string(F{7}) == "0b111");
356//
357template <typename E>
358inline std::string flag_string(E v) {
359 if (const auto name = flag_name(v)) {
360 return std::string(*name);
361 }
362 constexpr auto radix = sizeof(E) == 1 ? Radix::kBin : Radix::kHex;
Dominik Laskowski04667b72021-12-15 13:14:54 -0800363 return to_string(to_underlying(v), radix);
Dominik Laskowski75788452021-02-09 18:51:25 -0800364}
365
366} // namespace android::ftl