From 5e817b8c475c8318913e72fa3a4223a169bff296 Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Thu, 20 Jun 2024 17:00:30 -0400 Subject: [PATCH 1/6] Add a row query function --- best/container/internal/row.h | 93 +++++++++++++++++++++++++++++++++++ best/container/row.h | 73 ++++++++++++++++++++++++++- best/container/row_test.cc | 21 ++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) diff --git a/best/container/internal/row.h b/best/container/internal/row.h index 8218fbf..c9f7ffe 100644 --- a/best/container/internal/row.h +++ b/best/container/internal/row.h @@ -1,11 +1,104 @@ #ifndef BEST_CONTAINER_INTERNAL_ROW_H_ #define BEST_CONTAINER_INTERNAL_ROW_H_ +#include + #include "best/container/object.h" #include "best/meta/empty.h" +#include "best/meta/tags.h" #include "best/meta/tlist.h" namespace best::row_internal { +// This builds a set of lookup indices that find the first index that matches +// lookup up a column type or column name. +template +struct entry { + size_t table[n]; + size_t total = 0; + + constexpr void append(size_t m) { table[total++] = m; } +}; + +template +concept has_col_name = requires { + typename T::BestRowKey; + requires(!best::same); +}; + +template +struct lookup_table final : entry... { + size_t next_idx = 0; + + template + constexpr auto operator+(entry*) + requires(!has_col_name) + { + if constexpr (std::is_base_of_v, lookup_table>) { + static_cast&>(*this).append(next_idx++); + return *this; + } else { + return lookup_table{ + entry(*this)..., + entry{{next_idx}, 1}, + next_idx + 1, + }; + } + } + + template + constexpr auto operator+(entry*) + requires has_col_name + { + using K = T::BestRowKey; + if constexpr (std::is_base_of_v, lookup_table>) { + static_cast&>(*this).append(next_idx); + static_cast&>(*this).append(next_idx++); + return *this; + } else if constexpr (std::is_base_of_v, lookup_table>) { + static_cast&>(*this).append(next_idx++); + return lookup_table{ + entry(*this)..., + entry{{next_idx}, 1}, + next_idx + 1, + }; + } else { + return lookup_table{ + entry(*this)..., + entry{{next_idx}, 1}, + entry{{next_idx}, 1}, + next_idx + 1, + }; + } + } + + template + constexpr size_t count() const { + if constexpr (std::is_base_of_v, lookup_table>) { + return static_cast&>(*this).total; + } + return {}; + } +}; + +template +inline constexpr auto lookup = + (lookup_table{} + ... + (entry*){}); + +template , + size_t count = lut.template count()> +inline constexpr auto apply_lookup(auto cb) { + if constexpr (count == 0) { + return best::call(cb); + } else { + const size_t(&table)[sizeof...(Ts)] = + static_cast&>(lut).table; + + return [&](std::index_sequence) { + return best::call(cb, best::index...); + }(std::make_index_sequence{}); + } +} + template struct impl; diff --git a/best/container/row.h b/best/container/row.h index 2aae8ab..458d66a 100644 --- a/best/container/row.h +++ b/best/container/row.h @@ -8,6 +8,7 @@ #include "best/meta/empty.h" #include "best/meta/init.h" #include "best/meta/tags.h" +#include "best/meta/taxonomy.h" #include "best/meta/tlist.h" //! A product type, like `std::tuple`. @@ -100,6 +101,15 @@ class row final template using ptr = best::as_ptr>; // clang-format on + /// # `row::selectable` + /// + /// Whether an element of this row can be selected by the type `T`, in other + /// words, if `T` is among `Elems`, *or* there is a type `U` among `Elems` + /// such that `U::BestRowKey` is `T`. + template + static constexpr bool selectable = + row_internal::lookup.template count() > 0; + private: using impl = row_internal::impl; @@ -123,7 +133,11 @@ class row final /// Constructs a row by initializing each element from the corresponding /// argument. constexpr row(auto&&... args) - requires(best::constructible && ...) + requires(best::constructible && ...) && + (!best::same> || ...) + : impl{{best::in_place, best::in_place, BEST_FWD(args)}...} {} + constexpr row(devoid&&... args) + requires(sizeof...(args) > 0) : impl{{best::in_place, best::in_place, BEST_FWD(args)}...} {} /// # `row[index]` @@ -169,6 +183,22 @@ class row final template constexpr decltype(auto) get(best::index_t = {}) &&; // clang-format on + /// # `row::select()` + /// + /// Returns a row containing all types in this row that are either of that + /// type, or which have a member alias named `BestRowKey` of that type. They + /// are returned in the order they occur in this row. + // clang-format off + template constexpr decltype(auto) select(best::tlist idx = {}) const& + requires selectable; + template constexpr decltype(auto) select(best::tlist idx = {}) & + requires selectable; + template constexpr decltype(auto) select(best::tlist idx = {}) const&& + requires selectable; + template constexpr decltype(auto) select(best::tlist idx = {}) && + requires selectable; + // clang-format on + /// # `row::first()`, `row::second()`, `row::last()` /// /// Gets the first, second, or last value; helper for `std::pair`-like uses. @@ -440,6 +470,47 @@ constexpr decltype(auto) row::get(best::index_t idx) && { } } +template +template +constexpr decltype(auto) row::select(best::tlist idx) const& + requires selectable +{ + return row_internal::apply_lookup( + [&](index_t... idx) { + return best::row...>(at(idx)...); + }); +} +template +template +constexpr decltype(auto) row::select(best::tlist idx) & + requires selectable +{ + return row_internal::apply_lookup( + [&](index_t... idx) { + return best::row...>(at(idx)...); + }); +} +template +template +constexpr decltype(auto) row::select(best::tlist idx) const&& + requires selectable +{ + return row_internal::apply_lookup( + [&](index_t... idx) { + return best::row...>(BEST_MOVE(*this).at(idx)...); + }); +} +template +template +constexpr decltype(auto) row::select(best::tlist idx) && + requires selectable +{ + return row_internal::apply_lookup( + [&](index_t... idx) { + return best::row...>(BEST_MOVE(*this).at(idx)...); + }); +} + // XXX: This code tickles a clang-format bug. template constexpr decltype(auto) row::first() const& diff --git a/best/container/row_test.cc b/best/container/row_test.cc index 5a7a0e6..49a62d4 100644 --- a/best/container/row_test.cc +++ b/best/container/row_test.cc @@ -39,4 +39,25 @@ best::test ToString = [](auto& t) { t.expect_eq(best::format("{:?}", x2), "(1, 2)"); t.expect_eq(best::format("{:?}", x3), "(1, void, 3)"); }; + +struct Tag { + using BestRowKey = Tag; + bool operator==(const Tag&) const = default; +}; +struct Tagged1 { + using BestRowKey = Tag; + bool operator==(const Tagged1&) const = default; +}; + +best::test Select = [](auto& t) { + best::row x0{1, 2, nullptr, 4}; + + t.expect_eq(x0.select(), best::row(1, 4)); + t.expect_eq(x0.select(), best::row(nullptr)); + t.expect_eq(x0.select(), best::row()); + + best::row x1{42, {}, {}}; + t.expect_eq(x1.select(), best::row(Tagged1())); + t.expect_eq(x1.select(), best::row(Tagged1(), Tag())); +}; } // namespace best::row_test \ No newline at end of file From 8f30a0281644be285ab9bf23b52b972eba228c38 Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Thu, 20 Jun 2024 22:00:03 -0400 Subject: [PATCH 2/6] add reflection --- best/base/fwd.h | 9 + best/container/choice.h | 66 +++-- best/container/choice_test.cc | 8 +- best/container/internal/choice.h | 84 ++++-- best/container/option.h | 114 +++++---- best/container/result.h | 48 ++-- best/container/row.h | 51 ++-- best/container/row_test.cc | 8 + best/meta/BUILD | 24 ++ best/meta/empty.h | 30 ++- best/meta/internal/reflect.h | 126 +++++++++ best/meta/internal/tlist.h | 22 ++ best/meta/reflect.h | 412 ++++++++++++++++++++++++++++++ best/meta/reflect_test.cc | 69 +++++ best/meta/tags.h | 9 + best/meta/taxonomy.h | 34 +++ best/meta/tlist.h | 8 + best/text/BUILD | 1 + best/text/internal/format_impls.h | 24 +- 19 files changed, 992 insertions(+), 155 deletions(-) create mode 100644 best/meta/internal/reflect.h create mode 100644 best/meta/reflect.h create mode 100644 best/meta/reflect_test.cc diff --git a/best/base/fwd.h b/best/base/fwd.h index 05a48b2..afc223b 100644 --- a/best/base/fwd.h +++ b/best/base/fwd.h @@ -56,6 +56,15 @@ class track_location; // best/meta/ebo.h struct empty; +// best/meta/reflect.h +class mirror; +template +class reflected_field; +template +class reflected_value; +template +class reflected_type; + // best/meta/tlist.h template class tlist; diff --git a/best/container/choice.h b/best/container/choice.h index 9bfacec..242eeca 100644 --- a/best/container/choice.h +++ b/best/container/choice.h @@ -238,27 +238,26 @@ class choice final { return *at(t); } - /// # `choice::match()` + /// # `choice::match()`, `choice::index_match()` /// /// Calls one of `cases` (chosen by overload resolution) on the currently /// chosen alternative. /// /// The chosen callback is, where `n` is the current alternative, the callback - /// that matches the first concept in this list: + /// that matches the concept `best::callable)>`. However, if + /// `choice::type` is void, it instead tests for `best::callable`, + /// if that fails, `best::callable`. /// - /// - `best::callable, ref)>` - /// - `best::callable)>` - /// - /// If `choice::type` is void, the list is instead: - /// - /// - `best::callable)>` - /// - `best::callable` - /// - `best::callable, best::empty)>` - /// - `best::callable` + /// `best::index_match()` is identical, except that callbacks are expected to + /// accept a `best::index_t` as their first argument. constexpr decltype(auto) match(auto... cases) const&; constexpr decltype(auto) match(auto... cases) &; constexpr decltype(auto) match(auto... cases) const&&; constexpr decltype(auto) match(auto... cases) &&; + constexpr decltype(auto) index_match(auto... cases) const&; + constexpr decltype(auto) index_match(auto... cases) &; + constexpr decltype(auto) index_match(auto... cases) const&&; + constexpr decltype(auto) index_match(auto... cases) &&; /// # `choice::permute()` /// @@ -274,7 +273,7 @@ class choice final { best::vlist permutation = {}) const& requires(inverse.has_value()) { - return match([&](auto tag, auto&&... value) { + return index_match([&](auto tag, auto&&... value) { return best::choice...>(best::index<(*inverse)[tag.value]>, BEST_FWD(value)...); }); @@ -290,7 +289,7 @@ class choice final { #undef BEST_CHOICE_CLANG_FORMAT_HACK_ requires(inverse.has_value()) { - return match([&](auto tag, auto&&... value) { + return index_match([&](auto tag, auto&&... value) { return best::choice...>(best::index<(*inverse)[tag.value]>, BEST_FWD(value)...); }); @@ -299,11 +298,10 @@ class choice final { friend void BestFmt(auto& fmt, const choice& ch) requires requires(best::object... alts) { (fmt.format(alts), ...); } { - ch.match( - [&](best::index_t) { fmt.format("choice<{}>(void)", n); }, - [&](auto idx, const auto& value) { - fmt.format("choice<{}>({:!})", idx.value, value); - }); + ch.index_match([&](auto idx) { fmt.format("choice<{}>(void)", idx.value); }, + [&](auto idx, const auto& value) { + fmt.format("choice<{}>({:!})", idx.value, value); + }); } template @@ -321,8 +319,8 @@ class choice final { requires(best::equatable && ...) { return which() == that.which() && // - match( - [] { return true; }, // void case. + index_match( + [&](auto) { return true; }, // void case. [&](auto tag, const auto& value) { return value == that[tag]; }); } @@ -333,10 +331,11 @@ class choice final { { return (which() <=> that.which())->*best::or_cmp([&] { using Output = best::common_ord...>; - return match([]() -> Output { return best::Equal; }, // void case. - [&](auto tag, const auto& value) -> Output { - return value <=> that[tag]; - }); + return index_match( + [&](auto) -> Output { return best::Equal; }, // void case. + [&](auto tag, const auto& value) -> Output { + return value <=> that[tag]; + }); }); } @@ -515,6 +514,25 @@ constexpr decltype(auto) choice::match(auto... cases) && { return BEST_MOVE(*this).impl().match( choice_internal::Overloaded(BEST_FWD(cases)...)); } + +template +constexpr decltype(auto) choice::index_match(auto... cases) const& { + return impl().index_match(choice_internal::Overloaded(BEST_FWD(cases)...)); +} +template +constexpr decltype(auto) choice::index_match(auto... cases) & { + return impl().index_match(choice_internal::Overloaded(BEST_FWD(cases)...)); +} +template +constexpr decltype(auto) choice::index_match(auto... cases) const&& { + return BEST_MOVE(*this).impl().index_match( + choice_internal::Overloaded(BEST_FWD(cases)...)); +} +template +constexpr decltype(auto) choice::index_match(auto... cases) && { + return BEST_MOVE(*this).impl().index_match( + choice_internal::Overloaded(BEST_FWD(cases)...)); +} } // namespace best #endif // BEST_CONTAINER_CHOICE_H_ \ No newline at end of file diff --git a/best/container/choice_test.cc b/best/container/choice_test.cc index 71542c3..e79ac08 100644 --- a/best/container/choice_test.cc +++ b/best/container/choice_test.cc @@ -115,13 +115,13 @@ best::test Match = [](auto& t) { best::choice x2(best::index<0>, 42); best::choice x3(best::index<1>, 45); - t.expect_eq(x2.match( // + t.expect_eq(x2.index_match( // [](best::index_t<0>, int x) { return x * 2; }, // - [](int x) { return x; }), + [](auto, int x) { return x; }), 84); - t.expect_eq(x3.match( // + t.expect_eq(x3.index_match( // [](best::index_t<0>, int x) { return x * 2; }, // - [](int x) { return x; }), + [](auto, int x) { return x; }), 45); }; diff --git a/best/container/internal/choice.h b/best/container/internal/choice.h index 9e83581..ac37557 100644 --- a/best/container/internal/choice.h +++ b/best/container/internal/choice.h @@ -143,7 +143,7 @@ class impl : public storage { constexpr impl(const impl& that) requires(!info.trivial_copy) { - that.match([&](auto tag, auto&... value) { + that.index_match([&](auto tag, auto&... value) { std::construct_at(this, tag, value...); }); } @@ -154,7 +154,8 @@ class impl : public storage { constexpr impl& operator=(const impl& that) requires(!info.trivial_copy) { - that.match([&](auto tag, auto&... value) { emplace(value...); }); + that.index_match( + [&](auto tag, auto&... value) { emplace(value...); }); return *this; } @@ -164,7 +165,7 @@ class impl : public storage { constexpr impl(impl&& that) requires(!info.trivial_move) { - that.match([&](auto tag, auto&... value) { + that.index_match([&](auto tag, auto&... value) { std::construct_at(this, tag, BEST_MOVE(value)...); }); } @@ -175,7 +176,7 @@ class impl : public storage { constexpr impl& operator=(impl&& that) requires(!info.trivial_move) { - that.match([&](auto tag, auto&... value) { + that.index_match([&](auto tag, auto&... value) { emplace(BEST_MOVE(value)...); }); return *this; @@ -187,9 +188,7 @@ class impl : public storage { constexpr ~impl() requires(!info.trivial_dtor) { - match([](auto, auto&... value) { - (std::destroy_at(best::addr(value)), ...); - }); + match([](auto&... value) { (std::destroy_at(best::addr(value)), ...); }); } template @@ -264,6 +263,33 @@ class impl : public storage { make_match_arm(BEST_MOVE(*this), BEST_FWD(callback))); } + template + BEST_INLINE_SYNTHETIC constexpr decltype(auto) index_match( + F&& callback) const& { + return JumpTable[tag()]( + make_index_match_arm(*this, BEST_FWD(callback))); + } + template + BEST_INLINE_SYNTHETIC constexpr decltype(auto) index_match(F&& callback) & { + return JumpTable[tag()]( + make_index_match_arm(*this, BEST_FWD(callback))); + } + template + BEST_INLINE_SYNTHETIC constexpr decltype(auto) index_match( + F&& callback) const&& { + return JumpTable[tag()]( + make_index_match_arm(BEST_MOVE(*this), BEST_FWD(callback))); + } + template + BEST_INLINE_SYNTHETIC constexpr decltype(auto) index_match(F&& callback) && { + return JumpTable[tag()]( + make_index_match_arm(BEST_MOVE(*this), BEST_FWD(callback))); + } + private: template constexpr static auto make_jump_table(std::index_sequence) { @@ -279,24 +305,16 @@ class impl : public storage { template constexpr static auto make_match_arm(auto&& self, F&& callback) { return [&](best::index_t tag) -> decltype(auto) { - using I = decltype(tag); using Type = best::refcopy, decltype(self)&&>; // This needs to be dependent on tag, even trivially, so that the call // of callback(empty) in one of the if statements below uses two-phase // lookup. - using Empty = best::refcopy< - best::select, - decltype(self)&&>; + using Empty = best::dependent; if constexpr (best::is_void) { - if constexpr (best::callable) { - return best::call(BEST_FWD(callback), tag); - } else if constexpr (best::callable) { + if constexpr (best::callable) { return best::call(BEST_FWD(callback)); - } else if constexpr (best::callable) { - best::empty arg; - return best::call(BEST_FWD(callback), tag, static_cast(arg)); } else { best::empty arg; return best::call(BEST_FWD(callback), static_cast(arg)); @@ -305,13 +323,35 @@ class impl : public storage { unsafe u( "this function is only called after" "checking tag() via the jump table."); - if constexpr (best::callable) { - return best::call(BEST_FWD(callback), tag, - static_cast(*self.get(u, tag))); + return best::call(BEST_FWD(callback), + static_cast(*self.get(u, tag))); + } + }; + } + + template + constexpr static auto make_index_match_arm(auto&& self, F&& callback) { + return [&](best::index_t tag) -> decltype(auto) { + using Type = best::refcopy, decltype(self)&&>; + + // This needs to be dependent on tag, even trivially, so that the call + // of callback(empty) in one of the if statements below uses two-phase + // lookup. + using Empty = best::dependent; + + if constexpr (best::is_void) { + if constexpr (best::callable) { + return best::call(BEST_FWD(callback), tag); } else { - return best::call(BEST_FWD(callback), - static_cast(*self.get(u, tag))); + best::empty arg; + return best::call(BEST_FWD(callback), tag, static_cast(arg)); } + } else { + unsafe u( + "this function is only called after" + "checking tag() via the jump table."); + return best::call(BEST_FWD(callback), tag, + static_cast(*self.get(u, tag))); } }; } diff --git a/best/container/option.h b/best/container/option.h index b0bf48a..02127f9 100644 --- a/best/container/option.h +++ b/best/container/option.h @@ -2,7 +2,6 @@ #define BEST_CONTAINER_OPTION_H_ #include -#include #include "best/base/fwd.h" #include "best/container/choice.h" @@ -143,15 +142,19 @@ class option final { /// # `option::option(T)` /// /// This converts a value into a non-empty option containing it. - template + template constexpr option(U&& arg) - requires not_forbidden_conversion && best::constructible && - (!best::moveable> || best::is_ref) + requires not_forbidden_conversion && best::constructible + : option(best::in_place, BEST_FWD(arg)) {} + constexpr option(best::devoid arg) + requires best::moveable + : option(best::in_place, BEST_MOVE(arg)) {} + template + constexpr option(best::bind_t, U&& arg) + requires not_forbidden_conversion && best::constructible : option(best::in_place, BEST_FWD(arg)) {} - template - constexpr option(U arg) - requires not_forbidden_conversion && best::constructible && - best::moveable && (!best::is_ref) + constexpr option(best::bind_t, best::devoid arg) + requires best::moveable : option(best::in_place, BEST_MOVE(arg)) {} /// # `option::option(option&)` @@ -248,10 +251,11 @@ class option final { /// a value *and* it satisfies the predicate. constexpr bool has_value() const { return !is_empty(); } constexpr bool has_value(auto&& p) const { - return impl().match([](best::index_t<0>) { return false; }, - [&](best::index_t<1>, auto&&... args) { - return best::call(BEST_FWD(p), BEST_FWD(args)...); - }); + return impl().index_match([&](best::index_t<0>) { return false; }, + [&](best::index_t<1>, auto&&... args) { + return best::call(BEST_FWD(p), + BEST_FWD(args)...); + }); } constexpr explicit operator bool() const { return has_value(); } @@ -802,6 +806,8 @@ class option final { template option(T&&) -> option>; +template +option(best::bind_t, T&&) -> option; option(best::none_t) -> option; inline constexpr best::option VoidOption{best::in_place}; @@ -841,24 +847,26 @@ constexpr option::rref option::value(best::location loc) && { template constexpr auto option::map(auto&& f) const& { using U = best::call_result; - return impl().match([](best::index_t<0>) -> option { return best::none; }, - [&](best::index_t<1>, auto&&... args) -> option { - return best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + return impl().index_match( + [&](best::index_t<0>) -> option { return best::none; }, + [&](best::index_t<1>, auto&&... args) -> option { + return best::call(BEST_FWD(f), BEST_FWD(args)...); + }); } template constexpr auto option::map(auto&& f) & { using U = best::call_result; - return impl().match([](best::index_t<0>) -> option { return best::none; }, - [&](best::index_t<1>, auto&&... args) -> option { - return best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + return impl().index_match( + [&](best::index_t<0>) -> option { return best::none; }, + [&](best::index_t<1>, auto&&... args) -> option { + return best::call(BEST_FWD(f), BEST_FWD(args)...); + }); } template constexpr auto option::map(auto&& f) const&& { using U = best::call_result; - return BEST_MOVE(*this).impl().match( - [](best::index_t<0>) -> option { return best::none; }, + return BEST_MOVE(*this).impl().index_match( + [&](best::index_t<0>) -> option { return best::none; }, [&](best::index_t<1>, auto&&... args) -> option { return best::call(BEST_FWD(f), BEST_FWD(args)...); }); @@ -866,8 +874,8 @@ constexpr auto option::map(auto&& f) const&& { template constexpr auto option::map(auto&& f) && { using U = best::call_result; - return BEST_MOVE(*this).impl().match( - [](best::index_t<0>) -> option { return best::none; }, + return BEST_MOVE(*this).impl().index_match( + [&](best::index_t<0>) -> option { return best::none; }, [&](best::index_t<1>, auto&&... args) -> option { return best::call(BEST_FWD(f), BEST_FWD(args)...); }); @@ -892,58 +900,60 @@ constexpr auto option::map(auto&& d, auto&& f) && { template constexpr const option& option::inspect(auto&& f) const& { - impl().match([](best::index_t<0>) {}, - [&](best::index_t<1>, auto&&... args) { - best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + impl().index_match([&](best::index_t<0>) {}, + [&](best::index_t<1>, auto&&... args) { + best::call(BEST_FWD(f), BEST_FWD(args)...); + }); return *this; } template constexpr option& option::inspect(auto&& f) & { - impl().match([](best::index_t<0>) {}, - [&](best::index_t<1>, auto&&... args) { - best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + impl().index_match([&](best::index_t<0>) {}, + [&](best::index_t<1>, auto&&... args) { + best::call(BEST_FWD(f), BEST_FWD(args)...); + }); return *this; } template constexpr const option&& option::inspect(auto&& f) const&& { - BEST_MOVE(*this).impl().match([](best::index_t<0>) {}, - [&](best::index_t<1>, auto&&... args) { - best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + BEST_MOVE(*this).impl().index_match([&](best::index_t<0>) {}, + [&](best::index_t<1>, auto&&... args) { + best::call(BEST_FWD(f), + BEST_FWD(args)...); + }); return BEST_MOVE(*this); } template constexpr option&& option::inspect(auto&& f) && { - BEST_MOVE(*this).impl().match([](best::index_t<0>) {}, - [&](best::index_t<1>, auto&&... args) { - best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + BEST_MOVE(*this).impl().index_match([&](best::index_t<0>) {}, + [&](best::index_t<1>, auto&&... args) { + best::call(BEST_FWD(f), + BEST_FWD(args)...); + }); return BEST_MOVE(*this); } template constexpr auto option::then(auto&& f) const& { using U = best::unref>; - return impl().match([](best::index_t<0>) -> U { return best::none; }, - [&](best::index_t<1>, auto&&... args) -> U { - return best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + return impl().index_match([&](best::index_t<0>) -> U { return best::none; }, + [&](best::index_t<1>, auto&&... args) -> U { + return best::call(BEST_FWD(f), BEST_FWD(args)...); + }); } template constexpr auto option::then(auto&& f) & { using U = best::unref>; - return impl().match([](best::index_t<0>) -> U { return best::none; }, - [&](best::index_t<1>, auto&&... args) -> U { - return best::call(BEST_FWD(f), BEST_FWD(args)...); - }); + return impl().index_match([&](best::index_t<0>) -> U { return best::none; }, + [&](best::index_t<1>, auto&&... args) -> U { + return best::call(BEST_FWD(f), BEST_FWD(args)...); + }); } template constexpr auto option::then(auto&& f) const&& { using U = best::unref>; - return BEST_MOVE(*this).impl().match( - [](best::index_t<0>) -> U { return best::none; }, + return BEST_MOVE(*this).impl().index_match( + [&](best::index_t<0>) -> U { return best::none; }, [&](best::index_t<1>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }); @@ -951,8 +961,8 @@ constexpr auto option::then(auto&& f) const&& { template constexpr auto option::then(auto&& f) && { using U = best::unref>; - return BEST_MOVE(*this).impl().match( - [](best::index_t<0>) -> U { return best::none; }, + return BEST_MOVE(*this).impl().index_match( + [&](best::index_t<0>) -> U { return best::none; }, [&](best::index_t<1>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }); diff --git a/best/container/result.h b/best/container/result.h index 899ba0e..2d4d436 100644 --- a/best/container/result.h +++ b/best/container/result.h @@ -440,44 +440,44 @@ constexpr auto result::err() && -> option { template constexpr auto result::map(auto&& f) const& { using U = best::call_result; - return impl().match( + return impl().index_match( [&](best::index_t<0>, auto&&... args) -> result { return best::ok(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<1>, auto&&... args) -> result { + [&](best::index_t<1>, auto&&... args) -> result { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::map(auto&& f) & { using U = best::call_result; - return impl().match( + return impl().index_match( [&](best::index_t<0>, auto&&... args) -> result { return best::ok(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<1>, auto&&... args) -> result { + [&](best::index_t<1>, auto&&... args) -> result { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::map(auto&& f) const&& { using U = best::call_result; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<0>, auto&&... args) -> result { return best::ok(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<1>, auto&&... args) -> result { + [&](best::index_t<1>, auto&&... args) -> result { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::map(auto&& f) && { using U = best::call_result; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<0>, auto&&... args) -> result { return best::ok(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<1>, auto&&... args) -> result { + [&](best::index_t<1>, auto&&... args) -> result { return best::err(BEST_FWD(args)...); }); } @@ -485,44 +485,44 @@ constexpr auto result::map(auto&& f) && { template constexpr auto result::map_err(auto&& f) const& { using U = best::call_result; - return impl().match( + return impl().index_match( [&](best::index_t<1>, auto&&... args) -> result { return best::err(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<0>, auto&&... args) -> result { + [&](best::index_t<0>, auto&&... args) -> result { return best::ok(BEST_FWD(args)...); }); } template constexpr auto result::map_err(auto&& f) & { using U = best::call_result; - return impl().match( + return impl().index_match( [&](best::index_t<1>, auto&&... args) -> result { return best::err(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<0>, auto&&... args) -> result { + [&](best::index_t<0>, auto&&... args) -> result { return best::ok(BEST_FWD(args)...); }); } template constexpr auto result::map_err(auto&& f) const&& { using U = best::call_result; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<1>, auto&&... args) -> result { return best::err(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<0>, auto&&... args) -> result { + [&](best::index_t<0>, auto&&... args) -> result { return best::ok(BEST_FWD(args)...); }); } template constexpr auto result::map_err(auto&& f) && { using U = best::call_result; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<1>, auto&&... args) -> result { return best::err(best::call(BEST_FWD(f), BEST_FWD(args)...)); }, - [](best::index_t<0>, auto&&... args) -> result { + [&](best::index_t<0>, auto&&... args) -> result { return best::ok(BEST_FWD(args)...); }); } @@ -530,44 +530,44 @@ constexpr auto result::map_err(auto&& f) && { template constexpr auto result::then(auto&& f) const& { using U = best::unref>; - return impl().match( + return impl().index_match( [&](best::index_t<0>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }, - [](best::index_t<1>, auto&&... args) -> U { + [&](best::index_t<1>, auto&&... args) -> U { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::then(auto&& f) & { using U = best::unref>; - return impl().match( + return impl().index_match( [&](best::index_t<0>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }, - [](best::index_t<1>, auto&&... args) -> U { + [&](best::index_t<1>, auto&&... args) -> U { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::then(auto&& f) const&& { using U = best::unref>; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<0>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }, - [](best::index_t<1>, auto&&... args) -> U { + [&](best::index_t<1>, auto&&... args) -> U { return best::err(BEST_FWD(args)...); }); } template constexpr auto result::then(auto&& f) && { using U = best::unref>; - return BEST_MOVE(*this).impl().match( + return BEST_MOVE(*this).impl().index_match( [&](best::index_t<0>, auto&&... args) -> U { return best::call(BEST_FWD(f), BEST_FWD(args)...); }, - [](best::index_t<1>, auto&&... args) -> U { + [&](best::index_t<1>, auto&&... args) -> U { return best::err(BEST_FWD(args)...); }); } diff --git a/best/container/row.h b/best/container/row.h index 458d66a..1514f86 100644 --- a/best/container/row.h +++ b/best/container/row.h @@ -25,7 +25,8 @@ namespace best { template concept is_row = requires { { - best::unref::types.apply([]() -> best::row {}) + best::unref::types.apply( + []() -> best::row { std::abort(); }) } -> best::same; }; @@ -140,6 +141,24 @@ class row final requires(sizeof...(args) > 0) : impl{{best::in_place, best::in_place, BEST_FWD(args)}...} {} + constexpr row(best::bind_t, auto&&... args) + requires(best::constructible && ...) && + (!best::same> || ...) + : impl{{best::in_place, best::in_place, BEST_FWD(args)}...} {} + constexpr row(best::bind_t, devoid&&... args) + requires(sizeof...(args) > 0) + : impl{{best::in_place, best::in_place, BEST_FWD(args)}...} {} + + /// # `row::size()` + /// + /// Returns the number of elements in this row. + constexpr static size_t size() { return types.size(); } + + /// # `row::is_empty()` + /// + /// Returns whether this is the empty row `best::row<>`. + constexpr static bool is_empty() { return types.size() == 0; } + /// # `row[index]` /// /// Returns the `n`th element. @@ -189,14 +208,10 @@ class row final /// type, or which have a member alias named `BestRowKey` of that type. They /// are returned in the order they occur in this row. // clang-format off - template constexpr decltype(auto) select(best::tlist idx = {}) const& - requires selectable; - template constexpr decltype(auto) select(best::tlist idx = {}) & - requires selectable; - template constexpr decltype(auto) select(best::tlist idx = {}) const&& - requires selectable; - template constexpr decltype(auto) select(best::tlist idx = {}) && - requires selectable; + template constexpr decltype(auto) select(best::tlist idx = {}) const&; + template constexpr decltype(auto) select(best::tlist idx = {}) & ; + template constexpr decltype(auto) select(best::tlist idx = {}) const&& ; + template constexpr decltype(auto) select(best::tlist idx = {}) &&; // clang-format on /// # `row::first()`, `row::second()`, `row::last()` @@ -283,6 +298,8 @@ class row final template row(Elems&&...) -> row...>; +template +row(best::bind_t, Elems&&...) -> row; /// # `best::row_forward` /// @@ -472,9 +489,7 @@ constexpr decltype(auto) row::get(best::index_t idx) && { template template -constexpr decltype(auto) row::select(best::tlist idx) const& - requires selectable -{ +constexpr decltype(auto) row::select(best::tlist idx) const& { return row_internal::apply_lookup( [&](index_t... idx) { return best::row...>(at(idx)...); @@ -482,9 +497,7 @@ constexpr decltype(auto) row::select(best::tlist idx) const& } template template -constexpr decltype(auto) row::select(best::tlist idx) & - requires selectable -{ +constexpr decltype(auto) row::select(best::tlist idx) & { return row_internal::apply_lookup( [&](index_t... idx) { return best::row...>(at(idx)...); @@ -492,9 +505,7 @@ constexpr decltype(auto) row::select(best::tlist idx) & } template template -constexpr decltype(auto) row::select(best::tlist idx) const&& - requires selectable -{ +constexpr decltype(auto) row::select(best::tlist idx) const&& { return row_internal::apply_lookup( [&](index_t... idx) { return best::row...>(BEST_MOVE(*this).at(idx)...); @@ -502,9 +513,7 @@ constexpr decltype(auto) row::select(best::tlist idx) const&& } template template -constexpr decltype(auto) row::select(best::tlist idx) && - requires selectable -{ +constexpr decltype(auto) row::select(best::tlist idx) && { return row_internal::apply_lookup( [&](index_t... idx) { return best::row...>(BEST_MOVE(*this).at(idx)...); diff --git a/best/container/row_test.cc b/best/container/row_test.cc index 49a62d4..ab91217 100644 --- a/best/container/row_test.cc +++ b/best/container/row_test.cc @@ -60,4 +60,12 @@ best::test Select = [](auto& t) { t.expect_eq(x1.select(), best::row(Tagged1())); t.expect_eq(x1.select(), best::row(Tagged1(), Tag())); }; + +best::test refs = [](auto& t) { + int x = 0; + const int y = 2; + best::row x0(best::bind, x, y); + + static_assert(best::same>); +}; } // namespace best::row_test \ No newline at end of file diff --git a/best/meta/BUILD b/best/meta/BUILD index 84f8a65..1417a7b 100644 --- a/best/meta/BUILD +++ b/best/meta/BUILD @@ -81,6 +81,30 @@ cc_library( ] ) +cc_library( + name = "reflect", + hdrs = [ + "reflect.h", + "internal/reflect.h", + ], + deps = [ + ":taxonomy", + "//best/base:fwd", + "//best/container:row", + "//best/text:str", + ], +) + +cc_test( + name = "reflect_test", + srcs = ["reflect_test.cc"], + linkopts = ["-rdynamic"], + deps = [ + ":reflect", + "//best/test", + ] +) + cc_library( name = "tlist", hdrs = [ diff --git a/best/meta/empty.h b/best/meta/empty.h index b5907d8..0884145 100644 --- a/best/meta/empty.h +++ b/best/meta/empty.h @@ -10,6 +10,7 @@ #include "best/meta/internal/init.h" #include "best/meta/tags.h" #include "best/meta/taxonomy.h" +#include "best/meta/traits.h" //! Utilities for empty types. @@ -28,6 +29,12 @@ struct empty final { template concept is_empty = best::is_void || std::is_empty_v; +/// # `best::devoid` +/// +/// If `T` is void, produces `best::empty`. Otherwise produces `T`. +template +using devoid = best::select, empty, T>; + /// # `best::ebo` /// /// A wrapper type over a `T` that is an empty type if `T` is empty. This allows @@ -50,8 +57,8 @@ concept is_empty = best::is_void || std::is_empty_v; /// Doing a CTRP-type here ensures that this base is reasonably unique among /// other potential bases of `MyClass`. template && best::relocatable> + bool empty = best::is_empty, bool open = !std::is_final_v, + bool relo = best::relocatable> class ebo /* not final! */ { public: /// # `ebo::ebo(...)` @@ -77,8 +84,25 @@ class ebo /* not final! */ { #define BEST_EBO_VALUE_ _private }; +template +class ebo /* not final! */ : T { + public: + constexpr ebo(best::in_place_t, auto&&... args) : T(BEST_FWD(args)...) {} + + constexpr ebo() + requires best::constructible + = default; + constexpr ebo(const ebo&) = default; + constexpr ebo& operator=(const ebo&) = default; + constexpr ebo(ebo&&) = default; + constexpr ebo& operator=(ebo&&) = default; + + constexpr const T& get() const { return *this; } + constexpr T& get() { return *this; } +}; + template -class ebo /* not final! */ { +class ebo /* not final! */ { public: constexpr ebo(best::in_place_t, auto&&... args) { (void)T(BEST_FWD(args)...); diff --git a/best/meta/internal/reflect.h b/best/meta/internal/reflect.h new file mode 100644 index 0000000..529dfff --- /dev/null +++ b/best/meta/internal/reflect.h @@ -0,0 +1,126 @@ +#ifndef BEST_META_INTERNAL_REFLECT_H_ +#define BEST_META_INTERNAL_REFLECT_H_ + +#include "best/base/fwd.h" +#include "best/meta/taxonomy.h" +#include "best/text/str.h" + +namespace best::reflect_internal { +enum kind { Field, Struct, Value, Enum, NoFields }; + +struct validator { + /// This needs to be in a struct so the info classes can befriend it. + template + static constexpr bool value = + (Info::Kind == Struct && best::same) || + (Info::Kind == Enum && best::same) || + (Info::Kind == NoFields && (best::is_struct || best::is_enum)); +}; + +template +concept valid_reflection = validator::value; + +template +class field_info final { + template + friend class best::reflected_field; + template + friend class best::reflected_type; + friend mirror; + friend validator; + + using struct_ = S; + using type = T; + inline static constexpr auto Kind = Field; + + constexpr field_info(best::str name, type struct_::*ptr, + best::row tags) + : name_(name), ptr_(ptr), tags_(tags) {} + + best::str name_; + type struct_::*ptr_; + best::row tags_; +}; + +template +class struct_info final { + template + friend class best::reflected_type; + friend mirror; + friend validator; + + using struct_ = S; + using enum_ = void; + inline static constexpr auto Kind = Struct; + + constexpr struct_info(best::str name, Tags tags, best::row fields) + : name_(name), tags_(tags), items_(fields) {} + + best::str name_; + Tags tags_; + best::row items_; +}; + +template +class elem_info final { + template + friend class best::reflected_value; + template + friend class best::reflected_type; + friend mirror; + friend validator; + + using enum_ = E; + inline static constexpr auto Kind = Value; + + constexpr elem_info(best::str name, enum_ elem, best::row tags) + : name_(name), elem_(elem), tags_(tags) {} + + best::str name_; + enum_ elem_; + best::row tags_; +}; + +template +class enum_info final { + template + friend class best::reflected_type; + friend mirror; + friend validator; + + using struct_ = void; + using enum_ = E; + inline static constexpr auto Kind = Enum; + + constexpr enum_info(best::str name, Tags tags, best::row values) + : name_(name), tags_(tags), items_(values) {} + + best::str name_; + Tags tags_; + best::row items_; +}; + +template +class no_fields final { + template + friend class best::reflected_type; + friend mirror; + friend validator; + + using struct_ = void; + using enum_ = void; + inline static constexpr auto Kind = NoFields; + + constexpr no_fields(best::str name, Tags tags) : name_(name), tags_(tags) {} + + best::str name_; + Tags tags_; + best::row<> items_; +}; + +template +inline constexpr auto info = BestReflect(mirror::BEST_MIRROR_FTADLE_, (T*){}); + +}; // namespace best::reflect_internal + +#endif // BEST_META_INTERNAL_REFLECT_H_ \ No newline at end of file diff --git a/best/meta/internal/tlist.h b/best/meta/internal/tlist.h index 9551a87..0256a34 100644 --- a/best/meta/internal/tlist.h +++ b/best/meta/internal/tlist.h @@ -7,6 +7,7 @@ #include #include "best/base/fwd.h" +#include "best/base/port.h" #include "best/container/bounds.h" #if !__has_builtin(__type_pack_element) @@ -78,6 +79,27 @@ constexpr auto concat(tlist, auto... rest) { return concat(rest...); } +template +struct entry {}; + +struct fail : std::false_type { + constexpr auto operator+(auto) { return fail{}; } +}; + +template +struct set final : std::true_type, entry... { + template + constexpr auto operator+(entry) { + if constexpr (std::is_base_of_v, set>) { + return fail{}; + } else { + return set{}; + } + } +}; + +template +concept uniq = (set<>{} + ... + entry{}).value; } // namespace best::tlist_internal #endif // BEST_META_INTERNAL_TLIST_H_ \ No newline at end of file diff --git a/best/meta/reflect.h b/best/meta/reflect.h new file mode 100644 index 0000000..2b32a75 --- /dev/null +++ b/best/meta/reflect.h @@ -0,0 +1,412 @@ +#ifndef BEST_META_REFLECT_H_ +#define BEST_META_REFLECT_H_ + +#include "best/container/row.h" +#include "best/meta/internal/reflect.h" +#include "best/meta/taxonomy.h" +#include "best/text/str.h" + +//! Struct and enum reflection. +//! +//! `best` provides a mechanism for reflecting the fields and variants of +//! user-defined structs and enums, which must define the `BestReflect()` +//! FTADLE: +//! +//! ``` +//! friend constexpr auto BestReflect(auto& mirror, MyStruct*) { +//! return mirror.reflect( +//! "MyStruct", +//! mirror.field("foo", &MyStruct::foo), +//! mirror.field("bar", &MyStruct::bar), +//! ); +//! } +//! ``` + +namespace best { + +/// # `best::reflected`, `best::is_reflected_struct`, +/// `best::is_reflected_enum` +/// +/// Whether `T` can be reflected, i.e., it is a struct or enum type that has a +/// working `BestReflect()` FTADLE implementation. +template +concept reflected = requires(const best::mirror& m, best::as_auto* ptr) { + { + BestReflect(m, ptr) + } -> reflect_internal::valid_reflection>; +}; +template +concept is_reflected_struct = + best::is_struct> && best::reflected; +template +concept is_reflected_enum = + best::is_enum> && best::reflected; + +/// # `best::reflect` +/// +/// Obtains a reflection of a reflected type. This will be a value of the type +/// `best::reflected_type`, although the exact specialization is not nameable. +template +inline constexpr auto reflect = + best::reflected_type>>{}; + +/// # `best::mirror` +/// +/// A value of this type is passed to the `BestReflect` FTADLE. This type +/// cannot be constructed by users and exists only for exposition. +class mirror final { + public: + /// # `mirror()` + /// + /// Reflects that there exists a type with the given name, the given members, + /// and optionally, a row of tag types. + constexpr auto operator()(best::str name, auto... members) const; + constexpr auto operator()(best::str name, best::is_row auto tags, + auto... members) const; + + /// # `mirror.field()` + /// + /// Reflects that `Struct` has a data member with the given name, the + /// given type, and optionally, a row of tag types. The result of this call + /// should be passed to `reflect()`. + template + constexpr auto field(best::str name, Type Struct::*member, + auto... tags) const; + + /// # `mirror.value()` + /// + /// Reflects that Enum `Struct` has an enumerator with the given name, the + /// given value, and optionally, a row of tag types. The result of this call + /// should be passed to `reflect()`. + template + constexpr auto value(best::str name, Enum value, auto... tags) const; + + mirror(const mirror&) = delete; + mirror& operator=(const mirror&) = delete; + + private: + constexpr mirror() = default; + + public: + static const mirror BEST_MIRROR_FTADLE_; +}; +inline constexpr mirror mirror::BEST_MIRROR_FTADLE_{}; +#define BEST_MIRROR_FTADLE_ _private + +/// # `best::reflected_field` +/// +/// A field of some reflected struct. The type parameter is an implementation +/// detail; this type cannot be instantiated by users. +/// +/// A `best::reflected_field` offers accessors for information about the field, +/// such as its name, access to a member-to-ptr, and so on. +/// +/// A field can also be used to offset into a struct, with the syntax +/// `my_struct->*field`. `my_struct` can be a reference or pointer to +/// the reflected type, or a dereferenceable type that returns one. +template +class reflected_field final { + private: + static_assert(info_.Kind == reflect_internal::Field, + "cannot instantiate best::reflected_field directly"); + using info_t = best::as_auto; + + public: + /// # `reflected_field::reflected` + /// + /// The type this field was reflected from. + using reflected = info_t::struct_; + + /// # `reflected_field::type` + /// + /// The type of this field. + using type = info_t::type; + + /// # `reflected_field::name()` + /// + /// Returns the name of this field as specified in the `BestReflect()` call. + constexpr best::str name() const { return info_.name_; } + + /// # `reflected_field::tags()` + /// + /// Returns any tags on this field that can be selected by `Key`. + template + constexpr best::is_row auto tags(best::tlist key = {}) const { + return info_.tags_.select(key); + } + + /// # `reflected_field::as_offset()` + /// + /// Returns a pointer-to-member that represents this field. + constexpr type reflected::*as_offset() const { return info_.ptr_; } + + constexpr friend decltype(auto) operator->*(auto&& r, reflected_field f) + requires best::same, reflected> + { + return BEST_FWD(r).*(f.as_offset()); + } + constexpr friend decltype(auto) operator->*( + best::is_deref auto&& r, reflected_field f) { + return (*BEST_FWD(r)).*(f.as_offset()); + } + + private: + template + friend class reflected_type; +}; + +/// # `best::reflected_value` +/// +/// A value of some reflected enum. The type parameter is an implementation +/// detail; this type cannot be instantiated by users. +/// +/// A `best::reflected_value` offers accessors for information about the field, +/// such as its name, access to the enum value itself, and so on. +template +class reflected_value final { + private: + static_assert(info_.Kind == reflect_internal::Value, + "cannot instantiate best::reflected_value directly"); + using info_t = best::as_auto; + + public: + /// # `reflected_field::reflected` + /// + /// The type this field was reflected from. + using reflected = info_t::enum_; + + /// # `reflected_field::value` + /// + /// The actual reflected value. + static constexpr reflected value = info_.elem_; + + /// # `reflected_field::name()` + /// + /// Returns the name of this field as specified in the `BestReflect()` call. + constexpr best::str name() const { return info_.name_; } + + /// # `reflected_field::tags()` + /// + /// Returns any tags on this field that can be selected by `Key`. + template + constexpr best::is_row auto tags(best::tlist key = {}) const { + return info_.tags_.select(key); + } + + private: + template + friend class reflected_type; +}; + +/// # `best::reflected_type` +/// +/// The result of reflecting a type. The type parameter is an implementation +/// detail; this type cannot be instantiated by users. +/// +/// To obtain an instance of this type, use `best::reflect`. +template +class reflected_type final { + private: + static_assert(info_.Kind == reflect_internal::Struct || + info_.Kind == reflect_internal::Enum || + info_.Kind == reflect_internal::NoFields, + "cannot instantiate best::field directly"); + using info_t = best::as_auto; + + public: + /// # `reflected_type::reflected` + /// + /// The type this is a reflection of. + using reflected = + best::select; + + /// # `reflected_type::name()` + /// + /// Returns the name of this type as specified in the `BestReflect()` call. + constexpr best::str name() const { return info_.name_; } + + /// # `reflected_type::find(member)` + /// + /// Looks up the field or value corresponding to a particular member. This + /// the member should be a pointer-to-member if this is a reflects a struct, + /// or a value of the underlying enum if this reflects an enum. + /// + /// It will then call `cb` with the appropriate value. Internally, it has the + /// semantics of calling `best::choice::match()` on a choice that can contain + /// any of the possible reflected field/value types produced by fields() or + /// values(), respectively. If no element matches, it is as if the selected + /// element has type `void`. + /// + /// Hence, the best way to use this function is to write something like this. + /// + /// ``` + /// auto name = best::reflect.find(xyz, + /// [](auto value) { return value.name() }, + /// [] { return best::str(""); }); + /// ``` + constexpr decltype(auto) find(auto member, auto&&... cases) const; + + /// # `reflected_type::tags()` + /// + /// Returns any tags on this type that can be selected by `Key`. + template + constexpr best::is_row auto tags(best::tlist key = {}) const { + return info_.tags_.select(key); + } + + /// # `reflected_type::fields()` + /// + /// Calls `cb` with a pack of `best::reflected_field`s for this type. + constexpr decltype(auto) fields(auto&& cb) const + requires best::is_struct; + + /// # `reflected_type::each_field(...)` + /// + /// Zips together the fields of a bunch of references-to-`reflected`, and + /// calls `cb` (the last argument of `args...`) on each row of fields. + constexpr void each_field(auto&&... args) const + requires best::is_struct; + + /// # `reflected_type::values()` + /// + /// Calls `cb` with a pack of `best::reflected_values`s for this type. + constexpr decltype(auto) values(auto&& cb) const + requires best::is_enum; + + constexpr reflected_type() = default; + + private: + /// Workaround for a dumb Clang 17 conformance bug. + template + static constexpr auto item = info_.items_[best::index]; +}; + +/// # `best::fields()` +/// +/// Extracts all of the fields out of a reflected struct and creates a row +/// of references of them. +constexpr auto fields(best::is_reflected_struct auto&& value) { + return best::reflect.fields( + [&](auto... f) { return best::row(best::bind, value->*f...); }); +} +} // namespace best + +/******************************************************************************/ + +///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// + +/******************************************************************************/ + +namespace best { +constexpr auto mirror::operator()(best::str name, best::is_row auto tags, + auto... members) const { + if constexpr (sizeof...(members) == 0) { + return reflect_internal::no_fields>{name, + tags}; + } else if constexpr (((members.Kind == reflect_internal::Field) && ...)) { + static_assert( + best::same::struct_...>, + "all fields passed to a best::mirror must have the same " + "struct type"); + using Struct = + best::tlist::struct_...>:: + template type<0>; + return best::reflect_internal::struct_info< + Struct, best::as_auto, + best::as_auto...>{name, tags, {members...}}; + } else if constexpr (((members.Kind == reflect_internal::Value) && ...)) { + static_assert( + best::same::enum_...>, + "all values passed to best::mirror must have the same " + "enum type"); + using Enum = best::tlist< + typename best::as_auto::enum_...>::template type<0>; + return best::reflect_internal::enum_info< + Enum, best::as_auto, + best::as_auto...>{name, tags, {members...}}; + } else { + static_assert(sizeof...(members) == 0, + "passed invalid member values into the mirror"); + } +} +constexpr auto mirror::operator()(best::str name, auto... members) const { + return operator()(name, row(), members...); +} + +template +constexpr auto mirror::field(best::str name, Type Struct::*member, + auto... tags) const { + return best::reflect_internal::field_info...>{ + name, member, {tags...}}; +} + +template +constexpr auto mirror::value(best::str name, Enum value, auto... tags) const { + return best::reflect_internal::elem_info...>{ + name, value, {tags...}}; +} + +template +constexpr decltype(auto) reflected_type::find(auto member, + auto&&... cases) const { + if constexpr (best::is_struct) { + return fields([&](auto... fields) { + using Ch = best::choice; + Ch ch(best::index<0>); + + ((best::equal(member, fields.as_offset()) ? void(ch = Ch(fields)) + : void()), + ...); + return ch.match(BEST_FWD(cases)...); + }); + } else { + return values([&](auto... values) { + using Ch = best::choice; + Ch ch(best::index<0>); + + ((best::equal(member, values.value) ? void(ch = Ch(values)) : void()), + ...); + return ch.match(BEST_FWD(cases)...); + }); + } +} + +template +constexpr decltype(auto) reflected_type::fields(auto&& cb) const + requires best::is_struct +{ + return best::indices.apply([&] { + return best::call(BEST_FWD(cb), best::reflected_field>{}...); + }); +} + +template +constexpr void reflected_type::each_field(auto&&... args) const + requires best::is_struct +{ + if constexpr (sizeof...(args) == 1) { + fields([&](auto... fields) { (best::call(args..., fields), ...); }); + } else { + best::row row(BEST_FWD(args)...); + each_field([&](auto field) { + best::indices.apply([&] { + best::call(BEST_MOVE(row).last(), + BEST_MOVE(row)[best::index]->*field...); + }); + }); + } +} + +template +constexpr decltype(auto) reflected_type::values(auto&& cb) const + requires best::is_enum +{ + return best::indices.apply([&] { + return best::call(BEST_FWD(cb), best::reflected_value>{}...); + }); +} +} // namespace best +#endif // BEST_META_REFLECT_H_ \ No newline at end of file diff --git a/best/meta/reflect_test.cc b/best/meta/reflect_test.cc new file mode 100644 index 0000000..8033c5b --- /dev/null +++ b/best/meta/reflect_test.cc @@ -0,0 +1,69 @@ +#include "best/meta/reflect.h" + +#include "best/test/test.h" + +namespace best::reflect_test { +struct Tag {}; + +template +struct MyCallback { + using BestRowKey = Tag; + F callback; +}; + +template +MyCallback(F) -> MyCallback; + +struct MyType final { + int x, y, z; + best::str s1, s2; + + constexpr friend auto BestReflect(auto& m, MyType*) { + return m("MyType", // + m.field("x", &MyType::x), // + m.field("y", &MyType::y), // + m.field("z", &MyType::z, MyCallback([] { return 42; })), // + m.field("s1", &MyType::s1), // + m.field("s2", &MyType::s2)); + } +}; + +static_assert(best::is_reflected_struct); + +enum class MyEnum { A, B, C }; +constexpr auto BestReflect(auto& m, MyEnum*) { + return m("MyEnum", // + m.value("A", MyEnum::A), // + m.value("B", MyEnum::B), // + m.value("C", MyEnum::C)); +} + +static_assert(best::is_reflected_enum); + +best::test ToString = [](auto& t) { + t.expect_eq(best::format("{:?}", MyType{1, 2, 3, "foo", "bar"}), + R"(MyType {x: 1, y: 2, z: 3, s1: "foo", s2: "bar"})"); + + t.expect_eq(best::format("{:?}, {:?}", MyEnum::B, MyEnum(42)), + "MyEnum::B, MyEnum(42)"); +}; + +best::test Fields = [](auto& t) { + MyType x0{1, 2, 3, "foo", "bar"}; + t.expect_eq(best::fields(x0), best::row(1, 2, 3, "foo", "bar")); +}; + +best::test FindTag = [](auto& t) { + int found = best::reflect.find( + &MyType::z, + [](auto field) { + auto tags = field.tags(best::types); + if constexpr (!tags.is_empty()) { + return tags[best::index<0>].callback(); + } + return 0; + }, + [] { return 0; }); + t.expect_eq(found, 42); +}; +} // namespace best::reflect_test \ No newline at end of file diff --git a/best/meta/tags.h b/best/meta/tags.h index c3ece39..f635176 100644 --- a/best/meta/tags.h +++ b/best/meta/tags.h @@ -23,6 +23,15 @@ struct rank<0> {}; inline constexpr struct in_place_t { } in_place; +/// # `best::bind` +/// +/// A tag for overriding the standard CTAD behavior of types like `best::row` +/// and `best::option`. By default, these types strip references off of their +/// arguments during CTAD, but if you pass `best::refs` as the first +/// constructor, it will cause them to keep the references. +inline constexpr struct bind_t { +} bind; + /// An alias for std::in_place_index. /// /// Use this to tag things that want to take a size_t. diff --git a/best/meta/taxonomy.h b/best/meta/taxonomy.h index 708fcd3..517ec6c 100644 --- a/best/meta/taxonomy.h +++ b/best/meta/taxonomy.h @@ -1,6 +1,7 @@ #ifndef BEST_META_TAXONOMY_H_ #define BEST_META_TAXONOMY_H_ +#include #include #include "best/meta/internal/abominable.h" @@ -199,10 +200,43 @@ using as_ptr = std::add_pointer_t>; template using unptr = std::remove_pointer_t; +/// # `best::is_deref` +/// +/// Whether a type `T` is "properly dereferenceable", that is, it has an +/// `operator*` that returns some type, and an `operator->` that returns a +/// pointer. If `Target` is `void`, the types of these operators is not checked. +/// Otherwise, both operators must yield reference/pointer to `Target`, +/// respectively. +template +concept is_deref = + (is_ptr && (is_void || + std::is_same_v>>)) || + (is_void && requires(T& r) { + operator*(r); + r.operator->(); + }) || requires(const T& cr) { + { operator*(cr) } -> std::convertible_to; + { cr.operator->() } -> std::convertible_to; + }; + /// # `best::addr()` /// /// Obtains the address of a reference without hitting `operator&`. constexpr auto addr(auto&& ref) { return __builtin_addressof(ref); } + +/// # `best::is_struct` +/// +/// Identifies a "struct type". In our highly narrow definition, this is a +/// final class type that is an aggregate. +template +concept is_struct = + std::is_class_v && std::is_aggregate_v && std::is_final_v; + +/// # `best::is_enum` +/// +/// Identifies an enumeration type. +template +concept is_enum = std::is_enum_v; } // namespace best #endif // BEST_META_TAXONOMY_H_ \ No newline at end of file diff --git a/best/meta/tlist.h b/best/meta/tlist.h index 17365bf..0fd5d7c 100644 --- a/best/meta/tlist.h +++ b/best/meta/tlist.h @@ -110,6 +110,14 @@ class tlist final { return tlist_internal::concat(those...); } + /// # `best::unique_pack` + /// + /// Whether this pack of types is a + template + static constexpr bool is_unique() { + return tlist_internal::uniq; + }; + private: template static constexpr bool type_callable = (... && callable); diff --git a/best/text/BUILD b/best/text/BUILD index ebcd212..ddefa4b 100644 --- a/best/text/BUILD +++ b/best/text/BUILD @@ -79,6 +79,7 @@ cc_library( "//best/container:span", "//best/math:conv", "//best/meta:guard", + "//best/meta:reflect", ] ) diff --git a/best/text/internal/format_impls.h b/best/text/internal/format_impls.h index 4074e1b..c46c3de 100644 --- a/best/text/internal/format_impls.h +++ b/best/text/internal/format_impls.h @@ -2,7 +2,9 @@ #define BEST_TEXT_INTERNAL_FORMAT_IMPLS_H_ #include +#include +#include "best/meta/reflect.h" #include "best/text/rune.h" #include "best/text/str.h" @@ -65,11 +67,6 @@ constexpr void BestFmtQuery(auto& query, T** range) { query.uses_method = [](rune r) { return r == 'p'; }; } -template -void BestFmt(auto& fmt, const best::row value) { - fmt.format(best::row(value.first, value.second())); -} - void BestFmt(auto& fmt, integer auto value) { // Taken liberally from Rust's implementation of Formatter::pad_integral(). @@ -168,6 +165,23 @@ constexpr void BestFmtQuery(auto& query, R* range) query.requires_debug = false; } +void BestFmt(auto& fmt, const best::is_reflected_struct auto& value) { + auto refl = best::reflect; + auto rec = fmt.record(refl.name()); + refl.fields( + [&](auto... field) { (rec.field(field.name(), value->*field), ...); }); +} + +void BestFmt(auto& fmt, const best::is_reflected_enum auto& value) { + auto refl = best::reflect; + refl.find( + value, [&](auto& val) { fmt.format("{}::{}", refl.name(), val.name()); }, + [&] { + using U = std::underlying_type_t>; + fmt.format("{}({})", refl.name(), U(value)); + }); +} + namespace format_internal { using mark_as_used = void; } // namespace format_internal From 16f5fd9838b8a731b8618afcb94413e6677c067f Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Fri, 21 Jun 2024 16:17:12 -0400 Subject: [PATCH 3/6] bytes, spans, oh my --- best/base/port.h | 9 + best/container/choice.h | 10 +- best/container/object.h | 2 +- best/container/option.h | 17 +- best/container/option_test.cc | 4 +- best/container/span.h | 689 ++++++++++++++++++++++------------ best/memory/BUILD | 11 + best/memory/bytes.cc | 2 +- best/memory/bytes.h | 89 ++--- best/memory/bytes_test.cc | 171 +++++++++ best/memory/internal/bytes.h | 142 +++++++ best/memory/internal/layout.h | 1 + best/meta/internal/reflect.h | 81 ++++ 13 files changed, 932 insertions(+), 296 deletions(-) create mode 100644 best/memory/bytes_test.cc diff --git a/best/base/port.h b/best/base/port.h index 30c9465..ab6940e 100644 --- a/best/base/port.h +++ b/best/base/port.h @@ -33,6 +33,15 @@ inline constexpr bool is_debug() { #define BEST_HAS_ATTRIBUTE(x_) __has_attribute(x_) #endif +/// # BEST_HAS_FEATURE()` +/// +/// Tests whether a particular GCC-like feature test is available. +#ifndef __has_feature +#define BEST_HAS_FEATURE(x_) 0 +#else +#define BEST_HAS_FEATURE(x_) __has_feature(x_) +#endif + /// # `BEST_STRINGIFY()` /// /// Stringifies a token string. diff --git a/best/container/choice.h b/best/container/choice.h index 242eeca..3760800 100644 --- a/best/container/choice.h +++ b/best/container/choice.h @@ -425,8 +425,9 @@ constexpr best::option::template crref> choice::at(best::index_t i) const&& { if (which() != n) return {}; return best::call_devoid([&]() -> decltype(auto) { - return static_cast>( - impl().deref(unsafe{"checked which() before this"}, i)); + return best::option(best::bind, + static_cast>(impl().deref( + unsafe{"checked which() before this"}, i))); }); } template @@ -435,8 +436,9 @@ constexpr best::option::template rref> choice::at(best::index_t i) && { if (which() != n) return {}; return best::call_devoid([&]() -> decltype(auto) { - return static_cast>( - impl().deref(unsafe{"checked which() before this"}, i)); + return best::option(best::bind, + static_cast>(impl().deref( + unsafe{"checked which() before this"}, i))); }); } diff --git a/best/container/object.h b/best/container/object.h index 5e01068..d9e22b9 100644 --- a/best/container/object.h +++ b/best/container/object.h @@ -371,7 +371,7 @@ class object_ptr final { public: // Public for structural-ness. - pointee* BEST_OBJECT_PTR_; + pointee* BEST_OBJECT_PTR_ = nullptr; #define BEST_OBJECT_PTR_ _private }; diff --git a/best/container/option.h b/best/container/option.h index 02127f9..3c6c111 100644 --- a/best/container/option.h +++ b/best/container/option.h @@ -8,6 +8,7 @@ #include "best/log/internal/crash.h" #include "best/log/location.h" #include "best/meta/init.h" +#include "best/meta/internal/init.h" #include "best/meta/tags.h" //! An optional type, like `std::optional`. @@ -143,20 +144,30 @@ class option final { /// /// This converts a value into a non-empty option containing it. template - constexpr option(U&& arg) - requires not_forbidden_conversion && best::constructible + constexpr explicit(!best::convertible) option(U&& arg) + requires not_forbidden_conversion && best::constructible && + (!best::copyable || !best::is_object) : option(best::in_place, BEST_FWD(arg)) {} constexpr option(best::devoid arg) requires best::moveable : option(best::in_place, BEST_MOVE(arg)) {} template - constexpr option(best::bind_t, U&& arg) + constexpr explicit(!best::convertible) option(best::bind_t, U&& arg) requires not_forbidden_conversion && best::constructible : option(best::in_place, BEST_FWD(arg)) {} constexpr option(best::bind_t, best::devoid arg) requires best::moveable : option(best::in_place, BEST_MOVE(arg)) {} + // This constructor exists because otherwise, converting e.g. int to long + // makes the compiler think there's a reference to temporary being bound that + // doesn't need to be. + template + constexpr explicit(!best::convertible) option(U arg) + requires not_forbidden_conversion && best::constructible && + best::copyable + : option(best::in_place, BEST_FWD(arg)) {} + /// # `option::option(option&)` /// /// Constructs a `best::option` by converting from a reference to diff --git a/best/container/option_test.cc b/best/container/option_test.cc index 9e4313b..ad09023 100644 --- a/best/container/option_test.cc +++ b/best/container/option_test.cc @@ -222,7 +222,7 @@ best::test Refs = [](auto& t) { best::option x3 = x1; t.expect_eq(x3, 42); - best::option> x4 = new int(42); + best::option> x4(new int(42)); best::option&&> x5 = std::move(x4).as_ref(); best::option> x6 = std::move(x5); t.expect_eq(**x6, 42); @@ -272,7 +272,7 @@ best::test Map = [](auto& t) { t.expect_eq(x3.map([] { return 42; }), 42); best::option> x4; - best::option> x5 = new int(42); + best::option> x5(new int(42)); t.expect_eq(std::move(x4).map([](auto&& x) { return std::move(x); }), best::none); diff --git a/best/container/span.h b/best/container/span.h index 55b7356..23c7598 100644 --- a/best/container/span.h +++ b/best/container/span.h @@ -3,6 +3,7 @@ #include #include +#include #include "best/base/port.h" #include "best/container/internal/span.h" @@ -71,6 +72,15 @@ concept is_span = template using span_type = typename best::as_auto::type; +/// # `best::from_nul` +/// +/// Wraps a pointer to a NUL (i.e., `T{0}`) terminated string in a `best::span`. +/// This function is essentially a safer `strlen()`. +template +constexpr best::span from_nul(T* ptr) { + return best::span::from_nul(ptr); +} + /// # `best::span_extent` /// /// Given T = best::span, returns n. @@ -164,7 +174,7 @@ class span final { /// /// This span's data pointer is always null. constexpr span() - requires(is_dynamic || extent == 0) + requires is_dynamic || (extent == 0) = default; /// # `span::span(span)` @@ -181,16 +191,8 @@ class span final { /// /// If this span has a statically-known size, this constructor will crash if /// len is not that size. - constexpr explicit(is_static) span(best::object_ptr data, size_t size, // - best::location loc = best::here) { - repr_.data = data; - if constexpr (is_static) { - best::bounds bounds_check = {.start = *extent, .count = 0}; - bounds_check.compute_count(this->size(), loc); - } else { - repr_.size = size; - } - } + constexpr explicit(is_static) span(best::object_ptr data, size_t size, + best::location loc = best::here); /// # `span::span(contiguous)` /// @@ -240,15 +242,7 @@ class span final { /// /// If this is a fixed-with span, this will perform the usual fatal bounds /// check upon construction. - constexpr static span from_nul(T* data) { - if (data == nullptr) return {data, 0}; - - auto ptr = data; - while (*ptr++ != T{0}) - ; - - return best::span(data, ptr - data - 1); - } + constexpr static span from_nul(T* data); private: template @@ -275,97 +269,57 @@ class span final { /// /// Returns the first, or first `m`, elements of this span, or `best::none` if /// there are not enough elements. - constexpr best::option first() const { return at(0); } + constexpr best::option first() const; template - constexpr best::option> first(best::index_t = {}) const { - return at({.end = m}).map(best::ctor>); - } + constexpr best::option> first(best::index_t = {}) const; /// # `span::last()` /// /// Returns the last, or last `m`, elements of this span, or `best::none` if /// there are not enough elements. - constexpr best::option last() const { return at(size() - 1); } + constexpr best::option last() const; template - constexpr best::option> last(best::index_t = {}) const { - return at({.start = size() - m}).map(best::ctor>); - } + constexpr best::option> last(best::index_t = {}) const; /// # `span::split_first()` /// /// Returns the first, or first `m`, elements of this span, and the remaining /// elements, or `best::none` if there are not enough elements. - constexpr best::option>>> split_first() const { - return split_first(best::index<1>).map([](auto pair) { - return best::row>>{pair.first()[0], pair.second()}; - }); - } + constexpr auto split_first() const + -> best::option>>>; template - constexpr best::option, span>>> split_first( - best::index_t = {}) const { - return at({.end = m}).map(best::ctor>).map([&](auto ch) { - return best::row{ch, span>(operator[]({.start = m}))}; - }); - } + constexpr auto split_first(best::index_t = {}) const + -> best::option, span>>>; /// # `span::split_last()` /// /// Returns the last, or last `m`, elements of this span, and the remaining /// elements, or `best::none` if there are not enough elements. - constexpr best::option>>> split_last() const { - return split_last(best::index<1>).map([](auto pair) { - return best::row>>{pair.first()[0], pair.second()}; - }); - } + constexpr auto split_last() const + -> best::option>>>; template - constexpr best::option, span>>> split_last( - best::index_t = {}) const { - return at({.start = size() - m}) - .map(best::ctor>) - .map([&](auto ch) { - return best::row{ - ch, - span>(operator[]({.end = size() - m})), - }; - }); - } + constexpr auto split_last(best::index_t = {}) const + -> best::option, span>>>; /// # `span::take_first()` /// /// Splits this span at `m`; returns the prefix, and updates this to be the /// rest. If `m > size()`, returns best::none and leaves this span untouched constexpr best::option> take_first(size_t m) - requires is_dynamic - { - if (m > size()) return best::none; - auto [prefix, rest] = *split_at(m); - *this = rest; - return prefix; - } + requires is_dynamic; /// # `span::take_last()` /// /// Splits this span at `m`; returns the suffix, and updates this to be the /// rest. If `m > size()`, returns best::none. constexpr best::option> take_last(size_t m) - requires is_dynamic - { - if (m > size()) return best::none; - auto [rest, suffix] = *split_at(size() - m); - *this = rest; - return suffix; - - return best::none; - } + requires is_dynamic; /// # `span[idx]` /// /// Extracts a single element. Crashes if the requested index is /// out-of-bounds. - constexpr T& operator[](best::track_location idx) const { - best::bounds{.start = idx, .count = 1}.compute_count(size(), idx); - return data()[idx]; - } + constexpr T& operator[](best::track_location idx) const; /// # `span[best::index]` /// @@ -374,18 +328,12 @@ class span final { /// compile time error; otherwise crashes. template constexpr T& operator[](best::index_t) const - requires((is_dynamic || idx < *extent)) - { - return (*this)[idx]; - } + requires is_dynamic || ((idx < *extent)); /// # `span[{.start = ...}]` /// /// Extracts a subspan. Crashes if the requested range is out-of-bounds. - constexpr best::span operator[](best::bounds::with_location range) const { - auto count = range.compute_count(size()); - return as_dynamic{data() + range.start, count}; - } + constexpr best::span operator[](best::bounds::with_location range) const; /// # `span[best::vals]` /// @@ -393,33 +341,19 @@ class span final { /// and `this->is_static`, produces a compile time error; otherwise crashes. template constexpr auto operator[](best::vlist) const - requires(range.try_compute_count(extent).has_value()) - { - constexpr auto count = range.try_compute_count(extent); - return with_extent(data() + range.start, *count); - } + requires(range.try_compute_count(extent).has_value()); /// # `span::at(idx)` /// /// Extracts a single element. If the requested index is out-of-bounds, /// returns best::none. - constexpr best::option at(size_t idx) const { - if (idx < size()) { - return data()[idx]; - } - return best::none; - } + constexpr best::option at(size_t idx) const; /// # `span::at({.start = ...})` /// /// Extracts a subspan. If the requested range is out-of-bounds, returns /// best::none. - constexpr best::option> at(best::bounds range) const { - if (auto count = range.try_compute_count(size())) { - return span{data() + range.start, *count}; - } - return best::none; - } + constexpr best::option> at(best::bounds range) const; /// # `span::at(unsafe, idx)` /// @@ -431,126 +365,71 @@ class span final { /// /// Extracts a subspan. If the requested range is out-of-bounds, /// Undefined Behavior. - constexpr best::span at(unsafe, best::bounds range) const { - size_t count = size() - range.start; - if (range.end) { - count = *range.end - range.start; - } else if (range.including_end) { - count = *range.end - range.start + 1; - } else if (range.count) { - count = *range.count; - } - - return best::span(data() + range.start, count); - } + constexpr best::span at(unsafe, best::bounds range) const; /// # `span.swap()` /// /// Swaps the elements at indices `a` and `b`. constexpr void swap(size_t a, size_t b, best::location loc = best::here) const - requires(!is_const) - { - using ::std::swap; - swap(operator[]({a, loc}), operator[]({b, loc})); - } + requires(!is_const); /// # `span::reverse()` /// /// Reverses the order of the elements in this span, in-place. constexpr void reverse() const - requires(!is_const) - { - for (size_t i = 0; i < size() / 2; ++i) { - swap(i, size() - i - 1); - } - } + requires(!is_const); /// # `span::split_at()` /// /// Splits this span at `idx` and returns both halves. - constexpr best::option, 2>> split_at( - size_t idx) const { - if (auto prefix = at({.end = idx})) { - auto rest = *this; - rest.repr_.data += idx; - rest.repr_.size -= idx; - return {{*prefix, rest}}; - } - - return best::none; - } + constexpr best::option, 2>> split_at(size_t) const; /// # `span::contains()` /// - /// Performs a linear search for a matching element. - constexpr bool contains(const best::equatable auto& needle) const { - for (const auto& candidate : *this) { - if (candidate == needle) return true; - } - return false; - } + /// Performs a linear search for a matching element or subspan and returns + /// whether it was found. + template U = const T> + constexpr bool contains(best::span needle) const; + constexpr bool contains(const best::equatable auto& needle) const; - /// # `span::starts_with()` + /// # `span::find()` /// - /// Checks if this span starts with a particular pattern. - template U = T> - constexpr bool starts_with(best::span needle) const { - return at({.end = needle.size()}) == needle; - } - - /// # `span::ends_with()` + /// Performs a linear search for a matching element or subspan and returns its + /// index. + template U> + constexpr best::option find(best::span needle) const; + constexpr best::option find( + const best::equatable auto& needle) const; + + /// # `span::starts_with()`, `span::ends_with()` /// - /// Checks if this span ends with a particular pattern. - template U = T> - constexpr bool ends_with(best::span needle) const { - return at({.start = size() - needle.size()}) == needle; - } + /// Checks if this span starts or ends with a particular pattern. + template U = const T> + constexpr bool starts_with(best::span needle) const; + template U = const T> + constexpr bool ends_with(best::span needle) const; - /// # `span::strip_prefix()` + /// # `span::strip_prefix()`, `span::strip_suffix()` /// - /// If this span starts with `prefix`, removes it and returns the rest; - /// otherwise returns `best::none`. - template U = T> + /// If this span starts with `prefix` (or ends with `suffix`), removes it and + /// returns the rest; otherwise returns `best::none`. + template U = const T> constexpr best::option> strip_prefix( - best::span prefix) const { - if (!starts_with(prefix)) return best::none; - return at({.start = prefix.size()}); - } - - /// # `span::strip_suffix()` - /// - /// If this span ends with `suffix`, removes it and returns the rest; - /// otherwise returns `best::none`. - template U = T> + best::span prefix) const; + template U = const T> constexpr best::option> strip_suffix( - best::span suffix) const { - if (!ends_with(suffix)) return best::none; - return at({.end = size() - suffix.size()}); - } - - /// # `span::consume_prefix()` - /// - /// Like `strip_prefix()`, but returns a bool and updates the span in-place. - template U = T> - constexpr bool consume_prefix(best::span prefix) - requires is_dynamic - { - if (!starts_with(prefix)) return false; - *this = *at({.start = prefix.size()}); - return true; - } + best::span suffix) const; - /// # `span::consume_suffix()` + /// # `span::consume_prefix()`, `span::consume_suffix()` /// - /// Like `strip_suffix()`, but returns a bool and updates the span in-place. - template U = T> - constexpr bool consume_suffix(best::span suffix) - requires is_dynamic - { - if (!ends_with(suffix)) return false; - *this = *at({.end = size() - suffix.size()}); - return true; - } + /// Like `strip_prefix()`/`strip_suffix()`, but returns a bool and updates the + /// span in-place. + template U = const T> + constexpr bool consume_prefix(best::span prefix) + requires is_dynamic; + template U = const T> + constexpr bool consume_suffix(best::span suffix) + requires is_dynamic; /// # `span::sort()` /// @@ -584,46 +463,20 @@ class span final { best::callable auto&&) const requires(!is_const); - /// # `span::copy_from()` + /// # `span::copy_from()`, `span::emplace_from()` /// /// Copies values from src. This has the same semantics as Go's `copy()` /// builtin: if the lengths are not equal, only the overlapping part is /// copied. + /// + /// `emplace_from()` additionally assumes the destination is uninitialized, so + /// it will call a constructor rather than assignment. template constexpr void copy_from(best::span src) const - requires(!is_const) - { - if (!std::is_constant_evaluated() && best::same && - best::copyable) { - best::copy_bytes(*this, src); - return; - } - - unsafe u("already performed a bounds check in the loop latch"); - size_t to_copy = best::min(size(), src.size()); - for (size_t i = 0; i < to_copy; ++i) { - at(u, i) = src.at(u, i); - } - } - - /// # `span::emplace_from()` - /// - /// Like `copy_from()`, but assumes this span's elements are uninitialized. + requires(!is_const); template constexpr void emplace_from(best::span src) const - requires(!is_const) - { - if (!std::is_constant_evaluated() && best::same && - best::copyable) { - best::copy_bytes(*this, src); - return; - } - - size_t to_copy = best::min(size(), src.size()); - for (size_t i = 0; i < to_copy; ++i) { - (data() + i).copy_from(src.data() + i, false); - } - } + requires(!is_const); /// # `span::iter`, `span::begin()`, `span::end()`. /// @@ -639,14 +492,8 @@ class span final { /// /// Destroys the elements of this span, in-place. This does not affect /// whatever the underlying storage for the span is. - constexpr void destroy_in_place() - requires(!is_const) - { - if constexpr (!best::is_debug() && best::destructible) return; - for (size_t i = 0; i < size(); ++i) { - (data() + i).destroy_in_place(); - } - } + constexpr void destroy_in_place() const + requires(!is_const); /// # `vec::shift_within()` /// @@ -701,7 +548,10 @@ span(best::object_ptr, size_t) -> span; template span(std::initializer_list) -> span; template -span(R&& r) -> span, best::static_size>; +span(R&& r) -> span, + // TODO(mcyoung): This part of the deduction guide has + // proven to be a pain in the ass. It should be opt-in. + best::static_size>; } // namespace best /******************************************************************************/ @@ -762,18 +612,370 @@ inline constexpr size_t BestStaticSize(auto, std::array*) { return n; } +template n> +constexpr span::span(best::object_ptr data, size_t size, + best::location loc) { + repr_.data = data; + if constexpr (is_static) { + best::bounds bounds_check = {.start = *extent, .count = 0}; + bounds_check.compute_count(this->size(), loc); + } else { + repr_.size = size; + } +} + +template n> +constexpr best::option span::first() const { + return at(0); +} +template n> +template +constexpr best::option> span::first( + best::index_t) const { + return at({.end = m}).map(best::ctor>); +} +template n> +constexpr best::option span::last() const { + return at(size() - 1); +} +template n> +template +constexpr best::option> span::last( + best::index_t) const { + return at({.start = size() - m}).map(best::ctor>); +} + +template n> +constexpr auto span::split_first() const + -> best::option>>> { + return split_first(best::index<1>).map([](auto pair) { + return best::row>>{pair.first()[0], pair.second()}; + }); +} +template n> +template +constexpr auto span::split_first(best::index_t) const + -> best::option, span>>> { + return at({.end = m}).map(best::ctor>).map([&](auto ch) { + return best::row{ch, span>(operator[]({.start = m}))}; + }); +} + +template n> +constexpr auto span::split_last() const + -> best::option>>> { + return split_last(best::index<1>).map([](auto pair) { + return best::row>>{pair.first()[0], pair.second()}; + }); +} +template n> +template +constexpr auto span::split_last(best::index_t) const + -> best::option, span>>> { + return at({.start = size() - m}) + .map(best::ctor>) + .map([&](auto ch) { + return best::row{ch, + span>(operator[]({.end = size() - m}))}; + }); +} + +template n> +constexpr span span::from_nul(T* data) { + if (data == nullptr) return {data, 0}; + + if constexpr (best::constexpr_byte_comparable) { + if (BEST_CONSTEXPR_MEMCMP || !std::is_constant_evaluated()) { + size_t len = +#if BEST_CONSTEXPR_MEMCMP + __builtin_strlen(data); +#else + bytes_internal::strlen(data); +#endif + return {data, len}; + } + } + + auto ptr = data; + while (*ptr++ != T{0}) + ; + return best::span(data, ptr - data - 1); +} + +template n> +constexpr best::option> span::take_first(size_t m) + requires is_dynamic +{ + if (m > size()) return best::none; + auto [prefix, rest] = *split_at(m); + *this = rest; + return prefix; +} + +template n> +constexpr best::option> span::take_last(size_t m) + requires is_dynamic +{ + if (m > size()) return best::none; + auto [rest, suffix] = *split_at(size() - m); + *this = rest; + return suffix; + + return best::none; +} + +template n> +constexpr T& span::operator[](best::track_location idx) const { + best::bounds{.start = idx, .count = 1}.compute_count(size(), idx); + return data()[idx]; +} + +template n> +template +constexpr T& span::operator[](best::index_t) const + requires is_dynamic || ((idx < *extent)) +{ + return (*this)[idx]; +} + +template n> +constexpr best::span span::operator[]( + best::bounds::with_location range) const { + auto count = range.compute_count(size()); + return as_dynamic{data() + range.start, count}; +} + +template n> +template +constexpr auto span::operator[](best::vlist) const + requires(range.try_compute_count(extent).has_value()) +{ + constexpr auto count = range.try_compute_count(extent); + return with_extent(data() + range.start, *count); +} + +template n> +constexpr best::option span::at(size_t idx) const { + if (idx < size()) { + return data()[idx]; + } + return best::none; +} + +template n> +constexpr best::option> span::at(best::bounds range) const { + if (auto count = range.try_compute_count(size())) { + return span{data() + range.start, *count}; + } + return best::none; +} + +template n> +constexpr best::span span::at(unsafe, best::bounds range) const { + size_t count = size() - range.start; + if (range.end) { + count = *range.end - range.start; + } else if (range.including_end) { + count = *range.end - range.start + 1; + } else if (range.count) { + count = *range.count; + } + + return best::span(data() + range.start, count); +} + +template n> +constexpr void span::swap(size_t a, size_t b, best::location loc) const + requires(!is_const) +{ + using ::std::swap; + swap(operator[]({a, loc}), operator[]({b, loc})); +} + +template n> +constexpr void span::reverse() const + requires(!is_const) +{ + for (size_t i = 0; i < size() / 2; ++i) { + swap(i, size() - i - 1); + } +} + +template n> +constexpr best::option, 2>> span::split_at( + size_t idx) const { + if (auto prefix = at({.end = idx})) { + auto rest = *this; + rest.repr_.data += idx; + rest.repr_.size -= idx; + return {{*prefix, rest}}; + } + + return best::none; +} + +template n> +constexpr bool span::contains( + const best::equatable auto& needle) const { + return find(needle).has_value(); +} +template n> +template U> +constexpr bool span::contains(best::span needle) const { + return find(needle).has_value(); +} + +template n> +constexpr best::option span::find( + const best::equatable auto& needle) const { + size_t idx = 0; + for (auto& candidate : *this) { + if (candidate == needle) return idx; + ++idx; + } + return best::none; +} +template n> +template U> +constexpr best::option span::find(best::span needle) const { + if (needle.is_empty()) return 0; + + if constexpr (best::constexpr_byte_comparable) { + return best::search_bytes(*this, needle); + } else if (!std::is_constant_evaluated()) { + if constexpr (best::byte_comparable) { + return best::search_bytes(*this, needle); + } + } + + auto haystack = *this; + auto& first = needle[0]; + while (haystack.size() >= needle.size()) { + // Skip to the next possible start. + auto next = haystack.find(first); + if (!next) return best::none; + + // Skip forward. + haystack = at(unsafe("we just did a bounds check; avoid going through " + "best::option here for constexpr speed"), + {.start = *next}); + + // Now check if we found the full needle. + if (haystack.starts_with(needle)) return size() - haystack.size(); + } + return false; +} + +template n> +template U> +constexpr bool span::starts_with(best::span needle) const { + return at({.end = needle.size()}) == needle; +} +template n> +template U> +constexpr bool span::ends_with(best::span needle) const { + return at({.start = size() - needle.size()}) == needle; +} + +template n> +template U> +constexpr best::option> span::strip_prefix( + best::span prefix) const { + if (!starts_with(prefix)) return best::none; + return at({.start = prefix.size()}); +} + +template n> +template U> +constexpr best::option> span::strip_suffix( + best::span suffix) const { + if (!ends_with(suffix)) return best::none; + return at({.end = size() - suffix.size()}); +} + +template n> +template U> +constexpr bool span::consume_prefix(best::span prefix) + requires is_dynamic +{ + if (!starts_with(prefix)) return false; + *this = *at({.start = prefix.size()}); + return true; +} + +/// # `span::consume_suffix()` +/// +/// Like `strip_suffix()`, but returns a bool and updates the span in-place. +template n> +template U> +constexpr bool span::consume_suffix(best::span suffix) + requires is_dynamic +{ + if (!ends_with(suffix)) return false; + *this = *at({.end = size() - suffix.size()}); + return true; +} + +template n> +template +constexpr void span::copy_from(best::span src) const + requires(!is_const) +{ + if (!std::is_constant_evaluated() && best::same && + best::copyable) { + best::copy_bytes(*this, src); + return; + } + + unsafe u("already performed a bounds check in the loop latch"); + size_t to_copy = best::min(size(), src.size()); + for (size_t i = 0; i < to_copy; ++i) { + at(u, i) = src.at(u, i); + } +} +template n> +template +constexpr void span::emplace_from(best::span src) const + requires(!is_const) +{ + if (!std::is_constant_evaluated() && best::same && + best::copyable) { + best::copy_bytes(*this, src); + return; + } + + size_t to_copy = best::min(size(), src.size()); + for (size_t i = 0; i < to_copy; ++i) { + (data() + i).copy_from(src.data() + i, false); + } +} + +template n> +constexpr void span::destroy_in_place() const + requires(!is_const) +{ + if constexpr (!best::is_debug() && best::destructible) return; + for (size_t i = 0; i < size(); ++i) { + (data() + i).destroy_in_place(); + } +} + template n> template m> constexpr bool span::operator==(span that) const requires best::equatable { - if (size() != that.size()) return false; - if constexpr (best::byte_comparable) { - if (!std::is_constant_evaluated()) { + if (std::is_constant_evaluated()) { + if constexpr (best::constexpr_byte_comparable) { + return best::equate_bytes(span(*this), span(that)); + } + } else { + if constexpr (best::byte_comparable) { return best::equate_bytes(span(*this), span(that)); } } + if (size() != that.size()) return false; for (size_t i = 0; i < size(); ++i) { if (data()[i] != that.data()[i]) { return false; @@ -788,10 +990,13 @@ template m> constexpr auto span::operator<=>(span that) const requires best::comparable { - if constexpr (best::byte_comparable) { - if (!std::is_constant_evaluated()) { - return best::order_type( - best::compare_bytes(span(*this), span(that))); + if (std::is_constant_evaluated()) { + if constexpr (best::constexpr_byte_comparable) { + return best::compare_bytes(span(*this), span(that)); + } + } else { + if constexpr (best::byte_comparable) { + return best::compare_bytes(span(*this), span(that)); } } diff --git a/best/memory/BUILD b/best/memory/BUILD index a17815f..a0a91af 100644 --- a/best/memory/BUILD +++ b/best/memory/BUILD @@ -45,4 +45,15 @@ cc_library( deps = [ "//best/container:option", ] +) + +cc_test( + name = "bytes_test", + srcs = ["bytes_test.cc"], + linkopts = ["-rdynamic"], + deps = [ + ":bytes", + "//best/container:span", + "//best/test", + ] ) \ No newline at end of file diff --git a/best/memory/bytes.cc b/best/memory/bytes.cc index 4ca4bf8..f9f3261 100644 --- a/best/memory/bytes.cc +++ b/best/memory/bytes.cc @@ -8,7 +8,7 @@ void* memmem(const void* a, size_t an, const void* b, size_t bn) noexcept { const char* ap = reinterpret_cast(a); const char* bp = reinterpret_cast(b); - while (an >= bn && an > 0) { + while (an >= bn) { if (bytes_internal::memcmp(ap, bp, bn) == 0) { return const_cast(ap); } diff --git a/best/memory/bytes.h b/best/memory/bytes.h index 1de56a3..c442831 100644 --- a/best/memory/bytes.h +++ b/best/memory/bytes.h @@ -1,6 +1,8 @@ #ifndef BEST_MEMORY_BYTES_H_ #define BEST_MEMORY_BYTES_H_ +#include + #include "best/base/hint.h" #include "best/container/option.h" #include "best/memory/internal/bytes.h" @@ -11,14 +13,29 @@ //! functions. namespace best { +/// # `BEST_CONSTEXPR_MEMCMP` +/// +/// Whether constexpr `memcmp()` is known to be available. +#define BEST_CONSTEXPR_MEMCMP BEST_CONSTEXPR_MEMCMP_ + /// # `best::byte_comparable` /// /// Whether a pair of types' equality is modeled by `memcmp()`. template -concept byte_comparable = - (std::is_integral_v || std::is_enum_v || - std::is_pointer_v)&&(std::is_integral_v || std::is_enum_v || - std::is_pointer_v)&&sizeof(T) == sizeof(U); +concept byte_comparable = requires { + best::bytes_internal::can_memcmp(best::bytes_internal::tag{}, + best::bytes_internal::tag{}); +}; + +/// # `best::constexpr_byte_comparable` +/// +/// Whether a pair of types can be `memcmp`'d in constexpr. +template +concept constexpr_byte_comparable = requires { + requires byte_comparable; + requires bytes_internal::is_char && bytes_internal::is_char; + requires bool(BEST_CONSTEXPR_MEMCMP); +}; /// # `best::copy_bytes()` /// @@ -60,35 +77,28 @@ BEST_INLINE_ALWAYS void fill_bytes(best::span dst, uint8_t fill) { /// /// A typed wrapper over `memcmp()` that is optimized for performing equality /// comparisons. +/// +/// If T and U are both `char`, this function is constexpr. template -BEST_INLINE_ALWAYS bool equate_bytes(best::span lhs, best::span rhs) - requires(sizeof(T) == sizeof(U)) +BEST_INLINE_ALWAYS constexpr bool equate_bytes(best::span lhs, + best::span rhs) + requires best::byte_comparable { - if (lhs.size() != rhs.size()) return false; - if (lhs.data() == rhs.data() || lhs.is_empty()) return true; - - return 0 == - bytes_internal::memcmp(lhs.data(), rhs.data(), lhs.size() * sizeof(T)); + return bytes_internal::equate(lhs, rhs); } /// # `best::compare_bytes()` /// /// A typed wrapper over `memcmp()`. This performs total lexicographic /// comparison between two spans. +/// +/// If T and U are both `char`, this function is constexpr. template -BEST_INLINE_ALWAYS best::ord compare_bytes(best::span lhs, best::span rhs) - requires(sizeof(T) == sizeof(U)) +BEST_INLINE_ALWAYS constexpr best::ord compare_bytes(best::span lhs, + best::span rhs) + requires best::byte_comparable { - if (lhs.data() == rhs.data() || lhs.is_empty() || rhs.is_empty()) { - return lhs.size() <=> rhs.size(); - } - - auto to_compare = best::min(lhs.size(), rhs.size()) * sizeof(T); - int result = bytes_internal::memcmp(lhs.data(), rhs.data(), to_compare); - if (result == 0) { - return lhs.size() <=> rhs.size(); - } - return result <=> 0; + return bytes_internal::compare(lhs, rhs); } /// # `best::search_bytes()` @@ -100,28 +110,21 @@ BEST_INLINE_ALWAYS best::ord compare_bytes(best::span lhs, best::span rhs) /// Note that this will find the first *aligned* index, which is to say that /// calls to `memmem()` will continue until it either fails or returns an /// aligned index. +/// +/// If T and U are both `char`, this function is constexpr. template -BEST_INLINE_ALWAYS best::option search_bytes(best::span haystack, - best::span needle) - requires(sizeof(T) == sizeof(U)) +BEST_INLINE_ALWAYS constexpr best::option search_bytes( + best::span haystack, best::span needle) + requires best::byte_comparable { - auto* data = - reinterpret_cast(static_cast(haystack.data())); - size_t size = haystack.size() * sizeof(T); - size_t travel = 0; - - do { - void* found = bytes_internal::memmem(data, size, needle.data(), - needle.size() * sizeof(T)); - if (!found) return best::none; - - size_t offset = static_cast(found) - data; - travel += offset; - data += offset; - size -= offset; - } while (travel % alignof(T) != 0); - - return travel / sizeof(T); + if (!std::is_constant_evaluated()) { + return bytes_internal::search(haystack, needle); + } else if constexpr (best::constexpr_byte_comparable) { + return bytes_internal::search_constexpr(haystack, needle); + } else { + best::crash_internal::crash( + "cannot call best::search_bytes() in constexpr for this type"); + } } } // namespace best diff --git a/best/memory/bytes_test.cc b/best/memory/bytes_test.cc new file mode 100644 index 0000000..4b8a2a7 --- /dev/null +++ b/best/memory/bytes_test.cc @@ -0,0 +1,171 @@ +#include "best/memory/bytes.h" + +#include "best/container/span.h" +#include "best/test/test.h" + +namespace best::bytes_test { + +// Equality checker that does not depend on best::span::operator==, which is +// what this test is testing in the first place. +template +void eq(best::test& t, best::span a, best::span b, + best::location loc = best::here) { + if (!t.expect(a.size() == b.size(), + {"expected sizes to be equal: {} != {}", loc}, a.size(), + b.size())) { + return; + } + + bool equal = true; + for (size_t i = 0; i < a.size(); ++i) { + equal &= a[i] == b[i]; + } + + t.expect(equal, {"expected equal values:\n {:x?}\n {:x?}", loc}, a, b); +} + +best::test Mutate = [](auto& t) { + char a_[16] = {}; + auto a = best::span(a_); + best::fill_bytes(a, '?'); + eq(t, a, best::span("????????????????", 16)); + + auto b = best::from_nul("abcdefgh"); + auto c = best::from_nul("xzyxzyxzyxzyxzyxzy"); + + best::copy_bytes(a, b); + eq(t, a, best::span("abcdefgh????????", 16)); + + best::copy_overlapping_bytes(a[{.start = 7}], a[{.end = 7}]); + eq(t, a, best::span("abcdefgabcdefg??", 16)); + + best::copy_bytes(a, c); + eq(t, a, c[{.end = 16}]); +}; + +best::test Equate = [](auto& t) { + constexpr auto a = best::from_nul("abcdefgh1"); + constexpr auto b = best::from_nul("abcdefgh2"); + constexpr best::span e; + + t.expect(best::equate_bytes(e, e)); + t.expect(!best::equate_bytes(e, b)); + t.expect(!best::equate_bytes(a, e)); + + t.expect(!best::equate_bytes(a, b)); + t.expect(!best::equate_bytes(a[{.end = 8}], b)); + t.expect(!best::equate_bytes(a, b[{.end = 8}])); + t.expect(best::equate_bytes(a[{.end = 8}], b[{.end = 8}])); + +#if BEST_CONSTEXPR_MEMCMP + static_assert(best::equate_bytes(e, e)); + static_assert(!best::equate_bytes(e, b)); + static_assert(!best::equate_bytes(a, e)); + + static_assert(!best::equate_bytes(a, b)); + static_assert(!best::equate_bytes(a[{.end = 8}], b)); + static_assert(!best::equate_bytes(a, b[{.end = 8}])); + static_assert(best::equate_bytes(a[{.end = 8}], b[{.end = 8}])); +#endif + + int x[] = {1, 2, 3, 4, 5, 6, 7}; + int y[] = {1, 2, 3, 4, 5, 6, 8}; + + best::span a2 = x; + best::span b2 = y; + best::span e2; + + t.expect(best::equate_bytes(e2, e2)); + t.expect(!best::equate_bytes(e2, b2)); + t.expect(!best::equate_bytes(a2, e2)); + + t.expect(!best::equate_bytes(a2, b2)); + t.expect(!best::equate_bytes(a2[{.end = 6}], b2)); + t.expect(!best::equate_bytes(a2, b2[{.end = 6}])); + t.expect(best::equate_bytes(a2[{.end = 6}], b2[{.end = 6}])); +}; + +best::test Compare = [](auto& t) { + constexpr auto a = best::from_nul("abcdefgh1"); + constexpr auto b = best::from_nul("abcdefgh2"); + constexpr best::span e; + + t.expect(best::compare_bytes(e, e) == 0); + t.expect(best::compare_bytes(e, b) < 0); + t.expect(best::compare_bytes(a, e) > 0); + + t.expect(best::compare_bytes(a, b) < 0); + t.expect(best::compare_bytes(a[{.end = 8}], b) < 0); + t.expect(best::compare_bytes(a, b[{.end = 8}]) > 0); + t.expect(best::compare_bytes(a[{.end = 8}], b[{.end = 8}]) == 0); + +#if BEST_CONSTEXPR_MEMCMP + static_assert(best::compare_bytes(e, e) == 0); + static_assert(best::compare_bytes(e, b) < 0); + static_assert(best::compare_bytes(a, e) > 0); + + static_assert(best::compare_bytes(a, b) < 0); + static_assert(best::compare_bytes(a[{.end = 8}], b) < 0); + static_assert(best::compare_bytes(a, b[{.end = 8}]) > 0); + static_assert(best::compare_bytes(a[{.end = 8}], b[{.end = 8}]) == 0); +#endif + + int x[] = {1, 2, 3, 4, 5, 6, 7}; + int y[] = {1, 2, 3, 4, 5, 6, 8}; + + best::span a2 = x; + best::span b2 = y; + best::span e2; + + t.expect(best::compare_bytes(e2, e2) == 0); + t.expect(best::compare_bytes(e2, b2) < 0); + t.expect(best::compare_bytes(a2, e2) > 0); + + t.expect(best::compare_bytes(a2, b2) < 0); + t.expect(best::compare_bytes(a2[{.end = 6}], b2) < 0); + t.expect(best::compare_bytes(a2, b2[{.end = 6}]) > 0); + t.expect(best::compare_bytes(a2[{.end = 6}], b2[{.end = 6}]) == 0); +}; + +best::test Search = [](auto& t) { + constexpr auto a = best::from_nul("abcddefgh"); + constexpr auto b = best::from_nul("abc"); + constexpr auto c = best::from_nul("def"); + constexpr auto d = best::from_nul("ghi"); + constexpr auto f = best::from_nul("abcddefghi"); + constexpr best::span e; + + t.expect_eq(best::search_bytes(a, a), 0); + t.expect_eq(best::search_bytes(a, b), 0); + t.expect_eq(best::search_bytes(a, c), 4); + t.expect_eq(best::search_bytes(a, d), best::none); + t.expect_eq(best::search_bytes(a, e), 0); + t.expect_eq(best::search_bytes(a, f), best::none); + +#if BEST_CONSTEXPR_MEMCMP + static_assert(best::search_bytes(a, a) == 0); + static_assert(best::search_bytes(a, b) == 0); + static_assert(best::search_bytes(a, c) == 4); + static_assert(best::search_bytes(a, d) == best::none); + static_assert(best::search_bytes(a, e) == 0); + static_assert(best::search_bytes(a, f) == best::none); +#endif + + int x_[] = {1, 2, 3, 4, 4, 5, 6, 7, 8, 9}; + best::span x = x_; + + best::span a2 = x[{.count = 8}]; + best::span b2 = x[{.count = 3}]; + best::span c2 = x[{.start = 4, .count = 3}]; + best::span d2 = x[{.start = 7, .count = 3}]; + best::span f2 = x; + best::span e2; + + t.expect_eq(best::search_bytes(a2, a2), 0); + t.expect_eq(best::search_bytes(a2, b2), 0); + t.expect_eq(best::search_bytes(a2, c2), 4); + t.expect_eq(best::search_bytes(a2, d2), best::none); + t.expect_eq(best::search_bytes(a2, e2), 0); + t.expect_eq(best::search_bytes(a2, f2), best::none); +}; +} // namespace best::bytes_test \ No newline at end of file diff --git a/best/memory/internal/bytes.h b/best/memory/internal/bytes.h index 0ba072f..5665522 100644 --- a/best/memory/internal/bytes.h +++ b/best/memory/internal/bytes.h @@ -2,15 +2,157 @@ #define BEST_MEMORY_INTERNAL_BYTES_H_ #include +#include + +#include "best/base/hint.h" +#include "best/base/port.h" +#include "best/container/option.h" +#include "best/meta/taxonomy.h" +#include "best/meta/tlist.h" + +#define BEST_CONSTEXPR_MEMCMP_ BEST_HAS_FEATURE(cxx_constexpr_string_builtins) namespace best::bytes_internal { extern "C" { void* memcpy(void*, const void*, size_t) noexcept; void* memmove(void*, const void*, size_t) noexcept; +void* memchr(const void*, int, size_t) noexcept; void* memmem(const void*, size_t, const void*, size_t) noexcept; void* memset(void*, int, size_t) noexcept; int memcmp(const void*, const void*, size_t) noexcept; +size_t strlen(const char*) noexcept; } // extern "C" + +#if BEST_CONSTEXPR_MEMCMP_ +#define BEST_memcmp_ __builtin_memcmp +#define BEST_memchr_ __builtin_char_memchr +#else +#define BEST_memcmp_ best::bytes_internal::memcmp +#define BEST_memchr_ best::bytes_internal::memchr +#endif + +template +struct tag {}; + +template +void can_memcmp(tag, tag) + requires requires { + requires sizeof(T) == sizeof(U); +#if BEST_HAS_BUILTIN(__is_trivially_equality_comparable) + requires __is_trivially_equality_comparable(T); + requires __is_trivially_equality_comparable(U); +#else + requires std::is_integral_v; + requires std::is_integral_v; +#endif + }; + +template +void can_memcmp(tag, tag) + requires requires { + // Function pointer equality is all kinds of messed up. + + requires !best::is_func && !best::is_func; + // libc++ asserts there's something weird about virtual bases. + // This is not perfect detection for that case but allows a + // lot more "reasonable" code than libc++'s constraint. + // + // See: + // https://github.com/llvm/llvm-project/blob/ecf2a53407f517a261ee296e1f922c647a13a503/libcxx/include/__type_traits/is_equality_comparable.h#L41 + requires !std::is_polymorphic_v && !std::is_polymorphic_v; + + // Some platforms are just weird, man. + requires sizeof(T*) == sizeof(U*); + }; + +template +concept is_char = best::same, char> || + best::same, unsigned char> || + best::same, signed char> || + best::same, char8_t>; + +template +BEST_INLINE_ALWAYS constexpr bool equate(best::span lhs, best::span rhs) { + if (lhs.size() != rhs.size()) return false; + if (lhs.is_empty()) return true; + + if (!std::is_constant_evaluated()) { + if (lhs.data() == rhs.data()) return true; + } + return 0 == BEST_memcmp_(lhs.data(), rhs.data(), lhs.size() * sizeof(T)); +} + +template +BEST_INLINE_ALWAYS constexpr best::ord compare(best::span lhs, + best::span rhs) { + if (lhs.is_empty() || rhs.is_empty()) return lhs.size() <=> rhs.size(); + if (!std::is_constant_evaluated()) { + if (lhs.data() == rhs.data()) return lhs.size() <=> rhs.size(); + } + + auto to_compare = best::min(lhs.size(), rhs.size()) * sizeof(T); + int result = BEST_memcmp_(lhs.data(), rhs.data(), to_compare); + + if (result == 0) { + return lhs.size() <=> rhs.size(); + } + return result <=> 0; +} + +template +best::option constexpr search(best::span haystack, + best::span needle) { + auto* data = + reinterpret_cast(static_cast(haystack.data())); + size_t size = haystack.size() * sizeof(T); + size_t travel = 0; + + do { + void* found = bytes_internal::memmem(data, size, needle.data(), + needle.size() * sizeof(T)); + if (!found) return best::none; + + size_t offset = static_cast(found) - data; + travel += offset; + data += offset; + size -= offset; + } while (travel % alignof(T) != 0); + + return travel / sizeof(T); +} + +template +BEST_INLINE_ALWAYS constexpr best::option search_constexpr( + best::span haystack, best::span needle) { + size_t hz = haystack.size(); + size_t nz = needle.size(); + + if (nz == 0) return 0; + if (hz < nz) return best::none; + + T* hp = haystack.data().raw(); + U* np = needle.data().raw(); + + U first = *np; + const T* end = hp + hz; + while (true) { + size_t len = end - hp; + if (len < nz) return best::none; + + // Skip to the next possible candidate. + hp = (T*)BEST_memchr_(hp, first, len); + if (hp == nullptr) return best::none; + + // Check if we actually found the string. + if (BEST_memcmp_(hp, np, nz) == 0) { + return hp - haystack.data().raw(); + } + ++hp; + } +} + +#undef BEST_memcmp_ +#undef BEST_memchr_ } // namespace best::bytes_internal #endif // BEST_MEMORY_INTERNAL_BYTES_H_ \ No newline at end of file diff --git a/best/memory/internal/layout.h b/best/memory/internal/layout.h index 53b15bd..4c233d4 100644 --- a/best/memory/internal/layout.h +++ b/best/memory/internal/layout.h @@ -5,6 +5,7 @@ #include #include "best/container/object.h" +#include "best/meta/tlist.h" // This header contains implementations of the layout algorithms for structs and // unions; they are implemented as variable templates to encourage the compiler diff --git a/best/meta/internal/reflect.h b/best/meta/internal/reflect.h index 529dfff..009a3c3 100644 --- a/best/meta/internal/reflect.h +++ b/best/meta/internal/reflect.h @@ -1,11 +1,87 @@ #ifndef BEST_META_INTERNAL_REFLECT_H_ #define BEST_META_INTERNAL_REFLECT_H_ +#include + #include "best/base/fwd.h" #include "best/meta/taxonomy.h" #include "best/text/str.h" +// This needs to go in the global namespace, since its full name is relevant for +// substring operations that extract the names of things. These names are +// #defined away at the bottom of this header. +struct BEST_REFLECT_STRUCT_ { + BEST_REFLECT_STRUCT_* BEST_REFLECT_FIELD_; + enum BEST_REFLECT_ENUM_ { BEST_REFLECT_VALUE_ }; +}; + namespace best::reflect_internal { +// We work exclusively with spans here to avoid pulling in the full machinery +// of best::str encoding, which is not fast in constexpr. +template +constexpr best::span raw_name() { + return best::span::from_nul( + std::source_location::current().function_name()); +} +template +constexpr best::span raw_name() { + return best::span::from_nul( + std::source_location::current().function_name()); +} + +// Needles to search for that are *definitely* gonna be in the target compiler's +// pretty printed function names. +inline constexpr auto TypeNeedle = + best::span::from_nul("BEST_REFLECT_STRUCT_"); +inline constexpr auto FieldNeedle = + best::span::from_nul("BEST_REFLECT_FIELD_"); +inline constexpr auto ValueNeedle = best::span::from_nul( + "BEST_REFLECT_STRUCT_::BEST_REFLECT_ENUM_::BEST_REFLECT_VALUE_"); + +struct raw_offsets { + size_t start_offset; + size_t suffix_len; +}; + +// Helpers for creating a structural value that will contain the name of a +// field symbol. +template +extern const T v; +template +struct w { + const T* p; +}; +template +w(const T*) -> w; + +// Figure out how the compiler lays out the names of types, fields, and enum +// values in the names of function templates. +// inline constexpr auto TypeOffsets = [] { +// auto name = raw_name(); +// auto idx = *name.find(TypeNeedle); +// return raw_offsets{ +// .start_offset = idx, +// .suffix_len = name.size() - idx - TypeNeedle.size(), +// }; +// }(); +// inline constexpr auto FieldOffsets = [] { +// auto name = raw_name.BEST_REFLECT_FIELD_}>(); +// auto idx = *name.find(FieldNeedle); +// return raw_offsets{ +// .start_offset = idx, +// .suffix_len = name.size() - idx - FieldNeedle.size(), +// }; +// }(); +// inline constexpr auto ValueOffsets = [] { +// auto name = +// raw_name(); +// auto idx = *name.find(ValueNeedle); +// return raw_offsets{ +// .start_offset = idx, +// .suffix_len = name.size() - idx - ValueNeedle.size(), +// }; +// }(); + enum kind { Field, Struct, Value, Enum, NoFields }; struct validator { @@ -123,4 +199,9 @@ inline constexpr auto info = BestReflect(mirror::BEST_MIRROR_FTADLE_, (T*){}); }; // namespace best::reflect_internal +#define BEST_REFLECT_FIELD_ _private +#define BEST_REFLECT_STRUCT_ _private +#define BEST_REFLECT_VALUE_ _private +#define BEST_REFLECT_ENUM_ _private + #endif // BEST_META_INTERNAL_REFLECT_H_ \ No newline at end of file From abd25246d65078ee023d5b930f839be6c154eda0 Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Fri, 21 Jun 2024 16:19:06 -0400 Subject: [PATCH 4/6] cherrypick reflection out --- best/meta/BUILD | 24 -- best/meta/internal/reflect.h | 207 --------------- best/meta/reflect.h | 412 ------------------------------ best/meta/reflect_test.cc | 69 ----- best/text/BUILD | 1 - best/text/internal/format_impls.h | 19 -- 6 files changed, 732 deletions(-) delete mode 100644 best/meta/internal/reflect.h delete mode 100644 best/meta/reflect.h delete mode 100644 best/meta/reflect_test.cc diff --git a/best/meta/BUILD b/best/meta/BUILD index 1417a7b..84f8a65 100644 --- a/best/meta/BUILD +++ b/best/meta/BUILD @@ -81,30 +81,6 @@ cc_library( ] ) -cc_library( - name = "reflect", - hdrs = [ - "reflect.h", - "internal/reflect.h", - ], - deps = [ - ":taxonomy", - "//best/base:fwd", - "//best/container:row", - "//best/text:str", - ], -) - -cc_test( - name = "reflect_test", - srcs = ["reflect_test.cc"], - linkopts = ["-rdynamic"], - deps = [ - ":reflect", - "//best/test", - ] -) - cc_library( name = "tlist", hdrs = [ diff --git a/best/meta/internal/reflect.h b/best/meta/internal/reflect.h deleted file mode 100644 index 009a3c3..0000000 --- a/best/meta/internal/reflect.h +++ /dev/null @@ -1,207 +0,0 @@ -#ifndef BEST_META_INTERNAL_REFLECT_H_ -#define BEST_META_INTERNAL_REFLECT_H_ - -#include - -#include "best/base/fwd.h" -#include "best/meta/taxonomy.h" -#include "best/text/str.h" - -// This needs to go in the global namespace, since its full name is relevant for -// substring operations that extract the names of things. These names are -// #defined away at the bottom of this header. -struct BEST_REFLECT_STRUCT_ { - BEST_REFLECT_STRUCT_* BEST_REFLECT_FIELD_; - enum BEST_REFLECT_ENUM_ { BEST_REFLECT_VALUE_ }; -}; - -namespace best::reflect_internal { -// We work exclusively with spans here to avoid pulling in the full machinery -// of best::str encoding, which is not fast in constexpr. -template -constexpr best::span raw_name() { - return best::span::from_nul( - std::source_location::current().function_name()); -} -template -constexpr best::span raw_name() { - return best::span::from_nul( - std::source_location::current().function_name()); -} - -// Needles to search for that are *definitely* gonna be in the target compiler's -// pretty printed function names. -inline constexpr auto TypeNeedle = - best::span::from_nul("BEST_REFLECT_STRUCT_"); -inline constexpr auto FieldNeedle = - best::span::from_nul("BEST_REFLECT_FIELD_"); -inline constexpr auto ValueNeedle = best::span::from_nul( - "BEST_REFLECT_STRUCT_::BEST_REFLECT_ENUM_::BEST_REFLECT_VALUE_"); - -struct raw_offsets { - size_t start_offset; - size_t suffix_len; -}; - -// Helpers for creating a structural value that will contain the name of a -// field symbol. -template -extern const T v; -template -struct w { - const T* p; -}; -template -w(const T*) -> w; - -// Figure out how the compiler lays out the names of types, fields, and enum -// values in the names of function templates. -// inline constexpr auto TypeOffsets = [] { -// auto name = raw_name(); -// auto idx = *name.find(TypeNeedle); -// return raw_offsets{ -// .start_offset = idx, -// .suffix_len = name.size() - idx - TypeNeedle.size(), -// }; -// }(); -// inline constexpr auto FieldOffsets = [] { -// auto name = raw_name.BEST_REFLECT_FIELD_}>(); -// auto idx = *name.find(FieldNeedle); -// return raw_offsets{ -// .start_offset = idx, -// .suffix_len = name.size() - idx - FieldNeedle.size(), -// }; -// }(); -// inline constexpr auto ValueOffsets = [] { -// auto name = -// raw_name(); -// auto idx = *name.find(ValueNeedle); -// return raw_offsets{ -// .start_offset = idx, -// .suffix_len = name.size() - idx - ValueNeedle.size(), -// }; -// }(); - -enum kind { Field, Struct, Value, Enum, NoFields }; - -struct validator { - /// This needs to be in a struct so the info classes can befriend it. - template - static constexpr bool value = - (Info::Kind == Struct && best::same) || - (Info::Kind == Enum && best::same) || - (Info::Kind == NoFields && (best::is_struct || best::is_enum)); -}; - -template -concept valid_reflection = validator::value; - -template -class field_info final { - template - friend class best::reflected_field; - template - friend class best::reflected_type; - friend mirror; - friend validator; - - using struct_ = S; - using type = T; - inline static constexpr auto Kind = Field; - - constexpr field_info(best::str name, type struct_::*ptr, - best::row tags) - : name_(name), ptr_(ptr), tags_(tags) {} - - best::str name_; - type struct_::*ptr_; - best::row tags_; -}; - -template -class struct_info final { - template - friend class best::reflected_type; - friend mirror; - friend validator; - - using struct_ = S; - using enum_ = void; - inline static constexpr auto Kind = Struct; - - constexpr struct_info(best::str name, Tags tags, best::row fields) - : name_(name), tags_(tags), items_(fields) {} - - best::str name_; - Tags tags_; - best::row items_; -}; - -template -class elem_info final { - template - friend class best::reflected_value; - template - friend class best::reflected_type; - friend mirror; - friend validator; - - using enum_ = E; - inline static constexpr auto Kind = Value; - - constexpr elem_info(best::str name, enum_ elem, best::row tags) - : name_(name), elem_(elem), tags_(tags) {} - - best::str name_; - enum_ elem_; - best::row tags_; -}; - -template -class enum_info final { - template - friend class best::reflected_type; - friend mirror; - friend validator; - - using struct_ = void; - using enum_ = E; - inline static constexpr auto Kind = Enum; - - constexpr enum_info(best::str name, Tags tags, best::row values) - : name_(name), tags_(tags), items_(values) {} - - best::str name_; - Tags tags_; - best::row items_; -}; - -template -class no_fields final { - template - friend class best::reflected_type; - friend mirror; - friend validator; - - using struct_ = void; - using enum_ = void; - inline static constexpr auto Kind = NoFields; - - constexpr no_fields(best::str name, Tags tags) : name_(name), tags_(tags) {} - - best::str name_; - Tags tags_; - best::row<> items_; -}; - -template -inline constexpr auto info = BestReflect(mirror::BEST_MIRROR_FTADLE_, (T*){}); - -}; // namespace best::reflect_internal - -#define BEST_REFLECT_FIELD_ _private -#define BEST_REFLECT_STRUCT_ _private -#define BEST_REFLECT_VALUE_ _private -#define BEST_REFLECT_ENUM_ _private - -#endif // BEST_META_INTERNAL_REFLECT_H_ \ No newline at end of file diff --git a/best/meta/reflect.h b/best/meta/reflect.h deleted file mode 100644 index 2b32a75..0000000 --- a/best/meta/reflect.h +++ /dev/null @@ -1,412 +0,0 @@ -#ifndef BEST_META_REFLECT_H_ -#define BEST_META_REFLECT_H_ - -#include "best/container/row.h" -#include "best/meta/internal/reflect.h" -#include "best/meta/taxonomy.h" -#include "best/text/str.h" - -//! Struct and enum reflection. -//! -//! `best` provides a mechanism for reflecting the fields and variants of -//! user-defined structs and enums, which must define the `BestReflect()` -//! FTADLE: -//! -//! ``` -//! friend constexpr auto BestReflect(auto& mirror, MyStruct*) { -//! return mirror.reflect( -//! "MyStruct", -//! mirror.field("foo", &MyStruct::foo), -//! mirror.field("bar", &MyStruct::bar), -//! ); -//! } -//! ``` - -namespace best { - -/// # `best::reflected`, `best::is_reflected_struct`, -/// `best::is_reflected_enum` -/// -/// Whether `T` can be reflected, i.e., it is a struct or enum type that has a -/// working `BestReflect()` FTADLE implementation. -template -concept reflected = requires(const best::mirror& m, best::as_auto* ptr) { - { - BestReflect(m, ptr) - } -> reflect_internal::valid_reflection>; -}; -template -concept is_reflected_struct = - best::is_struct> && best::reflected; -template -concept is_reflected_enum = - best::is_enum> && best::reflected; - -/// # `best::reflect` -/// -/// Obtains a reflection of a reflected type. This will be a value of the type -/// `best::reflected_type`, although the exact specialization is not nameable. -template -inline constexpr auto reflect = - best::reflected_type>>{}; - -/// # `best::mirror` -/// -/// A value of this type is passed to the `BestReflect` FTADLE. This type -/// cannot be constructed by users and exists only for exposition. -class mirror final { - public: - /// # `mirror()` - /// - /// Reflects that there exists a type with the given name, the given members, - /// and optionally, a row of tag types. - constexpr auto operator()(best::str name, auto... members) const; - constexpr auto operator()(best::str name, best::is_row auto tags, - auto... members) const; - - /// # `mirror.field()` - /// - /// Reflects that `Struct` has a data member with the given name, the - /// given type, and optionally, a row of tag types. The result of this call - /// should be passed to `reflect()`. - template - constexpr auto field(best::str name, Type Struct::*member, - auto... tags) const; - - /// # `mirror.value()` - /// - /// Reflects that Enum `Struct` has an enumerator with the given name, the - /// given value, and optionally, a row of tag types. The result of this call - /// should be passed to `reflect()`. - template - constexpr auto value(best::str name, Enum value, auto... tags) const; - - mirror(const mirror&) = delete; - mirror& operator=(const mirror&) = delete; - - private: - constexpr mirror() = default; - - public: - static const mirror BEST_MIRROR_FTADLE_; -}; -inline constexpr mirror mirror::BEST_MIRROR_FTADLE_{}; -#define BEST_MIRROR_FTADLE_ _private - -/// # `best::reflected_field` -/// -/// A field of some reflected struct. The type parameter is an implementation -/// detail; this type cannot be instantiated by users. -/// -/// A `best::reflected_field` offers accessors for information about the field, -/// such as its name, access to a member-to-ptr, and so on. -/// -/// A field can also be used to offset into a struct, with the syntax -/// `my_struct->*field`. `my_struct` can be a reference or pointer to -/// the reflected type, or a dereferenceable type that returns one. -template -class reflected_field final { - private: - static_assert(info_.Kind == reflect_internal::Field, - "cannot instantiate best::reflected_field directly"); - using info_t = best::as_auto; - - public: - /// # `reflected_field::reflected` - /// - /// The type this field was reflected from. - using reflected = info_t::struct_; - - /// # `reflected_field::type` - /// - /// The type of this field. - using type = info_t::type; - - /// # `reflected_field::name()` - /// - /// Returns the name of this field as specified in the `BestReflect()` call. - constexpr best::str name() const { return info_.name_; } - - /// # `reflected_field::tags()` - /// - /// Returns any tags on this field that can be selected by `Key`. - template - constexpr best::is_row auto tags(best::tlist key = {}) const { - return info_.tags_.select(key); - } - - /// # `reflected_field::as_offset()` - /// - /// Returns a pointer-to-member that represents this field. - constexpr type reflected::*as_offset() const { return info_.ptr_; } - - constexpr friend decltype(auto) operator->*(auto&& r, reflected_field f) - requires best::same, reflected> - { - return BEST_FWD(r).*(f.as_offset()); - } - constexpr friend decltype(auto) operator->*( - best::is_deref auto&& r, reflected_field f) { - return (*BEST_FWD(r)).*(f.as_offset()); - } - - private: - template - friend class reflected_type; -}; - -/// # `best::reflected_value` -/// -/// A value of some reflected enum. The type parameter is an implementation -/// detail; this type cannot be instantiated by users. -/// -/// A `best::reflected_value` offers accessors for information about the field, -/// such as its name, access to the enum value itself, and so on. -template -class reflected_value final { - private: - static_assert(info_.Kind == reflect_internal::Value, - "cannot instantiate best::reflected_value directly"); - using info_t = best::as_auto; - - public: - /// # `reflected_field::reflected` - /// - /// The type this field was reflected from. - using reflected = info_t::enum_; - - /// # `reflected_field::value` - /// - /// The actual reflected value. - static constexpr reflected value = info_.elem_; - - /// # `reflected_field::name()` - /// - /// Returns the name of this field as specified in the `BestReflect()` call. - constexpr best::str name() const { return info_.name_; } - - /// # `reflected_field::tags()` - /// - /// Returns any tags on this field that can be selected by `Key`. - template - constexpr best::is_row auto tags(best::tlist key = {}) const { - return info_.tags_.select(key); - } - - private: - template - friend class reflected_type; -}; - -/// # `best::reflected_type` -/// -/// The result of reflecting a type. The type parameter is an implementation -/// detail; this type cannot be instantiated by users. -/// -/// To obtain an instance of this type, use `best::reflect`. -template -class reflected_type final { - private: - static_assert(info_.Kind == reflect_internal::Struct || - info_.Kind == reflect_internal::Enum || - info_.Kind == reflect_internal::NoFields, - "cannot instantiate best::field directly"); - using info_t = best::as_auto; - - public: - /// # `reflected_type::reflected` - /// - /// The type this is a reflection of. - using reflected = - best::select; - - /// # `reflected_type::name()` - /// - /// Returns the name of this type as specified in the `BestReflect()` call. - constexpr best::str name() const { return info_.name_; } - - /// # `reflected_type::find(member)` - /// - /// Looks up the field or value corresponding to a particular member. This - /// the member should be a pointer-to-member if this is a reflects a struct, - /// or a value of the underlying enum if this reflects an enum. - /// - /// It will then call `cb` with the appropriate value. Internally, it has the - /// semantics of calling `best::choice::match()` on a choice that can contain - /// any of the possible reflected field/value types produced by fields() or - /// values(), respectively. If no element matches, it is as if the selected - /// element has type `void`. - /// - /// Hence, the best way to use this function is to write something like this. - /// - /// ``` - /// auto name = best::reflect.find(xyz, - /// [](auto value) { return value.name() }, - /// [] { return best::str(""); }); - /// ``` - constexpr decltype(auto) find(auto member, auto&&... cases) const; - - /// # `reflected_type::tags()` - /// - /// Returns any tags on this type that can be selected by `Key`. - template - constexpr best::is_row auto tags(best::tlist key = {}) const { - return info_.tags_.select(key); - } - - /// # `reflected_type::fields()` - /// - /// Calls `cb` with a pack of `best::reflected_field`s for this type. - constexpr decltype(auto) fields(auto&& cb) const - requires best::is_struct; - - /// # `reflected_type::each_field(...)` - /// - /// Zips together the fields of a bunch of references-to-`reflected`, and - /// calls `cb` (the last argument of `args...`) on each row of fields. - constexpr void each_field(auto&&... args) const - requires best::is_struct; - - /// # `reflected_type::values()` - /// - /// Calls `cb` with a pack of `best::reflected_values`s for this type. - constexpr decltype(auto) values(auto&& cb) const - requires best::is_enum; - - constexpr reflected_type() = default; - - private: - /// Workaround for a dumb Clang 17 conformance bug. - template - static constexpr auto item = info_.items_[best::index]; -}; - -/// # `best::fields()` -/// -/// Extracts all of the fields out of a reflected struct and creates a row -/// of references of them. -constexpr auto fields(best::is_reflected_struct auto&& value) { - return best::reflect.fields( - [&](auto... f) { return best::row(best::bind, value->*f...); }); -} -} // namespace best - -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ - -namespace best { -constexpr auto mirror::operator()(best::str name, best::is_row auto tags, - auto... members) const { - if constexpr (sizeof...(members) == 0) { - return reflect_internal::no_fields>{name, - tags}; - } else if constexpr (((members.Kind == reflect_internal::Field) && ...)) { - static_assert( - best::same::struct_...>, - "all fields passed to a best::mirror must have the same " - "struct type"); - using Struct = - best::tlist::struct_...>:: - template type<0>; - return best::reflect_internal::struct_info< - Struct, best::as_auto, - best::as_auto...>{name, tags, {members...}}; - } else if constexpr (((members.Kind == reflect_internal::Value) && ...)) { - static_assert( - best::same::enum_...>, - "all values passed to best::mirror must have the same " - "enum type"); - using Enum = best::tlist< - typename best::as_auto::enum_...>::template type<0>; - return best::reflect_internal::enum_info< - Enum, best::as_auto, - best::as_auto...>{name, tags, {members...}}; - } else { - static_assert(sizeof...(members) == 0, - "passed invalid member values into the mirror"); - } -} -constexpr auto mirror::operator()(best::str name, auto... members) const { - return operator()(name, row(), members...); -} - -template -constexpr auto mirror::field(best::str name, Type Struct::*member, - auto... tags) const { - return best::reflect_internal::field_info...>{ - name, member, {tags...}}; -} - -template -constexpr auto mirror::value(best::str name, Enum value, auto... tags) const { - return best::reflect_internal::elem_info...>{ - name, value, {tags...}}; -} - -template -constexpr decltype(auto) reflected_type::find(auto member, - auto&&... cases) const { - if constexpr (best::is_struct) { - return fields([&](auto... fields) { - using Ch = best::choice; - Ch ch(best::index<0>); - - ((best::equal(member, fields.as_offset()) ? void(ch = Ch(fields)) - : void()), - ...); - return ch.match(BEST_FWD(cases)...); - }); - } else { - return values([&](auto... values) { - using Ch = best::choice; - Ch ch(best::index<0>); - - ((best::equal(member, values.value) ? void(ch = Ch(values)) : void()), - ...); - return ch.match(BEST_FWD(cases)...); - }); - } -} - -template -constexpr decltype(auto) reflected_type::fields(auto&& cb) const - requires best::is_struct -{ - return best::indices.apply([&] { - return best::call(BEST_FWD(cb), best::reflected_field>{}...); - }); -} - -template -constexpr void reflected_type::each_field(auto&&... args) const - requires best::is_struct -{ - if constexpr (sizeof...(args) == 1) { - fields([&](auto... fields) { (best::call(args..., fields), ...); }); - } else { - best::row row(BEST_FWD(args)...); - each_field([&](auto field) { - best::indices.apply([&] { - best::call(BEST_MOVE(row).last(), - BEST_MOVE(row)[best::index]->*field...); - }); - }); - } -} - -template -constexpr decltype(auto) reflected_type::values(auto&& cb) const - requires best::is_enum -{ - return best::indices.apply([&] { - return best::call(BEST_FWD(cb), best::reflected_value>{}...); - }); -} -} // namespace best -#endif // BEST_META_REFLECT_H_ \ No newline at end of file diff --git a/best/meta/reflect_test.cc b/best/meta/reflect_test.cc deleted file mode 100644 index 8033c5b..0000000 --- a/best/meta/reflect_test.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include "best/meta/reflect.h" - -#include "best/test/test.h" - -namespace best::reflect_test { -struct Tag {}; - -template -struct MyCallback { - using BestRowKey = Tag; - F callback; -}; - -template -MyCallback(F) -> MyCallback; - -struct MyType final { - int x, y, z; - best::str s1, s2; - - constexpr friend auto BestReflect(auto& m, MyType*) { - return m("MyType", // - m.field("x", &MyType::x), // - m.field("y", &MyType::y), // - m.field("z", &MyType::z, MyCallback([] { return 42; })), // - m.field("s1", &MyType::s1), // - m.field("s2", &MyType::s2)); - } -}; - -static_assert(best::is_reflected_struct); - -enum class MyEnum { A, B, C }; -constexpr auto BestReflect(auto& m, MyEnum*) { - return m("MyEnum", // - m.value("A", MyEnum::A), // - m.value("B", MyEnum::B), // - m.value("C", MyEnum::C)); -} - -static_assert(best::is_reflected_enum); - -best::test ToString = [](auto& t) { - t.expect_eq(best::format("{:?}", MyType{1, 2, 3, "foo", "bar"}), - R"(MyType {x: 1, y: 2, z: 3, s1: "foo", s2: "bar"})"); - - t.expect_eq(best::format("{:?}, {:?}", MyEnum::B, MyEnum(42)), - "MyEnum::B, MyEnum(42)"); -}; - -best::test Fields = [](auto& t) { - MyType x0{1, 2, 3, "foo", "bar"}; - t.expect_eq(best::fields(x0), best::row(1, 2, 3, "foo", "bar")); -}; - -best::test FindTag = [](auto& t) { - int found = best::reflect.find( - &MyType::z, - [](auto field) { - auto tags = field.tags(best::types); - if constexpr (!tags.is_empty()) { - return tags[best::index<0>].callback(); - } - return 0; - }, - [] { return 0; }); - t.expect_eq(found, 42); -}; -} // namespace best::reflect_test \ No newline at end of file diff --git a/best/text/BUILD b/best/text/BUILD index ddefa4b..ebcd212 100644 --- a/best/text/BUILD +++ b/best/text/BUILD @@ -79,7 +79,6 @@ cc_library( "//best/container:span", "//best/math:conv", "//best/meta:guard", - "//best/meta:reflect", ] ) diff --git a/best/text/internal/format_impls.h b/best/text/internal/format_impls.h index c46c3de..825aa0b 100644 --- a/best/text/internal/format_impls.h +++ b/best/text/internal/format_impls.h @@ -2,9 +2,7 @@ #define BEST_TEXT_INTERNAL_FORMAT_IMPLS_H_ #include -#include -#include "best/meta/reflect.h" #include "best/text/rune.h" #include "best/text/str.h" @@ -165,23 +163,6 @@ constexpr void BestFmtQuery(auto& query, R* range) query.requires_debug = false; } -void BestFmt(auto& fmt, const best::is_reflected_struct auto& value) { - auto refl = best::reflect; - auto rec = fmt.record(refl.name()); - refl.fields( - [&](auto... field) { (rec.field(field.name(), value->*field), ...); }); -} - -void BestFmt(auto& fmt, const best::is_reflected_enum auto& value) { - auto refl = best::reflect; - refl.find( - value, [&](auto& val) { fmt.format("{}::{}", refl.name(), val.name()); }, - [&] { - using U = std::underlying_type_t>; - fmt.format("{}({})", refl.name(), U(value)); - }); -} - namespace format_internal { using mark_as_used = void; } // namespace format_internal From 6a182f2bbb80fd049609d7d191710fddd1ceac63 Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Fri, 21 Jun 2024 17:08:22 -0400 Subject: [PATCH 5/6] more cleanup... --- best/base/hint.h | 2 +- best/container/BUILD | 1 - best/container/internal/span.h | 25 ----- best/container/span.h | 180 +++++++++++++++++++-------------- best/container/span_test.cc | 8 +- best/container/vec.h | 20 ++-- best/math/conv.h | 2 +- best/text/encoding.h | 2 +- best/text/str.h | 4 +- best/text/strbuf.h | 2 +- 10 files changed, 124 insertions(+), 122 deletions(-) delete mode 100644 best/container/internal/span.h diff --git a/best/base/hint.h b/best/base/hint.h index 56e9f02..08542f8 100644 --- a/best/base/hint.h +++ b/best/base/hint.h @@ -78,7 +78,7 @@ BEST_INLINE_ALWAYS constexpr void assume(bool truth) { /// Hides a value from the compiler's optimizer. [[nodiscard]] BEST_INLINE_SYNTHETIC constexpr auto&& black_box(auto&& value) { if (!std::is_constant_evaluated()) { - asm volatile("" : "+r"(value)); + asm volatile("" ::"m,r"(value)); } return decltype(value)(value); } diff --git a/best/container/BUILD b/best/container/BUILD index aff45f9..465a135 100644 --- a/best/container/BUILD +++ b/best/container/BUILD @@ -161,7 +161,6 @@ cc_library( name = "span", hdrs = [ "span.h", - "internal/span.h", "span_sort.h", ], deps = [ diff --git a/best/container/internal/span.h b/best/container/internal/span.h deleted file mode 100644 index cdced69..0000000 --- a/best/container/internal/span.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef BEST_CONTAINER_INTERNAL_SPAN_H_ -#define BEST_CONTAINER_INTERNAL_SPAN_H_ - -#include - -#include "best/container/object.h" -#include "best/container/option.h" - -namespace best::span_internal { - -template n> -struct repr { - best::object_ptr data = nullptr; - static constexpr size_t size = *n; -}; - -template -struct repr { - best::object_ptr data = nullptr; - size_t size = 0; -}; - -} // namespace best::span_internal - -#endif // BEST_CONTAINER_INTERNAL_SPAN_H_ \ No newline at end of file diff --git a/best/container/span.h b/best/container/span.h index 23c7598..0d9aaa6 100644 --- a/best/container/span.h +++ b/best/container/span.h @@ -6,7 +6,6 @@ #include #include "best/base/port.h" -#include "best/container/internal/span.h" #include "best/container/object.h" #include "best/container/option.h" #include "best/log/location.h" @@ -25,17 +24,55 @@ //! ranges, i.e., ranges that can be represented as spans. namespace best { +/// # `best::data()` +/// +/// Returns the data pointer of a contiguous range. +constexpr auto data(auto&& range) + requires requires { range.data(); } +{ + return BEST_FWD(range).data(); +} +template +constexpr T* data(T (&range)[n]) { + return range; +} +template +constexpr const T* data(const std::initializer_list& il) { + return il.begin(); +} + +/// # `best::size()` +/// +/// Returns the size of a contiguous range. +constexpr size_t size(const auto& range) + requires requires { range.size(); } +{ + return range.size(); +} +template +constexpr size_t size(const auto (&)[n]) { + return n; +} + /// # `best::contiguous` /// /// Whether T is a contiguous range that can be converted into a span. /// -/// This is defined as a type that enables calls to std::data and std::size(). +/// This is defined as a type that enables calls to best::data and best::size(). template -concept contiguous = requires(T&& t) { - { *std::data(t) } -> best::is_ref; - { std::size(t) } -> best::converts_to; +concept contiguous = requires(const T& ct, T&& t) { + best::size(ct); + { *best::data(ct) } -> best::is_ref; + { *best::data(t) } -> best::is_ref; }; +/// # `best::data_type` +/// +/// Extracts the referent type of a contiguous range. For example, +/// `best::data_type` is `int`. +template +using data_type = best::unref))>; + /// # `best::static_size` /// /// The static size of a best::contiguous type, if it has one. @@ -46,9 +83,9 @@ concept contiguous = requires(T&& t) { /// /// friend constexpr best::option BestStaticSize(auto, MyType*); /// -/// The returned size must equal the unique size returned by std::size when +/// The returned size must equal the unique size returned by best::size when /// applied to this type, or best::none. -template +template inline constexpr best::option static_size = BestStaticSize(best::types, best::as_ptr{}); @@ -63,14 +100,7 @@ concept static_contiguous = static_size.has_value(); /// Represents whether T is best::span for some U, n. template concept is_span = - best::same, best::span::type, - best::as_auto::extent>>; - -/// # `best::span_type` -/// -/// Given T = best::span, returns U. -template -using span_type = typename best::as_auto::type; + best::same, best::span, static_size>>; /// # `best::from_nul` /// @@ -81,11 +111,19 @@ constexpr best::span from_nul(T* ptr) { return best::span::from_nul(ptr); } -/// # `best::span_extent` +/// # `best::from_static` +/// +/// Constructs the best possible static span pointing to `range`. If `range` +/// does not have a static size, this returns a dynamic span. /// -/// Given T = best::span, returns n. -template -inline constexpr best::option span_extent = best::as_auto::extent; +/// Notably, this is not the behavior of `best::span`'s deduction guides: +/// `best::span(range)` will never deduce a static extent, because doing this by +/// default turns out to be very annoying. +template +constexpr best::span, best::static_size> from_static( + R&& range) { + return {BEST_FWD(range)}; +} /// # `best::span` /// @@ -137,20 +175,15 @@ class span final { using cptr = best::as_ptr; using ptr = best::as_ptr; - /// # `span::extent` - /// - /// The extent of this span, if it is statically known. - static constexpr best::option extent = n; - /// # `span::is_static` /// /// Whether this is a static span. - static constexpr bool is_static = extent.has_value(); + static constexpr bool is_static = n.has_value(); /// # `span::is_dynamic` /// /// Whether this is a dynamic span. - static constexpr bool is_dynamic = extent.is_empty(); + static constexpr bool is_dynamic = n.is_empty(); /// # `span::is_const` /// @@ -174,7 +207,7 @@ class span final { /// /// This span's data pointer is always null. constexpr span() - requires is_dynamic || (extent == 0) + requires is_dynamic || (n == 0) = default; /// # `span::span(span)` @@ -194,31 +227,21 @@ class span final { constexpr explicit(is_static) span(best::object_ptr data, size_t size, best::location loc = best::here); - /// # `span::span(contiguous)` + /// # `span::span(range)` /// /// Constructs a new span from a contiguous range. /// /// If this would construct a fixed-size span, and the list being constructed /// from has a different length, this constructor will crash. However, if /// this constructor can crash, it is explicit. - template - constexpr explicit( - is_static && - // XXX: For some god-forsaken reason Clang 17 seems to think that this is - // not a constant expression inside of this explicit(): `extent == - // best::static_size`, even though it's perfectly ok with it in the - // requires() that follows. This seems to be a bug (either in the - // compiler or in the standard). Either way, this ate an hour of my time. - // - // We work around it by inlining the optional equality here. - !(extent.has_value() == best::static_size.has_value() && - extent.value_or() == best::static_size.value_or())) - span(Range&& range, // - best::location loc = best::here) - requires best::qualifies_to, T> && - (is_dynamic || best::static_size.is_empty() || - extent == best::static_size) - : span(std::data(range), std::size(range), loc) {} + template + constexpr explicit(is_static && n != static_size) + span(R&& range, best::location loc = best::here) + requires best::qualifies_to>, T> && + (is_dynamic || // + best::static_size.is_empty() || // + best::static_size == best::static_size) + : span(best::data(range), best::size(range), loc) {} /// # `span::span{...}` /// @@ -231,7 +254,7 @@ class span final { constexpr explicit(is_static) span(std::initializer_list il, best::location loc = best::here) requires is_const - : span(std::data(il), std::size(il), loc) {} + : span(best::data(il), best::size(il), loc) {} /// # `span::from_nul()` /// @@ -253,12 +276,21 @@ class span final { /// # `span::data()` /// /// Returns the data pointer for this span. - constexpr best::object_ptr data() const { return repr_.data; } + constexpr best::object_ptr data() const { return data_; } /// # `span::size()` /// /// Returns the size (length) of this span. - constexpr size_t size() const { return repr_.size; } + constexpr static size_t size() + requires is_static + { + return *n; + } + constexpr size_t size() const + requires is_dynamic + { + return size_; + } /// # `span::is_empty()` /// @@ -328,7 +360,7 @@ class span final { /// compile time error; otherwise crashes. template constexpr T& operator[](best::index_t) const - requires is_dynamic || ((idx < *extent)); + requires is_dynamic || ((idx < size())); /// # `span[{.start = ...}]` /// @@ -341,7 +373,7 @@ class span final { /// and `this->is_static`, produces a compile time error; otherwise crashes. template constexpr auto operator[](best::vlist) const - requires(range.try_compute_count(extent).has_value()); + requires(range.try_compute_count(best::static_size).has_value()); /// # `span::at(idx)` /// @@ -518,7 +550,7 @@ class span final { requires best::equatable; template constexpr bool operator==(const R& range) const - requires best::equatable && (!is_span) + requires best::equatable> && (!is_span) { return *this == best::span(range); } @@ -528,17 +560,18 @@ class span final { requires best::comparable; template constexpr auto operator<=>(const R& range) const - requires best::comparable && (!is_span) + requires best::comparable> && (!is_span) { return *this <=> best::span(range); } constexpr friend best::option BestStaticSize(auto, span*) { - return extent; + return n; } private: - span_internal::repr repr_; + best::object_ptr data_ = nullptr; + [[no_unique_address]] best::select size_{}; }; template @@ -548,10 +581,7 @@ span(best::object_ptr, size_t) -> span; template span(std::initializer_list) -> span; template -span(R&& r) -> span, - // TODO(mcyoung): This part of the deduction guide has - // proven to be a pain in the ass. It should be opt-in. - best::static_size>; +span(R&& r) -> span>; } // namespace best /******************************************************************************/ @@ -614,13 +644,12 @@ inline constexpr size_t BestStaticSize(auto, std::array*) { template n> constexpr span::span(best::object_ptr data, size_t size, - best::location loc) { - repr_.data = data; + best::location loc) : data_(data) { if constexpr (is_static) { - best::bounds bounds_check = {.start = *extent, .count = 0}; + best::bounds bounds_check = {.start = this->size(), .count = 0}; bounds_check.compute_count(this->size(), loc); } else { - repr_.size = size; + size_ = size; } } @@ -697,8 +726,7 @@ constexpr span span::from_nul(T* data) { } auto ptr = data; - while (*ptr++ != T{0}) - ; + while (*ptr++ != T{0}); return best::span(data, ptr - data - 1); } @@ -733,7 +761,7 @@ constexpr T& span::operator[](best::track_location idx) const { template n> template constexpr T& span::operator[](best::index_t) const - requires is_dynamic || ((idx < *extent)) + requires is_dynamic || ((idx < size())) { return (*this)[idx]; } @@ -748,9 +776,9 @@ constexpr best::span span::operator[]( template n> template constexpr auto span::operator[](best::vlist) const - requires(range.try_compute_count(extent).has_value()) + requires(range.try_compute_count(best::static_size).has_value()) { - constexpr auto count = range.try_compute_count(extent); + constexpr auto count = range.try_compute_count(best::static_size); return with_extent(data() + range.start, *count); } @@ -765,7 +793,7 @@ constexpr best::option span::at(size_t idx) const { template n> constexpr best::option> span::at(best::bounds range) const { if (auto count = range.try_compute_count(size())) { - return span{data() + range.start, *count}; + return {{data() + range.start, *count}}; } return best::none; } @@ -781,7 +809,7 @@ constexpr best::span span::at(unsafe, best::bounds range) const { count = *range.count; } - return best::span(data() + range.start, count); + return {data() + range.start, count}; } template n> @@ -806,8 +834,8 @@ constexpr best::option, 2>> span::split_at( size_t idx) const { if (auto prefix = at({.end = idx})) { auto rest = *this; - rest.repr_.data += idx; - rest.repr_.size -= idx; + rest.data_ += idx; + rest.size_ -= idx; return {{*prefix, rest}}; } @@ -967,11 +995,11 @@ constexpr bool span::operator==(span that) const { if (std::is_constant_evaluated()) { if constexpr (best::constexpr_byte_comparable) { - return best::equate_bytes(span(*this), span(that)); + return best::equate_bytes(best::span(*this), best::span(that)); } } else { if constexpr (best::byte_comparable) { - return best::equate_bytes(span(*this), span(that)); + return best::equate_bytes(best::span(*this), best::span(that)); } } @@ -992,11 +1020,11 @@ constexpr auto span::operator<=>(span that) const { if (std::is_constant_evaluated()) { if constexpr (best::constexpr_byte_comparable) { - return best::compare_bytes(span(*this), span(that)); + return best::compare_bytes(best::span(*this), best::span(that)); } } else { if constexpr (best::byte_comparable) { - return best::compare_bytes(span(*this), span(that)); + return best::compare_bytes(best::span(*this), best::span(that)); } } diff --git a/best/container/span_test.cc b/best/container/span_test.cc index 68fca4d..2a4771d 100644 --- a/best/container/span_test.cc +++ b/best/container/span_test.cc @@ -10,8 +10,8 @@ static_assert(best::is_span>); static_assert(best::contiguous>); static_assert(best::contiguous>); -static_assert(best::span::extent.is_empty()); -static_assert(best::span::extent.has_value()); +static_assert(best::static_size>.is_empty()); +static_assert(best::static_size>.has_value()); best::test Empty = [](auto& t) { best::span empty; @@ -29,7 +29,7 @@ best::test Empty = [](auto& t) { best::test Convert = [](auto& t) { int arr[] = {1, 2, 3}; - best::span dfixed = arr; + auto dfixed = best::from_static(arr); static_assert(best::same>); t.expect(!dfixed.is_empty()); t.expect_eq(dfixed.size(), 3); @@ -124,7 +124,7 @@ best::test Ordering = [](auto& t) { best::test Indexing = [](auto& t) { int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - best::span sp = arr; + best::span sp = best::from_static(arr); auto tests = [&](auto sp) { t.expect_eq(sp.size(), 10); diff --git a/best/container/vec.h b/best/container/vec.h index 184c6ac..90aef72 100644 --- a/best/container/vec.h +++ b/best/container/vec.h @@ -97,7 +97,7 @@ class vec final { /// Constructs an owned copy of a range. template explicit vec(Range&& range) - requires best::constructible && + requires best::constructible && (!best::is_ref) && best::constructible : vec(alloc{}, BEST_FWD(range)) {} @@ -107,7 +107,7 @@ class vec final { /// Constructs an owned copy of a range using the given allocator. template vec(alloc alloc, Range&& range) - requires best::constructible && + requires best::constructible && (!best::is_ref) { assign(range); @@ -452,7 +452,7 @@ class vec final { /// Appends a range by copying to the end of this vector. template > void append(const Range& range) - requires best::constructible + requires best::constructible { splice(size(), range); } @@ -462,11 +462,11 @@ class vec final { /// Splices a range by copying into this vector, starting at idx. template > void splice(size_t idx, const Range& range) - requires best::constructible + requires best::constructible { auto ptr = insert_uninit(unsafe("we call copy_from immediately after this"), - idx, std::size(range)); - best::span(ptr, std::size(range)).copy_from(best::span(range)); + idx, best::size(range)); + best::span(ptr, best::size(range)).copy_from(best::span(range)); } /// # `vec::clear()`. @@ -563,12 +563,12 @@ class vec final { void spill_to_heap(best::option capacity_hint = best::none); bool operator==(const contiguous auto& range) const - requires best::equatable + requires best::equatable { return as_span() == best::span(range); } auto operator<=>(const contiguous auto& range) const - requires best::equatable + requires best::equatable { return as_span() <=> best::span(range); } @@ -790,11 +790,11 @@ void vec::assign(const contiguous auto& that) { } auto old_size = size(); - auto new_size = std::size(that); + auto new_size = best::size(that); resize_uninit(new_size); for (size_t i = 0; i < new_size; ++i) { - (data() + i).copy_from(std::data(that) + i, /*is_init=*/i < old_size); + (data() + i).copy_from(best::data(that) + i, /*is_init=*/i < old_size); } set_size(unsafe("updating size to that of the memcpy'd range"), new_size); } diff --git a/best/math/conv.h b/best/math/conv.h index a56d39c..f7cc307 100644 --- a/best/math/conv.h +++ b/best/math/conv.h @@ -49,7 +49,7 @@ constexpr best::result atoi(const string_type auto &str, // to the maximum number if nybbles, it will not overflow. However, if it // is a signed type, we need to subtract off one extra code unit, since // e.g. `80` will overflow `int8_t`. - size_t total_codes = std::size(str); + size_t total_codes = best::size(str); size_t maximum_codes_without_overflow = sizeof(Int) * 2 - best::signed_int; size_t cannot_overflow = diff --git a/best/text/encoding.h b/best/text/encoding.h index ff5c001..c1e23d6 100644 --- a/best/text/encoding.h +++ b/best/text/encoding.h @@ -136,7 +136,7 @@ concept string_type = best::contiguous && requires(best::ftadle& tag, const T& value) { { BestEncoding(tag, value) } -> best::encoding; { - std::data(value) + best::data(value) } -> best::same const*>; }; diff --git a/best/text/str.h b/best/text/str.h index acfb856..92de047 100644 --- a/best/text/str.h +++ b/best/text/str.h @@ -46,7 +46,7 @@ using str32 = best::text; #define BEST_IS_VALID_LITERAL(literal_, enc_) \ BEST_ENABLE_IF_CONSTEXPR(literal_) \ BEST_ENABLE_IF( \ - rune::validate(best::span(literal_, std::size(literal_) - 1), enc_), \ + rune::validate(best::span(literal_, best::size(literal_) - 1), enc_), \ "string literal must satisfy rune::validate() for the chosen encoding") /// # `best::text` @@ -126,7 +126,7 @@ class text final { constexpr static best::option from(best::span data, encoding enc = {}); constexpr static best::option from(const string_type auto& that) { - return from(span(std::data(that), std::size(that)), + return from(span(best::data(that), best::size(that)), best::encoding_of(that)); } diff --git a/best/text/strbuf.h b/best/text/strbuf.h index 47fd642..7922972 100644 --- a/best/text/strbuf.h +++ b/best/text/strbuf.h @@ -155,7 +155,7 @@ class textbuf final { return from(alloc{}, that); } static best::option from(alloc alloc, const string_type auto& that) { - return from(std::move(alloc), span(std::data(that), std::size(that)), + return from(std::move(alloc), span(best::data(that), best::size(that)), best::encoding_of(that)); } From b3407f570f9ec297f643ef8a7e8b459c1328dfdc Mon Sep 17 00:00:00 2001 From: Miguel Young de la Sota Date: Fri, 21 Jun 2024 17:18:22 -0400 Subject: [PATCH 6/6] banners --- best/container/choice.h | 8 +++----- best/container/option.h | 8 +++----- best/container/result.h | 8 +++----- best/container/row.h | 8 +++----- best/container/span.h | 17 +++++++++-------- best/container/vec.h | 8 +++----- best/text/format.h | 8 +++----- best/text/str.h | 14 ++++++-------- best/text/strbuf.h | 8 +++----- 9 files changed, 36 insertions(+), 51 deletions(-) diff --git a/best/container/choice.h b/best/container/choice.h index 3760800..7711212 100644 --- a/best/container/choice.h +++ b/best/container/choice.h @@ -363,11 +363,9 @@ class choice final { }; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/container/option.h b/best/container/option.h index 3c6c111..8304f43 100644 --- a/best/container/option.h +++ b/best/container/option.h @@ -829,11 +829,9 @@ class span; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/container/result.h b/best/container/result.h index 2d4d436..8ec0694 100644 --- a/best/container/result.h +++ b/best/container/result.h @@ -347,11 +347,9 @@ class [[nodiscard( }; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/container/row.h b/best/container/row.h index 1514f86..c6a8339 100644 --- a/best/container/row.h +++ b/best/container/row.h @@ -318,11 +318,9 @@ struct row_forward final { }; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/container/span.h b/best/container/span.h index 0d9aaa6..873110d 100644 --- a/best/container/span.h +++ b/best/container/span.h @@ -571,7 +571,8 @@ class span final { private: best::object_ptr data_ = nullptr; - [[no_unique_address]] best::select size_{}; + [[no_unique_address]] best::select + size_{}; }; template @@ -584,11 +585,9 @@ template span(R&& r) -> span>; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template n> @@ -644,7 +643,8 @@ inline constexpr size_t BestStaticSize(auto, std::array*) { template n> constexpr span::span(best::object_ptr data, size_t size, - best::location loc) : data_(data) { + best::location loc) + : data_(data) { if constexpr (is_static) { best::bounds bounds_check = {.start = this->size(), .count = 0}; bounds_check.compute_count(this->size(), loc); @@ -726,7 +726,8 @@ constexpr span span::from_nul(T* data) { } auto ptr = data; - while (*ptr++ != T{0}); + while (*ptr++ != T{0}) + ; return best::span(data, ptr - data - 1); } diff --git a/best/container/vec.h b/best/container/vec.h index 90aef72..1e1ae01 100644 --- a/best/container/vec.h +++ b/best/container/vec.h @@ -647,11 +647,9 @@ template vec(std::initializer_list) -> vec; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/text/format.h b/best/text/format.h index 0014ba8..b754de0 100644 --- a/best/text/format.h +++ b/best/text/format.h @@ -382,11 +382,9 @@ void eprintln(best::format_template templ = "", const Args&... args); decltype(auto) make_formattable(const auto& value); } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { void formatter::write(const best::string_type auto& string) { diff --git a/best/text/str.h b/best/text/str.h index 92de047..f34b49c 100644 --- a/best/text/str.h +++ b/best/text/str.h @@ -43,9 +43,9 @@ using str32 = best::text; /// literal for `enc_`. /// /// This is intended to be placed after any `requires` clauses. -#define BEST_IS_VALID_LITERAL(literal_, enc_) \ - BEST_ENABLE_IF_CONSTEXPR(literal_) \ - BEST_ENABLE_IF( \ +#define BEST_IS_VALID_LITERAL(literal_, enc_) \ + BEST_ENABLE_IF_CONSTEXPR(literal_) \ + BEST_ENABLE_IF( \ rune::validate(best::span(literal_, best::size(literal_) - 1), enc_), \ "string literal must satisfy rune::validate() for the chosen encoding") @@ -370,11 +370,9 @@ class text final { }; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template diff --git a/best/text/strbuf.h b/best/text/strbuf.h index 7922972..8a61ac6 100644 --- a/best/text/strbuf.h +++ b/best/text/strbuf.h @@ -493,11 +493,9 @@ class textbuf final { }; } // namespace best -/******************************************************************************/ - -///////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ///////////////////// - -/******************************************************************************/ +/* ////////////////////////////////////////////////////////////////////////// *\ + * ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// * +\* ////////////////////////////////////////////////////////////////////////// */ namespace best { template