diff options
Diffstat (limited to 'include/ftl/enum.h')
-rw-r--r-- | include/ftl/enum.h | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/include/ftl/enum.h b/include/ftl/enum.h new file mode 100644 index 0000000000..82af1d6cf8 --- /dev/null +++ b/include/ftl/enum.h @@ -0,0 +1,301 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cstddef> +#include <limits> +#include <optional> +#include <string_view> +#include <type_traits> +#include <utility> + +#include <ftl/string.h> + +// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the +// compiler-generated string literal for the signature of this function. The function is defined in +// the global namespace with a short name and inferred return type to reduce bloat in the read-only +// data segment. +template <typename E, E V> +constexpr auto ftl_enum() { + static_assert(std::is_enum_v<E>); + + using R = std::optional<std::string_view>; + using namespace std::literals; + + // The "pretty" signature has the following format: + // + // auto ftl_enum() [E = android::test::Enum, V = android::test::Enum::kValue] + // + std::string_view view = __PRETTY_FUNCTION__; + const auto template_begin = view.rfind('['); + const auto template_end = view.rfind(']'); + if (template_begin == view.npos || template_end == view.npos) return R{}; + + // Extract the template parameters without the enclosing brackets. Example (cont'd): + // + // E = android::test::Enum, V = android::test::Enum::kValue + // + view = view.substr(template_begin + 1, template_end - template_begin - 1); + const auto value_begin = view.rfind("V = "sv); + if (value_begin == view.npos) return R{}; + + // Example (cont'd): + // + // V = android::test::Enum::kValue + // + view = view.substr(value_begin); + const auto name_begin = view.rfind("::"sv); + if (name_begin == view.npos) return R{}; + + // Chop off the leading "::". + const auto name = view.substr(name_begin + 2); + + // A value that is not enumerated has the format "Enum)42". + return name.find(')') == view.npos ? R{name} : R{}; +} + +namespace android::ftl { + +// Trait for determining whether a type is specifically a scoped enum or not. By definition, a +// scoped enum is one that is not implicitly convertible to its underlying type. +// +// TODO: Replace with std::is_scoped_enum in C++23. +// +template <typename T, bool = std::is_enum_v<T>> +struct is_scoped_enum : std::false_type {}; + +template <typename T> +struct is_scoped_enum<T, true> : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> { +}; + +template <typename T> +inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value; + +// Shorthand for casting an enumerator to its integral value. +// +// TODO: Replace with std::to_underlying in C++23. +// +// enum class E { A, B, C }; +// static_assert(ftl::to_underlying(E::B) == 1); +// +template <typename E> +constexpr auto to_underlying(E v) { + return static_cast<std::underlying_type_t<E>>(v); +} + +// Traits for retrieving an enum's range. An enum specifies its range by defining enumerators named +// ftl_first and ftl_last. If omitted, ftl_first defaults to 0, whereas ftl_last defaults to N - 1 +// where N is the bit width of the underlying type, but only if that type is unsigned, assuming the +// enumerators are flags. Also, note that unscoped enums must define both bounds, as casting out-of- +// range values results in undefined behavior if the underlying type is not fixed. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// static_assert(ftl::enum_begin_v<E> == E::A); +// static_assert(ftl::enum_last_v<E> == E::F); +// static_assert(ftl::enum_size_v<E> == 6); +// +// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; +// +// static_assert(ftl::enum_begin_v<F> == F{0}); +// static_assert(ftl::enum_last_v<F> == F{15}); +// static_assert(ftl::enum_size_v<F> == 16); +// +template <typename E, typename = void> +struct enum_begin { + static_assert(is_scoped_enum_v<E>, "Missing ftl_first enumerator"); + static constexpr E value{0}; +}; + +template <typename E> +struct enum_begin<E, std::void_t<decltype(E::ftl_first)>> { + static constexpr E value = E::ftl_first; +}; + +template <typename E> +inline constexpr E enum_begin_v = enum_begin<E>::value; + +template <typename E, typename = void> +struct enum_end { + using U = std::underlying_type_t<E>; + static_assert(is_scoped_enum_v<E> && std::is_unsigned_v<U>, "Missing ftl_last enumerator"); + + static constexpr E value{std::numeric_limits<U>::digits}; +}; + +template <typename E> +struct enum_end<E, std::void_t<decltype(E::ftl_last)>> { + static constexpr E value = E{to_underlying(E::ftl_last) + 1}; +}; + +template <typename E> +inline constexpr E enum_end_v = enum_end<E>::value; + +template <typename E> +inline constexpr E enum_last_v = E{to_underlying(enum_end_v<E>) - 1}; + +template <typename E> +struct enum_size { + static constexpr auto kBegin = to_underlying(enum_begin_v<E>); + static constexpr auto kEnd = to_underlying(enum_end_v<E>); + static_assert(kBegin < kEnd, "Invalid range"); + + static constexpr std::size_t value = kEnd - kBegin; + static_assert(value <= 64, "Excessive range size"); +}; + +template <typename E> +inline constexpr std::size_t enum_size_v = enum_size<E>::value; + +namespace details { + +template <auto V> +struct Identity { + static constexpr auto value = V; +}; + +template <typename E> +using make_enum_sequence = std::make_integer_sequence<std::underlying_type_t<E>, enum_size_v<E>>; + +template <typename E, template <E> class = Identity, typename = make_enum_sequence<E>> +struct EnumRange; + +template <typename E, template <E> class F, typename T, T... Vs> +struct EnumRange<E, F, std::integer_sequence<T, Vs...>> { + static constexpr auto kBegin = to_underlying(enum_begin_v<E>); + static constexpr auto kSize = enum_size_v<E>; + + using R = decltype(F<E{}>::value); + const R values[kSize] = {F<static_cast<E>(Vs + kBegin)>::value...}; + + constexpr const auto* begin() const { return values; } + constexpr const auto* end() const { return values + kSize; } +}; + +template <auto V> +struct EnumName { + static constexpr auto value = ftl_enum<decltype(V), V>(); +}; + +template <auto I> +struct FlagName { + using E = decltype(I); + using U = std::underlying_type_t<E>; + + static constexpr E V{U{1} << to_underlying(I)}; + static constexpr auto value = ftl_enum<E, V>(); +}; + +} // namespace details + +// Returns an iterable over the range of an enum. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// std::string string; +// for (E v : ftl::enum_range<E>()) { +// string += ftl::enum_name(v).value_or("?"); +// } +// +// assert(string == "ABC??F"); +// +template <typename E> +constexpr auto enum_range() { + return details::EnumRange<E>{}; +} + +// Returns a stringified enumerator at compile time. +// +// enum class E { A, B, C }; +// static_assert(ftl::enum_name<E::B>() == "B"); +// +template <auto V> +constexpr std::string_view enum_name() { + constexpr auto kName = ftl_enum<decltype(V), V>(); + static_assert(kName, "Unknown enumerator"); + return *kName; +} + +// Returns a stringified enumerator, possibly at compile time. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// static_assert(ftl::enum_name(E::C).value_or("?") == "C"); +// static_assert(ftl::enum_name(E{3}).value_or("?") == "?"); +// +template <typename E> +constexpr std::optional<std::string_view> enum_name(E v) { + const auto value = to_underlying(v); + + constexpr auto kBegin = to_underlying(enum_begin_v<E>); + constexpr auto kLast = to_underlying(enum_last_v<E>); + if (value < kBegin || value > kLast) return {}; + + constexpr auto kRange = details::EnumRange<E, details::EnumName>{}; + return kRange.values[value - kBegin]; +} + +// Returns a stringified flag enumerator, possibly at compile time. +// +// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; +// +// static_assert(ftl::flag_name(F::Z).value_or("?") == "Z"); +// static_assert(ftl::flag_name(F{0b111}).value_or("?") == "?"); +// +template <typename E> +constexpr std::optional<std::string_view> flag_name(E v) { + const auto value = to_underlying(v); + + // TODO: Replace with std::popcount and std::countr_zero in C++20. + if (__builtin_popcountll(value) != 1) return {}; + + constexpr auto kRange = details::EnumRange<E, details::FlagName>{}; + return kRange.values[__builtin_ctzll(value)]; +} + +// Returns a stringified enumerator, or its integral value if not named. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// assert(ftl::enum_string(E::C) == "C"); +// assert(ftl::enum_string(E{3}) == "3"); +// +template <typename E> +inline std::string enum_string(E v) { + if (const auto name = enum_name(v)) { + return std::string(*name); + } + return to_string(to_underlying(v)); +} + +// Returns a stringified flag enumerator, or its integral value if not named. +// +// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; +// +// assert(ftl::flag_string(F::Z) == "Z"); +// assert(ftl::flag_string(F{7}) == "0b111"); +// +template <typename E> +inline std::string flag_string(E v) { + if (const auto name = flag_name(v)) { + return std::string(*name); + } + constexpr auto radix = sizeof(E) == 1 ? Radix::kBin : Radix::kHex; + return to_string(to_underlying(v), radix); +} + +} // namespace android::ftl |