diff --git a/CHANGELOG.md b/CHANGELOG.md index 090bbbd..d60fbc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.2.22 * Change the way `mbo::types::Extend` types are constructed to support more complex and deeper nested types. +* Provide a new `mbo::types::extender::Default` which wraps all default extenders. # 0.2.21 diff --git a/mbo/types/extend.h b/mbo/types/extend.h index 90136c3..55aeeae 100644 --- a/mbo/types/extend.h +++ b/mbo/types/extend.h @@ -42,16 +42,7 @@ namespace mbo::types { // compare itself. In the above example `{"First", "Last"}` will be printed. // If compiled on Clang it will print `{first: "First", last: "Last"}`. template -struct Extend - : extender_internal::ExtendImpl< - T, - // Default `Extend` functionality - extender::AbslStringify, - extender::AbslHashable, - extender::Comparable, - extender::Printable, - extender::Streamable, - Extender...> {}; +struct Extend : extender_internal::ExtendImpl {}; // Same as `Extend` but without default extenders. This alows to control the // exact extender set to be used. diff --git a/mbo/types/extend_test.cc b/mbo/types/extend_test.cc index e13a80c..aac9cd8 100644 --- a/mbo/types/extend_test.cc +++ b/mbo/types/extend_test.cc @@ -197,9 +197,19 @@ int DumpStructVisitor( return 0; } -int PrintStructVisitor( +struct StructVisitorElement { + std::string format; + std::string indent; + std::string type; + std::string name; + std::string line; +}; + +void PrintStructVisitor( std::size_t /*field_index*/, - std::vector>& fields, + std::vector& fields, + std::size_t& max_type_length, + bool print, std::string_view format, std::string_view indent = {}, std::string_view type = {}, @@ -220,27 +230,33 @@ int PrintStructVisitor( return absl::StrFormat("Unknown format: '%s'", format); } }(); - std::cout << line; + if (print) { + std::cout << line; + } + max_type_length = std::max(max_type_length, type.length()); if (format.starts_with("%s%s %s =") && indent == " ") { - fields.emplace_back(format, indent, name); + fields.emplace_back(StructVisitorElement{.format{format}, .indent{indent}, .type{type}, .name{name}, .line = line}); } - return 0; } // NOLINTEND(bugprone-easily-swappable-parameters,cert-dcl50-cpp) // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg,hicpp-vararg) template -void Print(const T* ptr) { - std::size_t field_index = 0; - std::vector> fields; - __builtin_dump_struct(ptr, &PrintStructVisitor, field_index, fields); +std::size_t Print(const T* ptr, bool print = false) { + std::size_t field_index{0}; + std::vector fields; + std::size_t max_type_length{0}; + __builtin_dump_struct(ptr, &PrintStructVisitor, field_index, fields, max_type_length, print); std::cout << '\n'; - for (const auto& [format, indent, name] : fields) { - std::cout << format << " -> " << name << '\n'; + for (const auto& field : fields) { + std::cout << field.format << " -> " << field.name << '\n'; + } + if (print) { + field_index = 0; + __builtin_dump_struct(ptr, &DumpStructVisitor, field_index); } - field_index = 0; - __builtin_dump_struct(ptr, &DumpStructVisitor, field_index); + return max_type_length; } // NOLINTEND(cppcoreguidelines-pro-type-vararg,hicpp-vararg) @@ -280,12 +296,10 @@ TEST_F(ExtendTest, StreamableComplexFields) { R"({.index: 25, .person: {.name: {.first: "Hugo", .last: "Meyer"}, .age: 42}, .data: *{{"bar", "foo"}}})", R"({25, {{"Hugo", "Meyer"}, 42}, *{{"bar", "foo"}}})")); #ifdef __clang__ - if (HasFailure()) { - std::cout << "Person:\n"; - debug::Print(&person); - std::cout << "Person::person.name:\n"; - debug::Print(&person.person.name); - } + std::cout << "Person:\n"; + EXPECT_THAT(debug::Print(&person, HasFailure()), 621); + std::cout << "Person::person.name:\n"; + EXPECT_THAT(debug::Print(&person.person.name, HasFailure()), 603); #endif // __clang__ } @@ -459,7 +473,7 @@ TEST_F(ExtendTest, ExtenderNames) { WhenSorted(ElementsAre("AbslStringify", "Comparable", "Printable", "Streamable"))); EXPECT_THAT( // All defaults, and as extra. T4::RegisteredExtenderNames(), - WhenSorted(ElementsAre("AbslHashable", "AbslStringify", "Comparable", "Printable", "Streamable"))); + WhenSorted(ElementsAre("AbslHashable", "AbslStringify", "Comparable", "Default", "Printable", "Streamable"))); } } // namespace diff --git a/mbo/types/extender.h b/mbo/types/extender.h index 00c7852..3cc8b0c 100644 --- a/mbo/types/extender.h +++ b/mbo/types/extender.h @@ -117,7 +117,7 @@ struct MakeExtender { friend struct extender_internal::UseExtender; // CRTP: Inject the base extender and provide type `ExtenderInfo`. That way an - // mplementation can create a typedef `Type` as follows: + // implementation can create a typedef `Type` as follows: // // ```c++ // template @@ -320,6 +320,55 @@ struct StreamableImpl : ExtenderBase { // This default Extender is automatically available through `mb::types::Extend`. struct Streamable final : MakeExtender<"Streamable"_ts, StreamableImpl, AbslStringify> {}; +template +struct DefaultImpl : ExtenderBase { + using Type = typename ExtenderBase::ExtenderInfo::Type; +}; + +// The default extender is a wrapper around: +// * Streamable +// * Printable +// * Comparable +// * AbslHashable +// * AbslStringify +struct Default final { + using RequiredExtender = void; + + // NOTE: The list of wrapped extenders needs to be in sync with `mbo::types::extender_internal::ExtendImpl`. + + template + struct ImplT : StreamableImpl>>>> {}; + + static constexpr std::string_view GetExtenderName() { return decltype("Default"_ts)::str(); } + + private: + // This friend is necessary to keep symbol visibility in check. But it has to + // be either 'struct' or 'class' and thus results in `ImplT` being a struct. + template + friend struct extender_internal::UseExtender; + + // CRTP: Inject the base extender and provide type `ExtenderInfo`. + template + struct ExtenderBase : ExtenderInfoT::ExtenderBase { + private: + // List `ImplT` and all default implementations. + friend struct ImplT>; + template + friend struct StreamableImpl; + template + friend struct PrintableImpl; + template + friend struct ComparableImpl; + template + friend struct AbslHashableImpl; + template + friend struct AbslStringifyImpl; + using ExtenderInfo = ExtenderInfoT; + }; + + template + struct Impl : ImplT> {}; +}; } // namespace mbo::types::extender #endif // MBO_TYPES_EXTENDER_H_ diff --git a/mbo/types/internal/extend.h b/mbo/types/internal/extend.h index e170f57..f86fa36 100644 --- a/mbo/types/internal/extend.h +++ b/mbo/types/internal/extend.h @@ -100,6 +100,9 @@ template concept ExtenderListValid = (IsExtender && ... && (!HasDuplicateTypes && AllRequiredPresent)); +template +concept HasDefaultExtender = (std::is_same_v || ...); + // Base Extender implementation for CRTP functionality injection. // This must always be present. template @@ -130,10 +133,34 @@ struct ExtendImpl ExtenderInfo, // Type seeding to inject `Type = T`. ExtendBase, // CRTP functionality injection. Extender...> { - using RegisteredExtenders = std::tuple; - - static constexpr std::array RegisteredExtenderNames() { - return {Extender::GetExtenderName()...}; + using RegisteredExtenders = std::conditional_t< + HasDefaultExtender, + std::tuple< + // The list of default extenders must be in sync with the implementation of `extender::Default`. + extender::AbslStringify, + extender::AbslHashable, + extender::Comparable, + extender::Printable, + extender::Streamable, + // Actual provided extenders go last: + Extender...>, + std::tuple>; + + static constexpr std::array> RegisteredExtenderNames() { + if constexpr (HasDefaultExtender) { + return { + // The list of default extenders must be in sync with the implementation of `extender::Default`. + extender::AbslStringify::GetExtenderName(), + extender::AbslHashable::GetExtenderName(), + extender::Comparable::GetExtenderName(), + extender::Printable::GetExtenderName(), + extender::Streamable::GetExtenderName(), + // Actual provided extenders go last: + Extender::GetExtenderName()..., + }; + } else { + return {Extender::GetExtenderName()...}; + } } };