ftl: add enum_string_full which includes the enum class name
Bug: 300701739
Test: presubmit
Change-Id: I235275b5d299efea779851434c392ca4e07ca4fd
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 075d12b..2c86e2e 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -25,12 +25,12 @@
#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() {
+// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "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 <bool S, typename E, E V>
+constexpr auto ftl_enum_builder() {
static_assert(std::is_enum_v<E>);
using R = std::optional<std::string_view>;
@@ -58,7 +58,9 @@
// V = android::test::Enum::kValue
//
view = view.substr(value_begin);
- const auto name_begin = view.rfind("::"sv);
+ 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 "::".
@@ -68,6 +70,18 @@
return name.find(')') == view.npos ? R{name} : R{};
}
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum() {
+ return ftl_enum_builder<false, E, V>();
+}
+
+// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum_full() {
+ return ftl_enum_builder<true, E, V>();
+}
+
namespace android::ftl {
// Trait for determining whether a type is specifically a scoped enum or not. By definition, a
@@ -191,6 +205,11 @@
static constexpr auto value = ftl_enum<decltype(V), V>();
};
+template <auto V>
+struct EnumNameFull {
+ static constexpr auto value = ftl_enum_full<decltype(V), V>();
+};
+
template <auto I>
struct FlagName {
using E = decltype(I);
@@ -230,6 +249,18 @@
return *kName;
}
+// Returns a stringified enumerator with class at compile time.
+//
+// enum class E { A, B, C };
+// static_assert(ftl::enum_name<E::B>() == "E::B");
+//
+template <auto V>
+constexpr std::string_view enum_name_full() {
+ constexpr auto kName = ftl_enum_full<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 };
@@ -249,6 +280,25 @@
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 <typename E>
+constexpr std::optional<std::string_view> enum_name_full(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::EnumNameFull>{};
+ 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 };
@@ -282,6 +332,21 @@
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 <typename E>
+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 };
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01..b68c2c3 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@
static_assert(ftl::enum_name(E::C).value_or("?") == "C");
static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@
static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
// Though not flags, the enumerators are within the implicit range of bit indices.
enum class Planet : std::uint8_t {
kMercury,
@@ -81,6 +90,9 @@
static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
// Unscoped enum must define explicit range, even if the underlying type is fixed.
enum Temperature : int {
kRoom = 20,
@@ -122,16 +134,28 @@
EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
}
{
EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+ EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+ EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
}
}
@@ -158,16 +182,30 @@
EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+ EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
}
{
EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+ EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+ EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
}
}