/* * 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 #include #include #include #include #include #include // Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as // std::optional 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 constexpr auto ftl_enum_builder() { static_assert(std::is_enum_v); using R = std::optional; 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 pos = S ? view.rfind("::"sv) - 2 : view.npos; const auto name_begin = view.rfind("::"sv, pos); 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{}; } // Returns the name of enumerator E::V (i.e. "V") as std::optional template constexpr auto ftl_enum() { return ftl_enum_builder(); } // Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional template constexpr auto ftl_enum_full() { return ftl_enum_builder(); } 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 > struct is_scoped_enum : std::false_type {}; template struct is_scoped_enum : std::negation>> { }; template inline constexpr bool is_scoped_enum_v = is_scoped_enum::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 >> constexpr auto to_underlying(E v) { return static_cast>(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::A); // static_assert(ftl::enum_last_v == E::F); // static_assert(ftl::enum_size_v == 6); // // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; // // static_assert(ftl::enum_begin_v == F{0}); // static_assert(ftl::enum_last_v == F{15}); // static_assert(ftl::enum_size_v == 16); // template struct enum_begin { static_assert(is_scoped_enum_v, "Missing ftl_first enumerator"); static constexpr E value{0}; }; template struct enum_begin> { static constexpr E value = E::ftl_first; }; template inline constexpr E enum_begin_v = enum_begin::value; template struct enum_end { using U = std::underlying_type_t; static_assert(is_scoped_enum_v && std::is_unsigned_v, "Missing ftl_last enumerator"); static constexpr E value{std::numeric_limits::digits}; }; template struct enum_end> { static constexpr E value = E{to_underlying(E::ftl_last) + 1}; }; template inline constexpr E enum_end_v = enum_end::value; template inline constexpr E enum_last_v = E{to_underlying(enum_end_v) - 1}; template struct enum_size { static constexpr auto kBegin = to_underlying(enum_begin_v); static constexpr auto kEnd = to_underlying(enum_end_v); static_assert(kBegin < kEnd, "Invalid range"); static constexpr std::size_t value = kEnd - kBegin; static_assert(value <= 64, "Excessive range size"); }; template inline constexpr std::size_t enum_size_v = enum_size::value; namespace details { template struct Identity { static constexpr auto value = V; }; template using make_enum_sequence = std::make_integer_sequence, enum_size_v>; template class = Identity, typename = make_enum_sequence> struct EnumRange; template class F, typename T, T... Vs> struct EnumRange> { static constexpr auto kBegin = to_underlying(enum_begin_v); static constexpr auto kSize = enum_size_v; using R = decltype(F::value); const R values[kSize] = {F(Vs + kBegin)>::value...}; constexpr const auto* begin() const { return values; } constexpr const auto* end() const { return values + kSize; } }; template struct EnumName { static constexpr auto value = ftl_enum(); }; template struct EnumNameFull { static constexpr auto value = ftl_enum_full(); }; template struct FlagName { using E = decltype(I); using U = std::underlying_type_t; static constexpr E V{U{1} << to_underlying(I)}; static constexpr auto value = ftl_enum(); }; } // 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()) { // string += ftl::enum_name(v).value_or("?"); // } // // assert(string == "ABC??F"); // template constexpr auto enum_range() { return details::EnumRange{}; } // Returns a stringified enumerator at compile time. // // enum class E { A, B, C }; // static_assert(ftl::enum_name() == "B"); // template constexpr std::string_view enum_name() { constexpr auto kName = ftl_enum(); static_assert(kName, "Unknown enumerator"); return *kName; } // Returns a stringified enumerator with class at compile time. // // enum class E { A, B, C }; // static_assert(ftl::enum_name() == "E::B"); // template constexpr std::string_view enum_name_full() { constexpr auto kName = ftl_enum_full(); 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 constexpr std::optional enum_name(E v) { const auto value = to_underlying(v); constexpr auto kBegin = to_underlying(enum_begin_v); constexpr auto kLast = to_underlying(enum_last_v); if (value < kBegin || value > kLast) return {}; constexpr auto kRange = details::EnumRange{}; return kRange.values[value - kBegin]; } // Returns a stringified enumerator with class, possibly at compile time. // // enum class E { A, B, C, F = 5, ftl_last = F }; // // static_assert(ftl::enum_name(E::C).value_or("?") == "E::C"); // static_assert(ftl::enum_name(E{3}).value_or("?") == "?"); // template constexpr std::optional enum_name_full(E v) { const auto value = to_underlying(v); constexpr auto kBegin = to_underlying(enum_begin_v); constexpr auto kLast = to_underlying(enum_last_v); if (value < kBegin || value > kLast) return {}; constexpr auto kRange = details::EnumRange{}; 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 constexpr std::optional 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{}; 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 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 enumerator with class, or its integral value if not named. // // enum class E { A, B, C, F = 5, ftl_last = F }; // // assert(ftl::enum_string(E::C) == "E::C"); // assert(ftl::enum_string(E{3}) == "3"); // template inline std::string enum_string_full(E v) { if (const auto name = enum_name_full(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 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