Skip to content

Commit

Permalink
* Provide a new mbo::types::extender::Default which wraps all defau…
Browse files Browse the repository at this point in the history
…lt extenders. (#19)
  • Loading branch information
helly25 committed Mar 24, 2024
1 parent 1f2dbde commit e4f7477
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
11 changes: 1 addition & 10 deletions mbo/types/extend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<typename T, typename... Extender>
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<T, extender::Default, Extender...> {};

// Same as `Extend` but without default extenders. This alows to control the
// exact extender set to be used.
Expand Down
54 changes: 34 additions & 20 deletions mbo/types/extend_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::tuple<std::string, std::string, std::string>>& fields,
std::vector<StructVisitorElement>& fields,
std::size_t& max_type_length,
bool print,
std::string_view format,
std::string_view indent = {},
std::string_view type = {},
Expand All @@ -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<typename T>
void Print(const T* ptr) {
std::size_t field_index = 0;
std::vector<std::tuple<std::string, std::string, std::string>> 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<StructVisitorElement> 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)
Expand Down Expand Up @@ -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__
}

Expand Down Expand Up @@ -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
Expand Down
51 changes: 50 additions & 1 deletion mbo/types/extender.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename ExtenderBase>
Expand Down Expand Up @@ -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<typename ExtenderBase>
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<typename ExtenderBase>
struct ImplT : StreamableImpl<PrintableImpl<ComparableImpl<AbslHashableImpl<AbslStringifyImpl<ExtenderBase>>>>> {};

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<typename T, typename Extender>
friend struct extender_internal::UseExtender;

// CRTP: Inject the base extender and provide type `ExtenderInfo`.
template<typename ExtenderInfoT>
struct ExtenderBase : ExtenderInfoT::ExtenderBase {
private:
// List `ImplT` and all default implementations.
friend struct ImplT<ExtenderBase<ExtenderInfoT>>;
template<typename B>
friend struct StreamableImpl;
template<typename B>
friend struct PrintableImpl;
template<typename B>
friend struct ComparableImpl;
template<typename B>
friend struct AbslHashableImpl;
template<typename B>
friend struct AbslStringifyImpl;
using ExtenderInfo = ExtenderInfoT;
};

template<typename ExtenderInfoT>
struct Impl : ImplT<ExtenderBase<ExtenderInfoT>> {};
};
} // namespace mbo::types::extender

#endif // MBO_TYPES_EXTENDER_H_
35 changes: 31 additions & 4 deletions mbo/types/internal/extend.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ template<typename... Extender>
concept ExtenderListValid =
(IsExtender<Extender> && ... && (!HasDuplicateTypes<Extender...> && AllRequiredPresent<Extender...>));

template<typename... Extenders>
concept HasDefaultExtender = (std::is_same_v<Extenders, extender::Default> || ...);

// Base Extender implementation for CRTP functionality injection.
// This must always be present.
template<typename T>
Expand Down Expand Up @@ -130,10 +133,34 @@ struct ExtendImpl
ExtenderInfo<T, void>, // Type seeding to inject `Type = T`.
ExtendBase, // CRTP functionality injection.
Extender...> {
using RegisteredExtenders = std::tuple<Extender...>;

static constexpr std::array<std::string_view, sizeof...(Extender)> RegisteredExtenderNames() {
return {Extender::GetExtenderName()...};
using RegisteredExtenders = std::conditional_t<
HasDefaultExtender<Extender...>,
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<Extender...>>;

static constexpr std::array<std::string_view, std::tuple_size_v<RegisteredExtenders>> RegisteredExtenderNames() {
if constexpr (HasDefaultExtender<Extender...>) {
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()...};
}
}
};

Expand Down

0 comments on commit e4f7477

Please sign in to comment.