From 7a01a018ad735bad3f74961dace3e458af627709 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Fri, 8 Sep 2023 23:29:18 -0400 Subject: [PATCH 01/31] Remove use of FnMutRef in slice splitting Use the Fn concepts instead and templatize the Split iterators. This avoids holding a pointer to a possibly-temporary function object. --- sus/collections/__private/slice_methods.inc | 58 +++--- .../__private/slice_mut_methods.inc | 48 +++-- sus/collections/iterators/split.h | 192 +++++++++--------- sus/collections/slice_unittest.cc | 4 +- 4 files changed, 153 insertions(+), 149 deletions(-) diff --git a/sus/collections/__private/slice_methods.inc b/sus/collections/__private/slice_methods.inc index 8cba107ed..294428931 100644 --- a/sus/collections/__private/slice_methods.inc +++ b/sus/collections/__private/slice_methods.inc @@ -112,7 +112,7 @@ binary_search(const T& x) const& noexcept /// element could be inserted while maintaining sorted order. constexpr ::sus::result::Result<::sus::num::usize, ::sus::num::usize> binary_search_by( - ::sus::fn::FnMutRef f) const& noexcept { + ::sus::fn::FnMut auto f) const& noexcept { using Result = ::sus::result::Result<::sus::num::usize, ::sus::num::usize>; // INVARIANTS: @@ -518,7 +518,7 @@ constexpr ::sus::Option last() && = delete; /// See also `binary_search()`, `binary_search_by()`, and /// `binary_search_by_key()`. constexpr ::sus::num::usize partition_point( - ::sus::fn::FnMutRef pred) const& noexcept { + ::sus::fn::FnMut auto pred) const& noexcept { return binary_search_by([pred = ::sus::move(pred)](const T& x) { if (::sus::fn::call_mut(pred, x)) { return std::strong_ordering::less; @@ -642,13 +642,15 @@ constexpr ::sus::collections::Vec repeat(usize n) const& noexcept /// /// As with `split()`, if the first or last element is matched, an empty slice /// will be the first (or last) item returned by the iterator. -constexpr RSplit rsplit( - ::sus::fn::FnMutRef pred) const& noexcept { - return RSplit(Split(_iter_refs_expr, *this, ::sus::move(pred))); +template <::sus::fn::FnMut Pred> +constexpr RSplit rsplit(Pred pred) const& noexcept { + return RSplit( + Split(_iter_refs_expr, *this, ::sus::move(pred))); } #if _delete_rvalue -constexpr Split rsplit(::sus::fn::FnMutRef) && = delete; +template <::sus::fn::FnMut Pred> +constexpr Split rsplit(Pred) && = delete; #endif /// Returns an iterator over subslices separated by elements that match `pred` @@ -656,15 +658,17 @@ constexpr Split rsplit(::sus::fn::FnMutRef) && = delete; /// and works backwards. The matched element is not contained in the subslices. /// /// The last element returned, if any, will contain the remainder of the slice. -constexpr RSplitN rsplitn( - usize n, ::sus::fn::FnMutRef pred) const& noexcept { - return RSplitN( - RSplit(Split(_iter_refs_expr, *this, ::sus::move(pred))), n); +template <::sus::fn::FnMut Pred> +constexpr RSplitN rsplitn(usize n, Pred pred) const& noexcept { + return RSplitN( // + RSplit( + Split(_iter_refs_expr, *this, ::sus::move(pred))), + n); } #if _delete_rvalue -constexpr RSplit rsplitn( - usize n, ::sus::fn::FnMutRef pred) && = delete; +template <::sus::fn::FnMut Pred> +constexpr RSplit rsplitn(usize n, Pred pred) && = delete; #endif /// Returns an iterator over subslices separated by elements that match `pred`. @@ -673,13 +677,14 @@ constexpr RSplit rsplitn( /// If the first element is matched, an empty slice will be the first item /// returned by the iterator. Similarly, if the last element in the slice is /// matched, an empty slice will be the last item returned by the iterator. -constexpr Split split( - ::sus::fn::FnMutRef pred) const& noexcept { - return Split(_iter_refs_expr, *this, ::sus::move(pred)); +template <::sus::fn::FnMut Pred> +constexpr Split split(Pred pred) const& noexcept { + return Split(_iter_refs_expr, *this, ::sus::move(pred)); } #if _delete_rvalue -constexpr Split split(::sus::fn::FnMutRef) && = delete; +template <::sus::fn::FnMut Pred> +constexpr Split split(Pred) && = delete; #endif /// Divides one slice into two at an index. @@ -756,14 +761,14 @@ constexpr ::sus::Option<::sus::Tuple>> split_first() && = /// If the last element of the slice is matched, that element will be considered /// the terminator of the preceding slice. That slice will be the last item /// returned by the iterator. -constexpr SplitInclusive split_inclusive( - ::sus::fn::FnMutRef pred) const& noexcept { - return SplitInclusive(_iter_refs_expr, *this, ::sus::move(pred)); +template <::sus::fn::FnMut Pred> +constexpr SplitInclusive split_inclusive(Pred pred) const& noexcept { + return SplitInclusive(_iter_refs_expr, *this, ::sus::move(pred)); } #if _delete_rvalue -constexpr SplitInclusive split_inclusive( - ::sus::fn::FnMutRef) && = delete; +template <::sus::fn::FnMut Pred> +constexpr SplitInclusive split_inclusive(Pred) && = delete; #endif /// Returns the last and all the rest of the elements of the slice, or `None` if @@ -786,14 +791,15 @@ constexpr ::sus::Option<::sus::Tuple>> split_last() && = /// in the subslices. /// /// The last element returned, if any, will contain the remainder of the slice. -constexpr SplitN splitn( - usize n, ::sus::fn::FnMutRef pred) const& noexcept { - return SplitN(Split(_iter_refs_expr, *this, ::sus::move(pred)), n); +template <::sus::fn::FnMut Pred> +constexpr SplitN splitn(usize n, Pred pred) const& noexcept { + return SplitN( + Split(_iter_refs_expr, *this, ::sus::move(pred)), n); } #if _delete_rvalue -constexpr Split splitn(usize n, - ::sus::fn::FnMutRef pred) && = delete; +template <::sus::fn::FnMut Pred> +constexpr Split splitn(usize n, Pred pred) && = delete; #endif /// Returns `true` if `needle` is a prefix of the slice. diff --git a/sus/collections/__private/slice_mut_methods.inc b/sus/collections/__private/slice_mut_methods.inc index f74097fd2..af60d4b88 100644 --- a/sus/collections/__private/slice_mut_methods.inc +++ b/sus/collections/__private/slice_mut_methods.inc @@ -233,7 +233,7 @@ constexpr void fill(T value) NO_RETURN_REF noexcept /// This method uses a closure to create new values. If you’d rather `Clone` a /// given value, use `fill()`. If you want to default-construct elements for a /// type that satisfies `sus::construct::Default`, use `fill_with_default()`. -constexpr void fill_with(::sus::fn::FnMutRef f) NO_RETURN_REF noexcept { +constexpr void fill_with(::sus::fn::FnMut auto f) NO_RETURN_REF noexcept { T* ptr = as_mut_ptr(); T* const end_ptr = ptr + len(); while (ptr != end_ptr) { @@ -527,9 +527,10 @@ void rotate_right(::sus::num::usize k) NO_RETURN_REF noexcept /// /// As with `split_mut()`, if the first or last element is matched, an empty /// slice will be the first (or last) item returned by the iterator. -constexpr RSplitMut rsplit_mut(::sus::fn::FnMutRef pred) - RETURN_REF noexcept { - return RSplitMut(SplitMut(_iter_refs_expr, *this, ::sus::move(pred))); +template <::sus::fn::FnMut Pred> +constexpr RSplitMut rsplit_mut(Pred pred) RETURN_REF noexcept { + return RSplitMut( + SplitMut(_iter_refs_expr, *this, ::sus::move(pred))); } /// Returns an iterator over subslices separated by elements that match `pred` @@ -537,10 +538,13 @@ constexpr RSplitMut rsplit_mut(::sus::fn::FnMutRef pred) /// and works backwards. The matched element is not contained in the subslices. /// /// The last element returned, if any, will contain the remainder of the slice. -constexpr RSplitNMut rsplitn_mut( - usize n, ::sus::fn::FnMutRef pred) RETURN_REF noexcept { - return RSplitNMut( - RSplitMut(SplitMut(_iter_refs_expr, *this, ::sus::move(pred))), n); +template <::sus::fn::FnMut Pred> +constexpr RSplitNMut rsplitn_mut(usize n, + Pred pred) RETURN_REF noexcept { + return RSplitNMut( // + RSplitMut( + SplitMut(_iter_refs_expr, *this, ::sus::move(pred))), + n); } #if 0 @@ -587,7 +591,7 @@ constexpr RSplitNMut rsplitn_mut( /// Panics when `index >= len()`, meaning it always panics on empty slices. ::sus::Tuple, T&, SliceMut> select_nth_unstable_by( usize index, - ::sus::fn::FnMutRef compare) + ::sus::fn::FnMut auto compare) RETURN_REF noexcept { ::sus::check_with_message( index < len(), "partition_at_index index greater than length of slice"); @@ -659,7 +663,7 @@ void sort() NO_RETURN_REF noexcept /// /// TODO: Rust's stable sort is O(n * log(n)), so this can be improved. It can /// also be constexpr? -void sort_by(::sus::fn::FnMutRef +void sort_by(::sus::fn::FnMut auto compare) NO_RETURN_REF noexcept { if (len() > ::sus::num::usize(0u)) { std::stable_sort(as_mut_ptr(), as_mut_ptr() + len(), @@ -760,7 +764,7 @@ constexpr void sort_unstable() NO_RETURN_REF noexcept /// TODO: Rust's sort is unstable (i.e., may reorder equal elements), in-place /// (i.e., does not allocate), and O(n * log(n)) worst-case. constexpr void sort_unstable_by( - ::sus::fn::FnMutRef compare) + ::sus::fn::FnMut auto compare) NO_RETURN_REF noexcept { if (len() > 0u) { std::sort(as_mut_ptr(), as_mut_ptr() + len(), @@ -794,9 +798,9 @@ void sort_unstable_by_key(KeyFn f) NO_RETURN_REF noexcept { /// If the first element is matched, an empty slice will be the first item /// returned by the iterator. Similarly, if the last element in the slice is /// matched, an empty slice will be the last item returned by the iterator. -constexpr SplitMut split_mut(::sus::fn::FnMutRef pred) - RETURN_REF noexcept { - return SplitMut(_iter_refs_expr, *this, ::sus::move(pred)); +template <::sus::fn::FnMut Pred> +constexpr SplitMut split_mut(Pred pred) RETURN_REF noexcept { + return SplitMut(_iter_refs_expr, *this, ::sus::move(pred)); } /// Divides one mutable slice into two at an index. @@ -865,9 +869,10 @@ split_first_mut() RETURN_REF noexcept { /// If the last element of the slice is matched, that element will be considered /// the terminator of the preceding slice. That slice will be the last item /// returned by the iterator. -constexpr SplitInclusiveMut split_inclusive_mut( - ::sus::fn::FnMutRef pred) RETURN_REF noexcept { - return SplitInclusiveMut(_iter_refs_expr, *this, ::sus::move(pred)); +template <::sus::fn::FnMut Pred> +constexpr SplitInclusiveMut split_inclusive_mut(Pred pred) + RETURN_REF noexcept { + return SplitInclusiveMut(_iter_refs_expr, *this, ::sus::move(pred)); } /// Returns the last and all the rest of the elements of the slice, or `None` if @@ -889,10 +894,11 @@ sus_pure constexpr ::sus::Option<::sus::Tuple>> split_last_mut() /// contained in the subslices. /// /// The last element returned, if any, will contain the remainder of the slice. -constexpr SplitNMut splitn_mut( - usize n, ::sus::fn::FnMutRef pred) RETURN_REF noexcept { - return SplitNMut(SplitMut(_iter_refs_expr, *this, ::sus::move(pred)), - n); +template <::sus::fn::FnMut Pred> +constexpr SplitNMut splitn_mut(usize n, + Pred pred) RETURN_REF noexcept { + return SplitNMut( + SplitMut(_iter_refs_expr, *this, ::sus::move(pred)), n); } /// Returns a subslice with the `prefix` removed. diff --git a/sus/collections/iterators/split.h b/sus/collections/iterators/split.h index 1f7c5ed6e..22772db1e 100644 --- a/sus/collections/iterators/split.h +++ b/sus/collections/iterators/split.h @@ -32,7 +32,7 @@ namespace __private { /// match a predicate function, splitting at most a fixed number of /// times. template I> -class [[sus_trivial_abi]] GenericSplitN final +class GenericSplitN final : public ::sus::iter::IteratorBase, ItemT> { public: using Item = ItemT; @@ -66,8 +66,8 @@ class [[sus_trivial_abi]] GenericSplitN final I iter_; usize count_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(iter_), - decltype(count_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(iter_), decltype(count_)); }; } // namespace __private @@ -76,9 +76,9 @@ class [[sus_trivial_abi]] GenericSplitN final /// function. /// /// This struct is created by the `split()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] Split final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] Split final + : public ::sus::iter::IteratorBase, ::sus::collections::Slice> { public: // `Item` is a `Slice`. @@ -160,7 +160,7 @@ class [[nodiscard]] [[sus_trivial_abi]] Split final template B> friend class __private::GenericSplitN; // Access to finish(). - template + template APred> friend class RSplit; constexpr Option finish() noexcept { @@ -173,26 +173,27 @@ class [[nodiscard]] [[sus_trivial_abi]] Split final } constexpr Split(::sus::iter::IterRef ref, const Slice& values, - ::sus::fn::FnMutRef&& pred) noexcept + Pred&& pred) noexcept : ref_(::sus::move(ref)), v_(values), pred_(::sus::move(pred)) {} [[sus_no_unique_address]] ::sus::iter::IterRef ref_; Slice v_; - ::sus::fn::FnMutRef pred_; + Pred pred_; bool finished_ = false; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(ref_), - decltype(v_), decltype(pred_), - decltype(finished_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(ref_), decltype(v_), + decltype(pred_), + decltype(finished_)); }; /// An iterator over subslices separated by elements that match a predicate /// function. /// /// This struct is created by the `split_mut()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] SplitMut final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] SplitMut final + : public ::sus::iter::IteratorBase, ::sus::collections::SliceMut> { public: // `Item` is a `SliceMut`. @@ -274,7 +275,7 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitMut final template B> friend class __private::GenericSplitN; // Access to finish(). - template + template > friend class RSplitMut; constexpr Option finish() noexcept { @@ -286,24 +287,19 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitMut final } } - static constexpr auto with( - ::sus::iter::IterRef ref, const SliceMut& values, - ::sus::fn::FnMutRef&& pred) noexcept { - return SplitMut(::sus::move(ref), values, ::sus::move(pred)); - } - constexpr SplitMut(::sus::iter::IterRef ref, const SliceMut& values, - ::sus::fn::FnMutRef&& pred) noexcept + Pred&& pred) noexcept : ref_(::sus::move(ref)), v_(values), pred_(::sus::move(pred)) {} [[sus_no_unique_address]] ::sus::iter::IterRef ref_; SliceMut v_; - ::sus::fn::FnMutRef pred_; + Pred pred_; bool finished_ = false; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(ref_), - decltype(v_), decltype(pred_), - decltype(finished_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(ref_), decltype(v_), + decltype(pred_), + decltype(finished_)); }; /// An iterator over subslices separated by elements that match a predicate @@ -311,9 +307,9 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitMut final /// of the subslice. /// /// This struct is created by the `split_inclusive()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] SplitInclusive final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] SplitInclusive final + : public ::sus::iter::IteratorBase, ::sus::collections::Slice> { public: // `Item` is a `Slice`. @@ -398,9 +394,8 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitInclusive final template friend class Array; - constexpr SplitInclusive( - ::sus::iter::IterRef ref, const Slice& values, - ::sus::fn::FnMutRef&& pred) noexcept + constexpr SplitInclusive(::sus::iter::IterRef ref, const Slice& values, + Pred&& pred) noexcept : ref_(::sus::move(ref)), v_(values), pred_(::sus::move(pred)), @@ -408,12 +403,13 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitInclusive final [[sus_no_unique_address]] ::sus::iter::IterRef ref_; Slice v_; - ::sus::fn::FnMutRef pred_; + Pred pred_; bool finished_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(ref_), - decltype(v_), decltype(pred_), - decltype(finished_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(ref_), decltype(v_), + decltype(pred_), + decltype(finished_)); }; /// An iterator over subslices separated by elements that match a predicate @@ -421,9 +417,9 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitInclusive final /// of the subslice. /// /// This struct is created by the `split_inclusive_mut()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] SplitInclusiveMut final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] SplitInclusiveMut final + : public ::sus::iter::IteratorBase, ::sus::collections::SliceMut> { public: // `Item` is a `SliceMut`. @@ -509,15 +505,9 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitInclusiveMut final template friend class Array; - static constexpr auto with( - ::sus::iter::IterRef ref, const SliceMut& values, - ::sus::fn::FnMutRef&& pred) noexcept { - return SplitInclusiveMut(::sus::move(ref), values, ::sus::move(pred)); - } - - constexpr SplitInclusiveMut( - ::sus::iter::IterRef ref, const SliceMut& values, - ::sus::fn::FnMutRef&& pred) noexcept + constexpr SplitInclusiveMut(::sus::iter::IterRef ref, + const SliceMut& values, + Pred&& pred) noexcept : ref_(::sus::move(ref)), v_(values), pred_(::sus::move(pred)), @@ -525,25 +515,25 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitInclusiveMut final [[sus_no_unique_address]] ::sus::iter::IterRef ref_; SliceMut v_; - ::sus::fn::FnMutRef pred_; + Pred pred_; bool finished_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(ref_), - decltype(v_), decltype(pred_), - decltype(finished_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(ref_), decltype(v_), + decltype(pred_), + decltype(finished_)); }; /// An iterator over subslices separated by elements that match a predicate /// function, starting from the end of the slice. /// /// This struct is created by the `rsplit()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] RSplit final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] RSplit final + : public ::sus::iter::IteratorBase, ::sus::collections::Slice> { public: - // `Item` is a `Slice`. - using Item = typename Split::Item; + using Item = ::sus::collections::Slice; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next_back(); } @@ -571,25 +561,25 @@ class [[nodiscard]] [[sus_trivial_abi]] RSplit final constexpr Option finish() noexcept { return inner_.finish(); } - constexpr RSplit(Split&& split) noexcept + constexpr RSplit(Split&& split) noexcept : inner_(::sus::move(split)) {} - Split inner_; + Split inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; /// An iterator over the subslices of the vector which are separated by elements /// that match pred, starting from the end of the slice. /// /// This struct is created by the `rsplit_mut()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] RSplitMut final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] RSplitMut final + : public ::sus::iter::IteratorBase, ::sus::collections::SliceMut> { public: - // `Item` is a `SliceMut`. - using Item = typename SplitMut::Item; + using Item = ::sus::collections::SliceMut; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next_back(); } @@ -617,25 +607,25 @@ class [[nodiscard]] [[sus_trivial_abi]] RSplitMut final Option finish() noexcept { return inner_.finish(); } - constexpr RSplitMut(SplitMut&& split) noexcept + constexpr RSplitMut(SplitMut&& split) noexcept : inner_(::sus::move(split)) {} - SplitMut inner_; + SplitMut inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; /// An iterator over subslices separated by elements that match a predicate /// function, limited to a given number of splits. /// /// This struct is created by the `splitn()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] SplitN final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] SplitN final + : public ::sus::iter::IteratorBase, ::sus::collections::Slice> { public: - // `Item` is a `Slice`. - using Item = typename Split::Item; + using Item = ::sus::collections::Slice; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next(); } @@ -654,25 +644,25 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitN final template friend class Array; - constexpr SplitN(Split split, usize n) noexcept + constexpr SplitN(Split split, usize n) noexcept : inner_(::sus::move(split), n) {} - __private::GenericSplitN, Split> inner_; + __private::GenericSplitN, Split> inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; /// An iterator over mutable subslices separated by elements that match a /// predicate function, limited to a given number of splits. /// /// This struct is created by the `splitn_mut()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] SplitNMut final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] SplitNMut final + : public ::sus::iter::IteratorBase, ::sus::collections::SliceMut> { public: - // `Item` is a `SliceMut`. - using Item = typename SplitMut::Item; + using Item = ::sus::collections::SliceMut; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next(); } @@ -691,12 +681,13 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitNMut final template friend class Array; - constexpr SplitNMut(SplitMut split, usize n) noexcept + constexpr SplitNMut(SplitMut split, usize n) noexcept : inner_(::sus::move(split), n) {} - __private::GenericSplitN, SplitMut> inner_; + __private::GenericSplitN, SplitMut> inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; /// An iterator over subslices separated by elements that match a predicate @@ -704,13 +695,12 @@ class [[nodiscard]] [[sus_trivial_abi]] SplitNMut final /// slice. /// /// This struct is created by the `rsplitn()` method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] RSplitN final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] RSplitN final + : public ::sus::iter::IteratorBase, ::sus::collections::Slice> { public: - // `Item` is a `Slice`. - using Item = typename RSplit::Item; + using Item = ::sus::collections::Slice; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next(); } @@ -729,12 +719,13 @@ class [[nodiscard]] [[sus_trivial_abi]] RSplitN final template friend class Array; - constexpr RSplitN(RSplit split, usize n) noexcept + constexpr RSplitN(RSplit split, usize n) noexcept : inner_(::sus::move(split), n) {} - __private::GenericSplitN, RSplit> inner_; + __private::GenericSplitN, RSplit> inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; /// An iterator over subslices separated by elements that match a predicate @@ -742,13 +733,13 @@ class [[nodiscard]] [[sus_trivial_abi]] RSplitN final /// slice. /// /// This struct is created by the rsplitn_mut method on slices. -template -class [[nodiscard]] [[sus_trivial_abi]] RSplitNMut final - : public ::sus::iter::IteratorBase, +template Pred> +class [[nodiscard]] RSplitNMut final + : public ::sus::iter::IteratorBase, ::sus::collections::SliceMut> { public: // `Item` is a `SliceMut`. - using Item = typename RSplitMut::Item; + using Item = ::sus::collections::SliceMut; // sus::iter::Iterator trait. constexpr Option next() noexcept { return inner_.next(); } @@ -767,12 +758,13 @@ class [[nodiscard]] [[sus_trivial_abi]] RSplitNMut final template friend class Array; - constexpr RSplitNMut(RSplitMut split, usize n) noexcept + constexpr RSplitNMut(RSplitMut split, usize n) noexcept : inner_(::sus::move(split), n) {} - __private::GenericSplitN, RSplitMut> inner_; + __private::GenericSplitN, RSplitMut> inner_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(inner_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(inner_)); }; } // namespace sus::collections diff --git a/sus/collections/slice_unittest.cc b/sus/collections/slice_unittest.cc index 6f291c761..5e0fa3524 100644 --- a/sus/collections/slice_unittest.cc +++ b/sus/collections/slice_unittest.cc @@ -2563,8 +2563,8 @@ TEST(SliceMut, FillWith) { v1["2..4"_r].fill_with(f); EXPECT_EQ(v1[0u], 6); EXPECT_EQ(v1[1u], 7); - EXPECT_EQ(v1[2u], 10); - EXPECT_EQ(v1[3u], 11); + EXPECT_EQ(v1[2u], 6); + EXPECT_EQ(v1[3u], 7); } TEST(SliceMut, FillWithDefault) { From c44fb021f0abeb542c76ebbc89d711549c885ae6 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 03:43:22 -0400 Subject: [PATCH 02/31] Lots of docs improvements for numerics and into --- sus/construct/__private/into_ref.h | 9 +-- sus/construct/into.h | 46 ++++++++------- sus/construct/transmogrify.h | 70 ++++++++++++++--------- sus/mem/forward.h | 12 ++-- sus/num/overflow_integer.h | 49 ++++++++++++---- sus/num/overflow_integer_unittest.cc | 64 +++++++++++---------- sus/num/types.h | 84 +++++++++++++++++++++------- sus/tuple/tuple.h | 2 +- 8 files changed, 218 insertions(+), 118 deletions(-) diff --git a/sus/construct/__private/into_ref.h b/sus/construct/__private/into_ref.h index aeec43836..df267e22b 100644 --- a/sus/construct/__private/into_ref.h +++ b/sus/construct/__private/into_ref.h @@ -17,23 +17,24 @@ #pragma once #include "sus/construct/from.h" +#include "sus/mem/forward.h" namespace sus::construct ::__private { template struct IntoRef final { [[nodiscard]] constexpr IntoRef(FromType&& from) noexcept - : from_(static_cast(from)) {} + : from_(::sus::forward(from)) {} - template > ToType> + template > ToType> constexpr operator ToType() && noexcept { return static_cast(from_); } template <::sus::construct::From ToType> - requires(!std::same_as, ToType>) + requires(!std::same_as, ToType>) constexpr operator ToType() && noexcept { - return ToType::from(static_cast(from_)); + return ToType::from(::sus::forward(from_)); } // Doesn't copy or move. `IntoRef` should only be used as a temporary. diff --git a/sus/construct/into.h b/sus/construct/into.h index eb8d882d7..5789844df 100644 --- a/sus/construct/into.h +++ b/sus/construct/into.h @@ -29,38 +29,44 @@ namespace sus::construct { -/// A concept that declares `FromType` can be converted to `ToType` through the -/// [`From`]($sus::construct::From) concept or through an identity -/// transformation. +/// A concept that declares `FromType` can be converted to `ToType`. /// -/// When true, `ToType::from(FromType)` can be used to construct `ToType`, -/// or `ToType` is the same as `FromType`. +/// When true, the conversion can be done in one of three ways: +/// * `ToType::from(FromType)` can be used to construct `ToType`, which is the +/// preferred way to write conversions. It avoids any accidental conversions +/// as it does not have an ambiguous appearance with a copy or move. +/// * `ToType` is the same as `FromType`, in which case a reference to the +/// object is passed along, and no construction or conversion happens. /// -/// This is the inverse of the [`From`]($sus::construct::From) concept, meant to -/// be used on methods that want to receive any type and which will explicitly -/// convert what they are given type. +/// This is the inverse direction from the [`From`]($sus::construct::From) +/// concept, while also a broader generalization. It is meant to +/// be used on methods that want to receive any type that can explicitly be +/// converted to a specific type. /// /// This concept is not implementable directly, as it's satisfied for `T` by /// implementing the [`From`]($sus::construct::From) concept on a different -/// type. It is only possible to satisfy this concept for `ToType` that is not a +/// type. +/// It is only possible to satisfy this concept for a `ToType` that is not a /// reference, as it needs to be able to construct `ToType`. /// /// # Templates /// /// To receive [`into()`]($sus::construct::into) correctly for a templated /// function argument: -/// * Avoid -/// [`std::same_as`](https://en.cppreference.com/w/cpp/concepts/same_as), use -/// [`std::convertible_to`](https://en.cppreference.com/w/cpp/concepts/convertible_to) -/// instead, as this will -/// accept the marker type returned from [into]($sus::construct::into). +/// * Avoid [`std::same_as`]( +/// https://en.cppreference.com/w/cpp/concepts/same_as), use +/// [`std::convertible_to`]( +/// https://en.cppreference.com/w/cpp/concepts/convertible_to) instead, as +/// this will accept the marker type returned from +/// [into]($sus::construct::into). /// * If the argument is a fixed dependent type, like the following: /// ``` /// template > /// void f(In i) {} /// ``` /// Insert an extra template parameter that uses -/// [`std::convertible_to`](https://en.cppreference.com/w/cpp/concepts/convertible_to) +/// [`std::convertible_to`]( +/// https://en.cppreference.com/w/cpp/concepts/convertible_to) /// and the template type, such as: /// ``` /// template , std::convertible_to In> @@ -141,12 +147,14 @@ concept TryInto = ::sus::construct::TryFrom || /// Attempts to convert from the given value to a `ToType`. /// -/// Unlike `into()`, this function can not use type deduction to determine the -/// receiving type as it needs to determine the Result type and allow the caller -/// the chance to handle the error condition. +/// Unlike [`into()`]($sus::construct::into), this function can not use type +/// deduction to determine the +/// receiving type as it needs to determine the [`Result`]($sus::result::Result) +/// type and allow the caller the chance to handle the error condition. /// /// The `TryFrom` concept requires a `try_from()` method that returns a -/// `Result`. That `Result` will be the return type of this function. +/// [`Result`]($sus::result::Result). That [`Result`]($sus::result::Result) +/// will be the return type of this function. /// /// # Example /// ``` diff --git a/sus/construct/transmogrify.h b/sus/construct/transmogrify.h index 2d74f7c15..802330ed4 100644 --- a/sus/construct/transmogrify.h +++ b/sus/construct/transmogrify.h @@ -61,13 +61,18 @@ struct TransmogrifyImpl { /// conversion may truncate or extend `F` in order to do the conversion to `T`. /// /// This operation is also commonly known as type casting, or type coercion. The -/// conversion to `T` can be done by calling `sus::mog(from)`. +/// conversion to `T` can be done by calling +/// [`sus::mog(from)`]($sus::construct::mog). /// /// The conversion is defined for the identity conversion where both the input -/// and output are the same type, if the type is `Copy`, in which case the input -/// is copied and returned. As Transmogrification is meant to be a cheap -/// conversion, primarily for primitive types, it does not support `Clone` -/// types, and `sus::construct::Into` should be used in more complex cases. +/// and output are the same type, if the type is [`Copy`]($sus::mem::Copy), in +// which case the input is copied and returned. +/// As Transmogrification is meant to be a cheap +/// conversion, primarily for primitive types, it does not support +/// [`Clone`]($sus::mem::Clone) types, and [`Into`]($sus::construct::Into) +/// should be used in more complex cases. +/// +/// # Casting numeric types /// /// For numeric and primitive types, `Transmogrify` is defined to provide a /// mechanism like `static_cast` but it is much safer than `static_cast` @@ -76,33 +81,34 @@ struct TransmogrifyImpl { /// * Casting from a float to an integer will perform a static_cast, which /// rounds the float towards zero, except: /// * `NAN` will return 0. -/// * Values larger than the maximum integer value, including `f32::INFINITY`, +/// * Values larger than the maximum integer value, including +/// [`f32::INFINITY`]($sus::num::f32::INFINITY), /// will saturate to the maximum value of the integer type. /// * Values smaller than the minimum integer value, including -/// `f32::NEG_INFINITY`, will saturate to the minimum value of the integer -/// type. +/// [`f32::NEG_INFINITY`]($sus::num::f32::NEG_INFINITY), will saturate to +/// the minimum value of the integer type. /// * Casting from an integer to a float will perform a static_cast, which /// converts to the nearest floating point value. The rounding direction for /// values that land between representable floating point values is /// implementation defined (per C++20 Section 7.3.10). -/// * Casting from an `f32` (or `float`) to an `f64` (or `double`) preserves the +/// * Casting from an [`f32`]($sus::num::f32) (or `float`) to an +/// [`f64`]($sus::num::f64) (or `double`) preserves the /// value unchanged. -/// * Casting from an `f64` (or `double`) to an `f32` (or float) performs the -/// same action as a static_cast if the value is in range for f32, otherwise: +/// * Casting from an [`f64`]($sus::num::f64) (or `double`) to an +/// [`f32`]($sus::num::f32) (or float) performs the same action as a +/// `static_cast` if the value is in range for [`f32`]($sus::num::f32), +/// otherwise: /// * `NAN` will return a `NAN`. -/// * Values outside of f32's range will return `f32::INFINITY` or -/// `f32::NEG_INFINITY` for positive and negative values respectively. -/// * Casting to and from `std::byte` produces the same values as casting to and -/// from `u8`. +/// * Values outside of [`f32`]($sus::num::f32)'s range will return +/// [`f32::INFINITY`]($sus::num::f32::INFINITY) or +/// [`f32::NEG_INFINITY`]($sus::num::f32::NEG_INFINITY) for positive and +/// negative values respectively. +/// * Casting to and from [`std::byte`]( +/// https://en.cppreference.com/w/cpp/types/byte) produces the same values +/// as casting to and from [`u8`]($sus::num::u8). /// /// These conversions are all defined in `sus/num/types.h`. /// -/// The transmogrifier is one of three of the most complicated inventions. The -/// other two are the [Cerebral -/// Enhance-O-Tron](https://calvinandhobbes.fandom.com/wiki/Cerebral_Enhance-O-Tron), -/// and the [Transmogrifier -/// Gun](https://calvinandhobbes.fandom.com/wiki/Transmogrifier_Gun). -/// /// # Extending to other types /// /// Types can participate in defining their transmogrification strategy by @@ -112,6 +118,14 @@ struct TransmogrifyImpl { /// /// The `Transmogrify` specialization needs a static method `mog_from()` that /// receives `const From&` and returns `To`. +/// +/// # Lore +/// +/// The transmogrifier is one of three of the most complicated inventions. The +/// other two are the [Cerebral +/// Enhance-O-Tron](https://calvinandhobbes.fandom.com/wiki/Cerebral_Enhance-O-Tron), +/// and the [Transmogrifier +/// Gun](https://calvinandhobbes.fandom.com/wiki/Transmogrifier_Gun). template concept Transmogrify = requires(const From& from) { { @@ -126,15 +140,19 @@ concept Transmogrify = requires(const From& from) { /// memory unsafety if used incorrectly. This behaves like `static_cast()` /// but without Undefined Behaviour. /// -/// The `mog` operation is supported for types `To` and `From` that satisfy -/// `Transmogrify`. +/// The [`mog`]($sus::construct::mog) operation is supported for types `To` and +/// `From` that satisfy [`Transmogrify`]( +/// $sus::construct::Transmogrify). /// /// To convert between types while ensuring the values are preserved, use -/// `sus::construct::Into` or `sus::construct::TryInto`. Usually prefer using -/// `sus::into(x)` or `sus::try_into(x)` over `sus::mog(x)` as most code +/// [`Into`]($sus::construct::Into) or [`TryInto`]($sus::construct::TryInto). +/// Usually prefer using [`sus::into(x)`]($sus::construct::into) or +/// [`sus::try_into(x)`]($sus::construct::try_into) over +/// [`sus::mog(x)`]($sus::construct::mog) as most code /// should preserve values across type transitions. /// -/// See `Transmogrify` for how numeric and primitive values are converted. +/// See [`Transmogrify`]($sus::construct::Transmogrify) for how numeric and +/// primitive values are converted. /// /// # Examples /// diff --git a/sus/mem/forward.h b/sus/mem/forward.h index 1e957a4a4..e4c4ff887 100644 --- a/sus/mem/forward.h +++ b/sus/mem/forward.h @@ -21,7 +21,7 @@ namespace sus::mem { -/// Passes a reference as an argument while preserving the reference type. +/// Move from non-reference values but pass through and preserve references. /// /// Typically, passing an rvalue reference will convert it to an lvalue. Using /// `sus::forward(t)` on an rvalue reference `T&& t` will preserve the rvalue @@ -35,11 +35,13 @@ namespace sus::mem { /// /// In the common case, when you want to receive a parameter that will be moved, /// it should be received by value. However, library implementors sometimes with -/// to receive an *rvalue reference*. If you find yourself needing to `move()` -/// from a universal reference instead of `forward()`, such as to construct a +/// to receive an *rvalue reference*. If you find yourself needing to +/// [`move()`]($sus::mem::move) from a universal reference instead of +/// [`forward()`]($sus::mem::forward), such as to construct a /// value type `T` from a universal reference `T&&` without introducing a copy, -/// use `IsMoveRef` to constrain the universal reference to be an rvalue, and -/// use `move()` instead of `forward()`. +/// use [`IsMoveRef`]($sus::mem::IsMoveRef) to constrain the universal reference +/// to be an rvalue, and use [`move()`]($sus::mem::move) instead of +/// [`forward()`]($sus::mem::forward). template sus_pure_const sus_always_inline constexpr T&& forward( std::remove_reference_t& t) noexcept { diff --git a/sus/num/overflow_integer.h b/sus/num/overflow_integer.h index 6a8152e24..7d53d2c15 100644 --- a/sus/num/overflow_integer.h +++ b/sus/num/overflow_integer.h @@ -28,28 +28,53 @@ namespace sus::num { /// An integer type that handles overflow instead of panicing. /// /// The value inside the integer can be accessed or unwrapped like with an -/// `Option`, which will panic if the integer has overflowed. Or it can be -/// converted into an `Option` that will represent the overflow state as `None`. +/// [`Option`]($sus::option::Option), which will panic if the integer has +/// overflowed. Or it can be converted into an [`Option`]($sus::option::Option) +/// that will represent the overflow state as `None`. +/// +/// This type is useful for performing a series of operations as a unit, and +/// then checking for overflow after. It satisfies the +/// [`Sum`]($sus::iter::Sum) and [`Product`]($sus::iter::Product) concepts +/// so can be used with [`sum`]($sus::iter::IteratorBase::sum) and +/// [`product`]($sus::iter::IteratorBase::product) for iterators over integers. +/// +/// # Examples +/// Using OverflowInteger to sum an iterator of integers and look for overflow +/// after without panicking. +/// ``` +/// auto a = sus::Array(2, i32::MAX); +/// auto maybe_answer = +/// a.iter().copied().product>(); +/// sus::check(maybe_answer.is_overflow()); +/// ``` template <::sus::num::Integer I> class OverflowInteger { public: - // Default constructs OverflowInteger with the default value of the inner + // Default constructs `OverflowInteger` with the default value of the inner // integer type `I`. explicit constexpr OverflowInteger() noexcept requires(::sus::construct::Default) : v_(Option(I())) {} - /// Constructs an OverflowInteger from the same subspace integer type. - template U> - explicit constexpr OverflowInteger(U u) noexcept - : v_(Option(::sus::move(u))) {} + /// Constructs an `OverflowInteger` from the same subspace integer type. + /// + /// # Implementation note + /// + /// Because `OverflowInteger` is constructible from a smaller integer, but + /// `I` is also constructible from a smaller integer, which can then be used + /// to construct `OverflowInteger`, on MSVC this conversion becomes ambiguous. + /// So we use `convertible_to` to make the constructor a template, + /// which allows the compiler to choose one. + /// https://developercommunity.visualstudio.com/t/Ambiguous-conversion-with-two-paths-of-d/10461863 + explicit constexpr OverflowInteger(std::convertible_to auto u) noexcept + : v_(Option(sus::move(u))) {} - /// Satisfies `sus::construct::From, U>` if the inner - /// integer type `I` satisfies `sus::construct::From`. + /// Satisfies `sus::construct::From, U>` if the + /// `OverflowInteger` is constructible from `U`. template - requires(::sus::construct::From) + requires(std::constructible_from) sus_pure static constexpr OverflowInteger from(U u) noexcept { - return OverflowInteger(::sus::move_into(u)); + return OverflowInteger(u); } /// Satisfies `sus::construct::TryFrom, U>` if the inner @@ -80,7 +105,7 @@ class OverflowInteger { /// If an iterator yields a subspace integer type, `iter.product()` would /// panic on overflow. So instead `iter.product>()` can be /// used (for integer type `T`) which will perform the product computation and - /// return an OverflowInteger without ever panicking. + /// return an `OverflowInteger` without ever panicking. static constexpr OverflowInteger from_product( ::sus::iter::Iterator auto&& it) noexcept requires(::sus::mem::IsMoveRef) diff --git a/sus/num/overflow_integer_unittest.cc b/sus/num/overflow_integer_unittest.cc index ba4951bd6..490ac02df 100644 --- a/sus/num/overflow_integer_unittest.cc +++ b/sus/num/overflow_integer_unittest.cc @@ -18,6 +18,7 @@ #include "googletest/include/gtest/gtest.h" #include "sus/collections/array.h" +#include "sus/iter/iterator.h" #include "sus/num/signed_integer.h" #include "sus/num/unsigned_integer.h" #include "sus/prelude.h" @@ -101,6 +102,13 @@ TEST(OverflowInteger, TryFrom) { sus::num::TryFromIntError::with_out_of_bounds()); } +TEST(OverflowInteger, Example_Iterator) { + auto a = sus::Array(2, i32::MAX); + auto maybe_answer = + a.iter().copied().product>(); + sus::check(maybe_answer.is_overflow()); // Overflow happened. +} + TEST(OverflowInteger, FromProduct) { static_assert(::sus::iter::Product, i32>); static_assert(::sus::iter::Product>); @@ -113,6 +121,14 @@ TEST(OverflowInteger, FromProduct) { static_assert(std::same_as>); EXPECT_EQ(o.to_option(), sus::None); } + // Reference iterator. + { + auto a = sus::Array(2, i32::MAX); + decltype(auto) o = + a.iter().copied().product>(); + static_assert(std::same_as>); + EXPECT_EQ(o.to_option(), sus::None); + } // To OverflowInteger without overflow. { auto a = sus::Array(2, 4); @@ -131,8 +147,8 @@ TEST(OverflowInteger, FromProduct) { } // Iterating OverflowInteger types without overflow. { - auto a = sus::Array, 2>( - OverflowInteger(2), OverflowInteger(4)); + auto a = sus::Array, 2>(OverflowInteger(2), + OverflowInteger(4)); decltype(auto) o = sus::move(a).into_iter().product(); static_assert(std::same_as>); EXPECT_EQ(o.to_option().unwrap(), 2 * 4); @@ -164,9 +180,8 @@ TEST(OverflowInteger, AsValue) { auto lvalue = OverflowInteger(i32::MAX); EXPECT_EQ(lvalue.as_value_unchecked(unsafe_fn), i32::MAX); EXPECT_EQ(OverflowInteger(i32::MAX).as_value(), i32::MAX); - EXPECT_EQ( - OverflowInteger(i32::MAX).as_value_unchecked(unsafe_fn), - i32::MAX); + EXPECT_EQ(OverflowInteger(i32::MAX).as_value_unchecked(unsafe_fn), + i32::MAX); } } @@ -213,8 +228,7 @@ TEST(OverflowIntegerDeathTest, AsValueMutOverflow) { TEST(OverflowInteger, Unwrap) { EXPECT_EQ(OverflowInteger(i32::MAX).unwrap(), i32::MAX); static_assert( - std::same_as(i32::MAX).unwrap()), - i32>); + std::same_as(i32::MAX).unwrap()), i32>); EXPECT_EQ(OverflowInteger(i32::MAX).unwrap_unchecked(unsafe_fn), i32::MAX); } @@ -225,10 +239,8 @@ TEST(OverflowInteger, ToOption) { lvalue += 1; EXPECT_EQ(lvalue.to_option(), sus::none()); - EXPECT_EQ(OverflowInteger(i32::MAX).to_option(), - sus::some(i32::MAX)); - EXPECT_EQ((OverflowInteger(i32::MAX) + 1).to_option(), - sus::none()); + EXPECT_EQ(OverflowInteger(i32::MAX).to_option(), sus::some(i32::MAX)); + EXPECT_EQ((OverflowInteger(i32::MAX) + 1).to_option(), sus::none()); } TEST(OverflowInteger, MathAssignFromInt) { @@ -445,10 +457,8 @@ TEST(OverflowInteger, Eq) { EXPECT_EQ(OverflowInteger(1) + i32::MAX, OverflowInteger(1) + i32::MAX); - EXPECT_NE(OverflowInteger(5), - OverflowInteger(1) + i32::MAX); - EXPECT_NE(OverflowInteger(1) + i32::MAX, - OverflowInteger(5)); + EXPECT_NE(OverflowInteger(5), OverflowInteger(1) + i32::MAX); + EXPECT_NE(OverflowInteger(1) + i32::MAX, OverflowInteger(5)); } TEST(OverflowInteger, StrongOrd) { @@ -456,17 +466,13 @@ TEST(OverflowInteger, StrongOrd) { static_assert(::sus::ops::StrongOrd, i32>); static_assert(::sus::ops::StrongOrd>); - EXPECT_EQ(OverflowInteger(5) <=> 4_i32, - std::strong_ordering::greater); - EXPECT_EQ(OverflowInteger(5) <=> 6_i32, - std::strong_ordering::less); + EXPECT_EQ(OverflowInteger(5) <=> 4_i32, std::strong_ordering::greater); + EXPECT_EQ(OverflowInteger(5) <=> 6_i32, std::strong_ordering::less); EXPECT_EQ(OverflowInteger(5) <=> 5_i32, std::strong_ordering::equivalent); - EXPECT_EQ(6_i32 <=> OverflowInteger(5), - std::strong_ordering::greater); - EXPECT_EQ(4_i32 <=> OverflowInteger(5), - std::strong_ordering::less); + EXPECT_EQ(6_i32 <=> OverflowInteger(5), std::strong_ordering::greater); + EXPECT_EQ(4_i32 <=> OverflowInteger(5), std::strong_ordering::less); EXPECT_EQ(5_i32 <=> OverflowInteger(5), std::strong_ordering::equivalent); @@ -477,18 +483,16 @@ TEST(OverflowInteger, StrongOrd) { EXPECT_EQ(OverflowInteger(5) <=> OverflowInteger(5), std::strong_ordering::equivalent); - EXPECT_EQ(OverflowInteger(1) + i32::MAX <=> - OverflowInteger(1) + i32::MAX, - std::strong_ordering::equivalent); + EXPECT_EQ( + OverflowInteger(1) + i32::MAX <=> OverflowInteger(1) + i32::MAX, + std::strong_ordering::equivalent); EXPECT_EQ(OverflowInteger(1) + i32::MAX <=> 0_i32, std::strong_ordering::greater); EXPECT_EQ(0_i32 <=> OverflowInteger(1) + i32::MAX, std::strong_ordering::less); - EXPECT_EQ(OverflowInteger(1) + i32::MAX <=> - OverflowInteger(0), + EXPECT_EQ(OverflowInteger(1) + i32::MAX <=> OverflowInteger(0), std::strong_ordering::greater); - EXPECT_EQ(OverflowInteger(0) <=> - OverflowInteger(1) + i32::MAX, + EXPECT_EQ(OverflowInteger(0) <=> OverflowInteger(1) + i32::MAX, std::strong_ordering::less); } diff --git a/sus/num/types.h b/sus/num/types.h index 73cb27e56..00c72f49b 100644 --- a/sus/num/types.h +++ b/sus/num/types.h @@ -18,34 +18,76 @@ namespace sus { -/// Safe integer and floating point numerics, and numeric concepts. +/// Safe integer (e.g. [`i32`]($sus::num::i32)) and floating point +/// (e.g. [`f32`]($sus::num::f32)) numerics, and numeric concepts. /// -/// This namespace contains safe integer types (i8, i16, u32, usize, etc.) and -/// floating point types (f32, f64). +/// This namespace contains safe integer types and floating point types. +/// +/// Safe numeric types: +/// * Signed integers: [`i8`]($sus::num::i8), [`i16`]($sus::num::i16), +/// [`i32`]($sus::num::i32), [`i64`]($sus::num::i64), +/// [`isize`]($sus::num::isize). +/// * Unsigned integers: [`u8`]($sus::num::u8), [`u16`]($sus::num::u16), +/// [`u32`]($sus::num::u32), [`u64`]($sus::num::u64), +/// [`usize`]($sus::num::usize), [`uptr`]($sus::num::uptr). +/// * Floating point: [`f32`]($sus::num::f32), [`f64`]($sus::num::f64). +/// * Portability helper: [`CInt`] /// /// Additionally, there are Concepts that match against safe numerics, C++ /// primitive types, and operations with numeric types. /// +/// The Subspace library numeric types can interoperate with primitive C++ +/// types, but are safer than primitive C++ types and eliminate many classes of +/// bugs that often lead to security vulnerabilities: +/// * Integer overflow is not allowed by default, and will +/// [`panic`]($sus::assertions::panic) to terminate the +/// program. Intentional overflow can be achieved through methods like +/// [`wrapping_add`]($sus::num::i32::wrapping_add) or +/// [`saturating_mul`]($sus::num::i32::saturating_mul). The +/// [`OverflowInteger`]($sus::num::OverflowInteger) type can be used for a +/// series of potentially-overflowing operations and unwraps to an integer +/// value if-and-only-if no overflow has occured. +/// * Integers and floats convert implicitly into each other or into primitive +/// types *only* when no data can be lost, otherwise conversions do not +/// compile. To convert fallibly and observe data loss, use the +/// [`TryFrom`]($sus::construct::TryFrom) concept methods, such as +/// `u32::try_from(3_i32)`. To do casting conversions with truncation, use +/// [`Transmogrify`]($sus::construct::Transmogrify). +/// * No integer promotion. Math on 8-bit and 16-bit integers will not change +/// their type, unlike primitive types which convert to (signed) int on any +/// math operation. +/// * No Undefined Behaviour in conversions. Conversions between all numeric +/// types, and between them and primitive types is well-defined for all +/// possible values, unlike conversions between primitive integer and +/// floating point types which can result in Undefined Behaviour. +/// +/// The numeric types also come with builtin methods to perform common +/// operations, such as [`abs`]($sus::num::i32::abs), +/// [`pow`]($sus::num::i32::pow), [`log10`]($sus::num::i32::log10), or +/// [`leading_ones`]($sus::num::i32::leading_ones). +/// /// # Conversions /// -/// To convert to and from integer values, use -/// [`sus::into`]($sus::construct::into) when -/// [`Into`]($sus::construct::Into) is satisfied between the two -/// types for lossless conversion. Otherwise use -/// [`sus::try_into`]($sus::construct::try_into) when -/// [`TryInto`]($sus::construct::TryInto) is satisfied to convert -/// and handle cases where the value can not be represented in the target type. -/// -/// To convert between floating point types, use -/// [`sus::into(x)`]($sus::construct::into) to losslessly promote `x` to a -/// larger type ([`f32`]($sus::num::f32) to [`f64`]($sus::num::f64)) or -/// `sus::try_into(x)` to convert `x` to a smaller type -/// ([`f64`]($sus::num::f64) to [`f32`]($sus::num::f32)). -/// -/// Use [`sus::mog()`]($sus::construct::mog) to do a lossy type coercion -/// (like `static_cast()`) between integer and floating point types, or C++ -/// primitive integers, floating point, or enums. When converting to a larger -/// signed integer type, the value will be sign-extended. +/// To explicitly invoke a lossless conversion, use +/// [`From`]($sus::construct::From). Use [`Into`]($sus::construct::Into) to +/// constain inputs in generic code, and [`sus::into()`]($sus::construct::into) +/// to type-deduce for conversions. Some lossless conversions are also allowed +/// to happen implicitly, though explicit conversion is better. +/// +/// To convert and handle the case where data is lost, use +/// [`TryFrom`]($sus::construct::TryFrom), or +/// [`TryInto`]($sus::construct::TryInto) in generic code. Using +/// `T::try_from(U).unwrap()` is a quick way to convert and find out if there +/// are values out of range, or to terminate on malicious inputs. Or +/// `T::try_from(U).unwrap_or_default()` to convert to the input value or else +/// to zero. +/// +/// To convert with truncation/loss of data, like `static_cast`, use +/// [`sus::mog()`]($sus::construct::mog). It can convert between +/// integers, floats, and enums, for both safe numerics and primitives. See +/// [Casting numeric types]( +/// $sus::construct::Transmogrify#casting-numeric-types) for the rules of +/// conversion through [`mog`]($sus::construct::mog). namespace num {} } // namespace sus diff --git a/sus/tuple/tuple.h b/sus/tuple/tuple.h index c46df7f2f..89ef670a9 100644 --- a/sus/tuple/tuple.h +++ b/sus/tuple/tuple.h @@ -44,7 +44,7 @@ namespace sus { /// The [`Tuple`]($sus::tuple_type::Tuple) type, and the -/// [`tuple`]($sus::tuple_type::tuple) type-deduction function. +/// [`tuple`]($sus::tuple_type::tuple) type-deduction constructor function. namespace tuple_type {} } // namespace sus From 98335010d549aa940d2699029b94e139eba66e08 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 14:17:52 -0400 Subject: [PATCH 03/31] codify static_cast in comment --- sus/construct/transmogrify.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sus/construct/transmogrify.h b/sus/construct/transmogrify.h index 802330ed4..20ae16ac4 100644 --- a/sus/construct/transmogrify.h +++ b/sus/construct/transmogrify.h @@ -87,7 +87,7 @@ struct TransmogrifyImpl { /// * Values smaller than the minimum integer value, including /// [`f32::NEG_INFINITY`]($sus::num::f32::NEG_INFINITY), will saturate to /// the minimum value of the integer type. -/// * Casting from an integer to a float will perform a static_cast, which +/// * Casting from an integer to a float will perform a `static_cast`, which /// converts to the nearest floating point value. The rounding direction for /// values that land between representable floating point values is /// implementation defined (per C++20 Section 7.3.10). From a588a975b43ff166324b8e14a237d07494aac1b0 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 14:18:04 -0400 Subject: [PATCH 04/31] Call static ctor methods "type-deduction constructor funtions" in docs --- sus/result/result.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sus/result/result.h b/sus/result/result.h index 510cd4c9c..c795c805e 100644 --- a/sus/result/result.h +++ b/sus/result/result.h @@ -47,7 +47,7 @@ namespace sus { /// The [`Result`]($sus::result::Result) type, and the /// [`ok`]($sus::result::ok) and [`err`]($sus::result::err) -/// type-deduction functions. +/// type-deduction constructor functions. namespace result {} } // namespace sus From a12c38a52931213dca81e19fa71d785283689463 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 14:18:34 -0400 Subject: [PATCH 05/31] Replace use of FnOnceRef with FnOnce in Peekable --- sus/iter/adaptors/peekable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sus/iter/adaptors/peekable.h b/sus/iter/adaptors/peekable.h index 0b7270ed2..eb664c65f 100644 --- a/sus/iter/adaptors/peekable.h +++ b/sus/iter/adaptors/peekable.h @@ -74,7 +74,7 @@ class [[nodiscard]] Peekable final /// If `func` returns `true` for the next value of this iterator, consume and /// return it. Otherwise, return `None`. constexpr Option next_if( - ::sus::fn::FnOnceRef&)> + ::sus::fn::FnOnce&)> auto pred) noexcept { Option o = next(); if (o.is_some() && ::sus::fn::call_once(::sus::move(pred), o.as_value())) { From a0244c3720db0cb7eaa2ff239796bf8cfe3f595c Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 14:41:46 -0400 Subject: [PATCH 06/31] Link to fn concepts from the call functions --- sus/fn/fn_concepts.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sus/fn/fn_concepts.h b/sus/fn/fn_concepts.h index fdd7aed80..e47969a9d 100644 --- a/sus/fn/fn_concepts.h +++ b/sus/fn/fn_concepts.h @@ -380,11 +380,11 @@ concept Fn = requires { requires FnOnce; }; -/// Invokes the `FnOnce`, passing any given arguments along, and returning the -/// result. +/// Invokes the [`FnOnce`]($sus::fn::FnOnce), passing any given arguments along, +/// and returning the result. /// /// This function is like -/// [`std::invoke()`](https://en.cppreference.com/w/cpp/utility/functional/invoke) +/// [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) /// but it provides the following additional guiderails: /// * Verifies that the thing being invoked is being moved from so that the /// correct overload will be invoked. @@ -395,11 +395,11 @@ sus_always_inline constexpr decltype(auto) call_once(F&& f, Args&&... args) return std::invoke(sus::move(f), sus::forward(args)...); } -/// Invokes the `FnMut`, passing any given arguments along, and returning the -/// result. +/// Invokes the [`FnMut`]($sus::fn::FnMut), passing any given arguments along, +/// and returning the result. /// /// This function is like -/// [`std::invoke()`](https://en.cppreference.com/w/cpp/utility/functional/invoke) +/// [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) /// but it provides the following additional guiderails: /// * Verifies that the thing being invoked is called as a mutable lvalue so /// that the correct overload will be invoked. @@ -410,11 +410,11 @@ sus_always_inline constexpr decltype(auto) call_mut(F&& f, Args&&... args) { sus::forward(args)...); } -/// Invokes the `Fn`, passing any given arguments along, and returning the -/// result. +/// Invokes the [`Fn`]($sus::fn::Fn), passing any given arguments along, +/// and returning the result. /// /// This function is like -/// [`std::invoke()`](https://en.cppreference.com/w/cpp/utility/functional/invoke) +/// [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) /// but it provides the following additional guiderails: /// * Verifies that the thing being invoked is called as a const lvalue so /// that the correct overload will be invoked. From 3e82b7664c8f4c8166a6e134f6aa5fa9313b16b7 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sat, 9 Sep 2023 14:44:09 -0400 Subject: [PATCH 07/31] Links on fn return type concepts --- sus/fn/fn_concepts.h | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/sus/fn/fn_concepts.h b/sus/fn/fn_concepts.h index e47969a9d..bb1903b0e 100644 --- a/sus/fn/fn_concepts.h +++ b/sus/fn/fn_concepts.h @@ -26,24 +26,33 @@ namespace sus::fn { -/// When used as the return type of the function signature in `Fn`, `FnMut` and -/// `FnOnce`, the concepts will match against any return type from a functor -/// except `void`. +/// When used as the return type of the function signature in +/// [`Fn`]($sus::fn::Fn), [`FnMut`]($sus::fn::FnMut) and +/// [`FnOnce`]($sus::fn::FnOnce), the concepts will match against any return +/// type from a functor except `void`. +/// +/// Use [`Anything`]($sus::fn::Anything) to include `void` as an accepted return +/// type. struct NonVoid { template constexpr NonVoid(T&&) noexcept {} }; -/// When used as the return type of the function signature in `Fn`, `FnMut` and -/// `FnOnce`, the concepts will match against any return type from a functor -/// including `void`. +/// When used as the return type of the function signature in +/// [`Fn`]($sus::fn::Fn), [`FnMut`]($sus::fn::FnMut) and +/// [`FnOnce`]($sus::fn::FnOnce), the concepts will match against any return +/// type from a functor including `void`. +/// +/// Use [`NonVoid`]($sus::fn::NonVoid) to exclude `void` as an accepted return +/// type. struct Anything { template constexpr Anything(T&&) noexcept {} }; -/// The version of a callable object that is called on an rvalue (moved-from) -/// receiver. A `FnOnce` is typically the best fit for any callable that will +/// The version of a callable object that may be called only once. +/// +/// A `FnOnce` is typically the best fit for any callable that will /// only be called at most once. However when a template (or constexpr) is not /// required, receiving a `FnOnceRef` instead of `FnOnce auto&&` will typically /// produce less code by using type erasure to avoid template instantiation. @@ -154,7 +163,9 @@ concept FnOnce = requires { }; /// The version of a callable object that is allowed to mutate internal state -/// and may be called multiple times. A `FnMut` is typically the best fit for +/// and may be called multiple times. +/// +/// A `FnMut` is typically the best fit for /// any callable that may be called one or more times. However when a template /// (or constexpr) is not required, receiving a `FnMutRef` instead of `FnMut /// auto` will typically produce less code by using type erasure to avoid @@ -268,7 +279,9 @@ concept FnMut = requires { }; /// The version of a callable object that may be called multiple times without -/// mutating internal state. A `Fn` is useful for a callable that is received as +/// mutating internal state. +/// +/// A `Fn` is useful for a callable that is received as /// a const reference to indicate it and may be called one or more times and /// does not change between call. However when a template (or constexpr) is not /// required, receiving a `FnRef` instead of `const Fn auto&` will typically From a6a6c56a44eb75273c0e2b0dfc2e49ff8e754c46 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 02:48:22 -0400 Subject: [PATCH 08/31] Add DynConcept, Dyn, and make DynError satisfy DynConcept Generalize Box::from(Error auto) to work with any DynConcept type erasure, with Box::from(C auto). --- sus/CMakeLists.txt | 2 + sus/boxed/box.h | 44 ++-- sus/boxed/dyn.h | 393 ++++++++++++++++++++++++++++++++++++ sus/boxed/dyn_unittest.cc | 327 ++++++++++++++++++++++++++++++ sus/error/error.h | 46 +++-- sus/error/error_unittest.cc | 13 +- 6 files changed, 794 insertions(+), 31 deletions(-) create mode 100644 sus/boxed/dyn.h create mode 100644 sus/boxed/dyn_unittest.cc diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index 9f84a8a60..f96a28fc0 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(subspace PUBLIC "assertions/panic.cc" "assertions/unreachable.h" "boxed/box.h" + "boxed/dyn.h" "choice/__private/all_values_are_unique.h" "choice/__private/index_of_value.h" "choice/__private/index_type.h" @@ -252,6 +253,7 @@ if(${SUBSPACE_BUILD_TESTS}) "assertions/panic_unittest.cc" "assertions/unreachable_unittest.cc" "boxed/box_unittest.cc" + "boxed/dyn_unittest.cc" "choice/choice_types_unittest.cc" "choice/choice_unittest.cc" "construct/transmogrify_unittest.cc" diff --git a/sus/boxed/box.h b/sus/boxed/box.h index 9f5d795f0..61176a574 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -36,6 +36,14 @@ namespace sus::boxed { // TODO: Box has an allocator parameter in Rust but std::unique_ptr does not. // Which do we do? + +/// A heap allocated object. +/// +/// # Box implements some concepts for its inner type +/// For some concepts in the Subspace library, if `T` satisfies the concept, +/// then `Box` will as well, forwarding through to the inner heap-allocated +/// object. Those concepts are: +/// * [`Error`]($sus::error::Error). template class [[sus_trivial_abi]] Box final { static_assert(!std::is_reference_v, "Box of a reference is not allowed."); @@ -168,24 +176,29 @@ class [[sus_trivial_abi]] Box final { return t_; } - /// Satisfies the [`From`]($sus::construct::From) concept for - /// [`Box`]($sus::boxed::Box)`<`[`DynError`]($sus::error::DynError)`>` when - /// `E` satisfies [`Error`]($sus::error::Error). This conversion moves and - /// type-erases `E` into a heap-alloocated - /// [`DynError`]($sus::error::DynError). + /// For a type-erased `DynC` of a concept `C`, `Box` can be + /// constructed from a type that satisfies `C`. /// - /// #[doc.overloads=dynerror.from.error] - template <::sus::error::Error E> - requires(sus::mem::Move) - constexpr static Box from(E e) noexcept - requires(std::same_as) + /// This satisfies the [`From`]($sus::construct::From) concept for + /// constructing `Box` from any type that satisfies the concept `C`. It + /// allows returning a non-templated type satisfying a concept. + /// + /// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of + /// concept-satisfying types. + template + constexpr static Box from(U u) noexcept + requires(std::same_as> && // + DynConcept && // + T::template SatisfiesConcept) { - using HeapError = ::sus::error::DynErrorTyped; - auto* heap_e = new HeapError(::sus::move(e)); - // This implicitly upcasts to the DynError. - return Box(FROM_POINTER, heap_e); + using DynTyped = T::template DynTyped; + // This implicitly upcasts to the `DynConcept` type `T`. + return Box(FROM_POINTER, new DynTyped(::sus::move(u))); } + /// A `Box` can be constructed from a string, which gets type-erased + /// into a type that satisfies [`Error`]($sus::error::Error). + /// Satisfies the [`From`]($sus::construct::From) concept for /// [`Box`]($sus::boxed::Box)`<`[`DynError`]($sus::error::DynError)`>`. This /// conversion moves and type-erases the `std::string` into a heap-alloocated @@ -195,7 +208,8 @@ class [[sus_trivial_abi]] Box final { constexpr static Box from(std::string s) noexcept requires(std::same_as) { - using HeapError = ::sus::error::DynErrorTyped<__private::StringError>; + using HeapError = ::sus::error::DynErrorTyped<__private::StringError, + __private::StringError>; auto* heap_e = new HeapError(__private::StringError(::sus::move(s))); // This implicitly upcasts to the DynError. return Box(FROM_POINTER, heap_e); diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h new file mode 100644 index 000000000..dcc4c8625 --- /dev/null +++ b/sus/boxed/dyn.h @@ -0,0 +1,393 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "sus/macros/lifetimebound.h" +#include "sus/mem/forward.h" + +namespace sus::boxed { + +/// A concept for generalized type erasure of concepts, allowing use of a +/// concept-satisfying type `T` without knowing the concrete type `T`. +/// +/// By providing a virtual type-erasure class that satisifies `DynConcept` along +/// with a concept `C`, it allows the use of generic concept-satisfying objects +/// without the use of templates. +/// This means a function accepting a concept-satisying object as +/// a parameter can: +/// * Be written outside of the header. +/// * Be a virtual method. +/// * Be part of a non-header-only library API. +/// +/// We can not specify a concept as a type parameter so there is a level of +/// indirection involved and the concept itself is not named. +/// +/// To type-erase a concept `C`, there must be a virtual class `DynC` declared +/// that satisfies this `DynConcept` concept for all concrete types `ConcreteT` +/// which satisfy the concept `C` being type-erased. +/// +/// # Performing the type erasure +/// +/// To type-erase a concept-satisfying object into the heap, use +/// [`Box`]($sus::boxed::Box), such as `Box` to hold a type-erased +/// heap-allocated object that is known to satisfy the concept `C`. +/// +/// Then, `Box` will come with a method +/// [`from`]($sus::boxed::Box::from!dync) that lifts any object satisfying the +/// concept `C` into the heap and type-erases it to `DynC`. This implies that +/// [`Box`]($sus::boxed::Box) will satisfy the +/// [`From`]($sus::construct::From) concept, and can be constructed with type +/// deduction through [`sus::into()`]($sus::construct::into) or +/// explicitly with `Box::from(sus::move(satisfies_c))`. +/// +/// To type-erase a concept-satisfying object into a `DynC` reference without +/// heap allocation, use [`dyn`]($sus::boxed::dyn), such as `dyn(x)` to +/// construct a type-erased reference to an object `x` that is known to satisfy +/// the concept `C`. +/// +/// The [`dyn`]($sus::boxed::dyn) function, and its returned type should only +/// appear in function call arguments. The returned type +/// [`Dyn`]($sus::boxed::Dyn) can not be moved, and it only converts a +/// reference to a `ConcreteT` into a reference to `DynC`. +/// This can be used to call functions that accept a type-erased concept +/// by reference, such as with a `const DynC&` parameter. +/// +/// # Type erasure of concepts in the Subspace library +/// +/// Some concepts in the Subspace library come with a virtual type-erasure class +/// that satisfies `DynConcept` and can be type-erased into `Box` for the +/// concept `C`: +/// * [`Error`]($sus::error::Error) +/// * [`Fn`]($sus::fn::Fn) +/// * [`FnMut`]($sus::fn::FnMut) +/// * [`FnOnce`]($sus::fn::FnOnce) +/// +/// For some concepts in the Subspace library, `Box` will also satisfy the +/// concept `C` itself, without having use the inner type. See +/// [Box implements some concepts for its inner type]( +/// $sus::boxed::Box#box-implements-some-concepts-for-its-inner-type). +/// +/// Since the actual type is erased, it can not be moved or copied. While it can +/// be constructed on the stack or the heap, any access to it other than its +/// initial declaration must be through a pointer or reference, similar to +/// [`Pin`](https://doc.rust-lang.org/std/pin/struct.Pin.html) types in +/// Rust. +/// +/// # Examples +/// +/// Providing the mechanism to type erase objects that satisfy a concept named +/// `MyConcept` through a `DynMyConcept` class: +/// ``` +/// // A concept which requires a single const-access method named `concept_fn`. +/// template +/// concept MyConcept = requires(const T& t) { +/// { t.concept_fn() } -> std::same_as; +/// }; +/// +/// template +/// class DynMyConceptTyped; +/// +/// class DynMyConcept { +/// sus_dyn_concept(MyConcept, DynMyConcept, DynMyConceptTyped); +/// +/// public: +/// // Pure virtual concept API. +/// virtual void concept_fn() const = 0; +/// }; +/// // Verifies that DynMyConcept also satisfies MyConcept, which is required. +/// static_assert(MyConcept); +/// +/// template +/// class DynMyConceptTyped final : public DynMyConcept { +/// sus_dyn_concept_typed(MyConcept, DynMyConcept, DynMyConceptTyped, v); +/// +/// // Virtual concept API implementation. +/// void concept_fn() const override { return v.concept_fn(); }; +/// }; +/// +/// // A type which satiesfies `MyConcept`. +/// struct MyConceptType { +/// void concept_fn() const {} +/// }; +/// +/// int main() { +/// // Verifies that DynMyConcept is functioning correctly, testing it against +/// // a type that satisfies MyConcept. +/// static_assert(sus::boxed::DynConcept); +/// +/// auto b = [](Box c) { c->concept_fn(); }; +/// // `Box` constructs from `MyConceptType`. +/// b(sus::into(MyConceptType())); +/// +/// auto d = [](const DynMyConcept& c) { c.concept_fn(); }; +/// // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`. +/// d(sus::dyn(MyConceptType())); +/// } +/// ``` +/// +/// An identical example to above, with a `DynMyConcept` class providing type +/// erasure for the `MyConcept` concept, however without the use of the helper +/// macros, showing all the required machinery: +/// ``` +/// // A concept which requires a single const-access method named `concept_fn`. +/// template +/// concept MyConcept = requires(const T& t) { +/// { t.concept_fn() } -> std::same_as; +/// }; +/// +/// template +/// class DynMyConceptTyped; +/// +/// class DynMyConcept { +/// public: +/// // Pure virtual concept API. +/// virtual void concept_fn() const = 0; +/// template +/// +/// static constexpr bool SatisfiesConcept = MyConcept; +/// template +/// using DynTyped = DynMyConceptTyped; +/// +/// DynMyConcept() = default; +/// virtual ~DynMyConcept() = default; +/// DynMyConcept(DynC&&) = delete; +/// DynMyConcept& operator=(DynMyConcept&&) = delete; +/// }; +/// // Verifies that DynMyConcept also satisfies MyConcept, which is required. +/// static_assert(MyConcept); +/// +/// template +/// class DynMyConceptTyped final : public DynMyConcept { +/// public: +/// // Virtual concept API implementation. +/// void concept_fn() const override { return c_.concept_fn(); }; +/// +/// constexpr DynMyConceptTyped(Store&& c) : c_(::sus::forward(c)) {} +/// +/// private: +/// Store c_; +/// }; +/// +/// // A type which satiesfies `MyConcept`. +/// struct MyConceptType { +/// void concept_fn() const {} +/// }; +/// +/// int main() { +/// // Verifies that DynMyConcept is functioning correctly, testing it against +/// // a type that satisfies MyConcept. +/// static_assert(sus::boxed::DynConcept); +/// +/// auto b = [](Box c) { c->concept_fn(); }; +/// // `Box` constructs from `MyConceptType`. +/// b(sus::into(MyConceptType())); +/// +/// auto d = [](const DynMyConcept& c) { c.concept_fn(); }; +/// // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`. +/// d(sus::dyn(MyConceptType())); +/// } +/// ``` +template +concept DynConcept = requires { + // The types are not qualified or references. + requires std::same_as>; + requires std::same_as>; + + // The SatisfiesConcept bool tests against the concept. + { DynC::template SatisfiesConcept } -> std::same_as; + // The `DynTyped` type alias names the typed subclass. The `DynTyped` class + // has two template parameters, the concrete type and the storage type (value + // or reference). + typename DynC::template DynTyped; + + // The type-erased `DynC` must also satisfy the concept, so it can be used + // in templated code still as well. + requires DynC::template SatisfiesConcept; + + // The typed class is a subclass of the type-erased `DynC` base class, and is + // final. + requires std::is_base_of_v< + DynC, typename DynC::template DynTyped>; + requires std::is_final_v< + typename DynC::template DynTyped>; + + // The type-erased `DynC` can not be moved (which would slice the typed + // subclass off). + requires !std::is_move_constructible_v; + requires !std::is_move_assignable_v; +}; + +/// Type erases a reference to a `ConcreteT` which satisfies a concept `C`, +/// into a reference to `DynC`. Returned from [`dyn`]($sus::boxed::dyn). +/// +/// Use [`dyn`]($sus::boxed::dyn) to convert to a `DynC` reference instead of +/// constructing this type directly. +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. +template +class [[nodiscard]] Dyn { + public: + static_assert(std::same_as>, + "DynC can be const-qualified but not a reference"); + static_assert(std::same_as>, + "ConcreteT can not be qualified or a reference"); + + static_assert( + DynC::template SatisfiesConcept, + "ConcreteT must satisfy the concept that DynC type-erases for."); + + /// Construct a `Dyn` from a mutable reference to `T`, which will + /// vend a mutable reference `DynC&`. + /// + /// #[doc.overloads=mut] + Dyn(ConcreteT& concrete sus_lifetimebound) + requires(!std::is_const_v) + : dyn_(concrete) {} + /// #[doc.overloads=mut] + Dyn(ConcreteT&& concrete sus_lifetimebound) + requires(!std::is_const_v) + : dyn_(concrete) {} + /// Construct a `Dyn` from a reference to `T`, which will + /// vend a const reference `const DynC&`. + /// + /// #[doc.overloads=const] + Dyn(const ConcreteT& concrete sus_lifetimebound) + requires(std::is_const_v) + // This drops the const qualifier on `concrete` however we have a const + // qualifier on `DynC` (checked by the requires clause on this + // constructor) which prevents the `concrete` from being accessed in a + // non-const way through the `operator DynC&` overloads. + : dyn_(const_cast(concrete)) {} + + /// Converts the reference to `ConcreteT` into a `DynC` reference. + operator const DynC&() const&& { return dyn_; } + operator DynC&() && + requires(!std::is_const_v) + { + return dyn_; + } + + /// `Dyn` can not be moved. + /// + /// `Dyn` only exists as a temporary to convert a + /// concrete reference to a concept type to into a type-erased reference. + Dyn(Dyn&&) = delete; + /// `Dyn` can not be moved. + /// + /// `Dyn` only exists as a temporary to convert a + /// concrete reference to a concept type to into a type-erased reference. + Dyn& operator=(Dyn&&) = delete; + + private: + /// The typed subclass of `DynC` which holds the reference to `ConcreteT`. + typename DynC::template DynTyped dyn_; +}; + +/// Type erases a reference to a `ConcreteT` which satisfies a concept `C`, +/// into a reference to `DynC`. +/// +/// Use `dyn(x)` to convert a mutable reference to `x` into `DynC&` and +/// `dyn(x)` to convert a const or mutable reference to `x` into +/// `const Dyn&`. +/// +/// Type erasure into `DynC` allows calling a method that receives a `DynC` +/// reference, such as `const DynC&`, without requiring a heap allocation into +/// a `Box`. +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. +template + requires(std::same_as> && // + std::same_as>) +constexpr Dyn dyn(ConcreteT& t sus_lifetimebound) noexcept { + return Dyn(t); +} +template + requires(std::same_as> && // + std::same_as> && // + std::is_rvalue_reference_v) +constexpr Dyn dyn(ConcreteT&& t sus_lifetimebound) noexcept { + return Dyn(t); +} +template + requires(std::same_as> && + std::same_as> && + std::is_const_v) +constexpr Dyn dyn( + const ConcreteT& t sus_lifetimebound) noexcept { + return Dyn(t); +} + +} // namespace sus::boxed + +// Promote `dyn` to the top `sus` namespace. +namespace sus { +using sus::boxed::dyn; +} + +/// Macro to help implement `DynC` for a concept `C`. The macro is placed in the +/// body of the `DynC` class. +/// +/// Here `DynC` is used as a placeholder name to refer to the virtual class +/// that type-erases for the concept `C`. The type erasure class is typically +/// named to match the concept, with a "Dyn" prefix. The type-aware subclass +/// of the type erasure class is typically named to match the concept with a +/// "Dyn" prefix and a "Typed" suffix. +/// +/// The `Concept` parameter is the concept `C` for which types are being +/// type-erased. +/// +/// The `DynConcept` parameter is the name of the type-erasure +/// class `DynC` which the macro is written within, and which has a pure virtual +/// interface matching the concept's requirements. +/// +/// The `DynConceptTyped` +/// parameter is the type-aware subclass of `DynC` which contains the +/// `sus_dyn_concept_typed` macro in its body, and the +/// implementation of the virtual interface that forwards calls through to the +/// concrete type. +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types, and [DynConcept examples]( +/// $sus::boxed::DynConcept#examples) for examples of using the macro. +#define sus_dyn_concept(Concept, DynConcept, DynConceptTyped) \ + public: \ + template \ + static constexpr bool SatisfiesConcept = Concept; \ + template \ + using DynTyped = DynConceptTyped; \ + \ + DynConcept() = default; \ + virtual ~DynConcept() = default; \ + DynConcept(DynConcept&&) = delete; \ + DynConcept& operator=(DynConcept&&) = delete + +/// Macro to help implement `DynCTyped` for a concept `C`. The macro is placed +/// in the body of the `DynCTyped` class. +/// +/// See the TODO: link [`sus_dyn_concept`] macro for more, and +/// [DynConcept examples]($sus::boxed::DynConcept#examples) for examples +/// of using the macro. +#define sus_dyn_concept_typed(Concept, DynConcept, DynConceptTyped, VarName) \ + public: \ + static_assert(Concept); \ + constexpr DynConceptTyped(Store&& c) : VarName(::sus::forward(c)) {} \ + \ + private: \ + Store VarName; diff --git a/sus/boxed/dyn_unittest.cc b/sus/boxed/dyn_unittest.cc new file mode 100644 index 000000000..5cd997924 --- /dev/null +++ b/sus/boxed/dyn_unittest.cc @@ -0,0 +1,327 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sus/boxed/dyn.h" + +#include "googletest/include/gtest/gtest.h" +#include "sus/boxed/box.h" +#include "sus/prelude.h" + +namespace { +using namespace sus::boxed; + +/// Some concept which requires two functions. +template +concept C = requires(const T& c, T& m) { + { c.concept_fn() } -> std::same_as; + { m.concept_fn_mut() } -> std::same_as; +}; + +template +struct DynCTyped; + +/// The C concept when type-erased. +struct DynC { + template + static constexpr bool SatisfiesConcept = C; + template + using DynTyped = DynCTyped; + + DynC() = default; + virtual ~DynC() = default; + DynC(DynC&&) = delete; + DynC& operator=(DynC&&) = delete; + + // Virtual concept API. + virtual i32 concept_fn() const = 0; + virtual i32 concept_fn_mut() = 0; +}; + +/// DynC satisfies concept C. +static_assert(C); + +/// The implementation of type-erasure for the C concept. +template +struct DynCTyped final : public DynC { + constexpr DynCTyped(Store&& c) : c_(sus::forward(c)) {} + + constexpr i32 concept_fn() const override { return c_.concept_fn(); } + constexpr i32 concept_fn_mut() override { return c_.concept_fn_mut(); } + + private: + Store c_; +}; + +/// Foo satisfies concept C. +struct Foo final { + i32 concept_fn() const noexcept { return called_const += 1, called_const; } + i32 concept_fn_mut() noexcept { return called_mut += 1, called_mut; } + + mutable i32 called_const; + i32 called_mut; +}; +static_assert(C); + +/// These act on the `C` concept but without being templated. +i32 GiveC(const DynC& c) { return c.concept_fn(); } +i32 GiveCMut(DynC& c) { return c.concept_fn_mut(); } +Box GiveBoxC(Box c) { return c; } + +/// `DynC` satisfies `DynConcept` with an implementation of `C` (which is `Foo` +/// here). +static_assert(sus::boxed::DynConcept); + +TEST(Dyn, Box) { + // Box::from(C) exists since DynC exists and satisfies `DynConcept` for `C`. + { + static_assert(sus::construct::From, Foo>); + auto b = Box::from(Foo()); + EXPECT_EQ(b->concept_fn(), 1); + EXPECT_EQ(b->concept_fn(), 2); + EXPECT_EQ(b->concept_fn_mut(), 1); + const auto bc = sus::move(b); + EXPECT_EQ(bc->concept_fn(), 3); + } + // Into works. + { + static_assert(sus::construct::Into>); + auto b = GiveBoxC(sus::into(Foo())); + EXPECT_EQ(b->concept_fn(), 1); + EXPECT_EQ(b->concept_fn(), 2); + EXPECT_EQ(b->concept_fn_mut(), 1); + } +} + +template +concept CanGiveCMutFromStruct = requires(Ref r) { + { GiveCMut(Dyn(r)) }; +}; +template +concept CanGiveCFromStruct = requires(Ref r) { + { GiveC(Dyn(r)) }; +}; + +// Tests the semantics of the struct itself, though it's not used directly. +TEST(Dyn, DynStruct) { + // Mutable. + { + Foo f; + + static_assert(CanGiveCFromStruct); + static_assert(CanGiveCMutFromStruct); + static_assert(CanGiveCFromStruct); + // Can't give a const `DynC` to a mutable reference `DynC&`. + static_assert(!CanGiveCMutFromStruct); + + EXPECT_EQ(GiveC(Dyn(f)), 1); + EXPECT_EQ(GiveC(Dyn(f)), 2); + EXPECT_EQ(GiveCMut(Dyn(f)), 1); + EXPECT_EQ(GiveC(Dyn(f)), 3); + EXPECT_EQ(GiveC(Dyn(f)), 4); + // ERROR: Can't give a const `DynC` to a mutable reference `DynC&`. + // GiveCMut(Dyn(f)); + } + // Const. + { + const Foo f; + + // Can't use a const `Foo` to construct a mutable `DynC`. + static_assert(!CanGiveCFromStruct); + // Can't use a const `Foo` to construct a mutable `DynC&`. + static_assert(!CanGiveCMutFromStruct); + static_assert(CanGiveCFromStruct); + // Can't give a const `DynC` to a mutable reference `DynC&`. + static_assert(!CanGiveCMutFromStruct); + + EXPECT_EQ(GiveC(Dyn(f)), 1); + EXPECT_EQ(GiveC(Dyn(f)), 2); + // ERROR: Can't use a const `Foo` to construct a mutable `DynC`. + // GiveC(Dyn(f)); + // GiveCMut(Dyn(f)); + // ERROR: Can't give a const `DynC` to a mutable reference `DynC&`. + // GiveCMut(Dyn(f)); + } + + // ERROR: Holding a reference to a temporary (on clang only). + // TODO: How to test this with a concept? + // [[maybe_unused]] auto x = Dyn(Foo()); +} + +template +concept CanGiveCMut = requires(Ref r) { + { GiveCMut(sus::dyn(r)) }; +}; +template +concept CanGiveC = requires(Ref r) { + { GiveC(sus::dyn(r)) }; +}; + +TEST(Dyn, DynFunction) { + // Mutable. + { + Foo f; + + static_assert(CanGiveC); + static_assert(CanGiveCMut); + static_assert(CanGiveC); + // Can't give a const `DynC` to a mutable reference `DynC&`. + static_assert(!CanGiveCMut); + + EXPECT_EQ(GiveC(sus::dyn(f)), 1); + EXPECT_EQ(GiveC(sus::dyn(f)), 2); + EXPECT_EQ(GiveCMut(sus::dyn(f)), 1); + EXPECT_EQ(GiveC(sus::dyn(f)), 3); + EXPECT_EQ(GiveC(sus::dyn(f)), 4); + // ERROR: Can't give a const `DynC` to a mutable reference `DynC&`. + // GiveCMut(sus::dyn(f)); + } + // Const. + { + const Foo f; + + // Can't use a const `Foo` to construct a mutable `DynC`. + static_assert(!CanGiveC); + // Can't use a const `Foo` to construct a mutable `DynC&`. + static_assert(!CanGiveCMut); + static_assert(CanGiveC); + // Can't give a const `DynC` to a mutable reference `DynC&`. + static_assert(!CanGiveCMut); + + EXPECT_EQ(GiveC(sus::dyn(f)), 1); + EXPECT_EQ(GiveC(sus::dyn(f)), 2); + // ERROR: Can't use a const `Foo` to construct a mutable `DynC`. + // GiveC(sus::dyn(f)); + // GiveCMut(sus::dyn(f)); + // ERROR: Can't give a const `DynC` to a mutable reference `DynC&`. + // GiveCMut(sus::dyn(f)); + } + + // ERROR: Holding a reference to a temporary (on clang only). + // TODO: How to test this with a concept? + // [[maybe_unused]] auto x = sus::dyn(Foo()); +} + +namespace example_no_macro { + +// A concept which requires a single const-access method named `concept_fn`. +template +concept MyConcept = requires(const T& t) { + { t.concept_fn() } -> std::same_as; +}; + +template +class DynMyConceptTyped; + +class DynMyConcept { + public: + // Pure virtual concept API. + virtual void concept_fn() const = 0; + template + + static constexpr bool SatisfiesConcept = MyConcept; + template + using DynTyped = DynMyConceptTyped; + + DynMyConcept() = default; + virtual ~DynMyConcept() = default; + DynMyConcept(DynC&&) = delete; + DynMyConcept& operator=(DynMyConcept&&) = delete; +}; +// Verifies that DynMyConcept also satisfies MyConcept, which is required. +static_assert(MyConcept); + +template +class DynMyConceptTyped final : public DynMyConcept { + public: + // Virtual concept API implementation. + void concept_fn() const override { return c_.concept_fn(); }; + + constexpr DynMyConceptTyped(Store&& c) : c_(sus::forward(c)) {} + + private: + Store c_; +}; + +// A type which satiesfies `MyConcept`. +struct MyConceptType { + void concept_fn() const {} +}; + +TEST(Dyn, Example_NoMacro) { + // Verifies that DynMyConcept is functioning correctly, testing it against + // a type that satisfies MyConcept. + static_assert(sus::boxed::DynConcept); + + auto b = [](Box c) { c->concept_fn(); }; + // `Box` constructs from `MyConceptType`. + b(sus::into(MyConceptType())); + + auto d = [](const DynMyConcept& c) { c.concept_fn(); }; + // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`. + d(sus::dyn(MyConceptType())); +} + +} // namespace example_no_macro + +namespace example_macro { + +// A concept which requires a single const-access method named `concept_fn`. +template +concept MyConcept = requires(const T& t) { + { t.concept_fn() } -> std::same_as; +}; + +template +class DynMyConceptTyped; + +class DynMyConcept { + sus_dyn_concept(MyConcept, DynMyConcept, DynMyConceptTyped); + + public: + // Pure virtual concept API. + virtual void concept_fn() const = 0; +}; +// Verifies that DynMyConcept also satisfies MyConcept, which is required. +static_assert(MyConcept); + +template +class DynMyConceptTyped final : public DynMyConcept { + sus_dyn_concept_typed(MyConcept, DynMyConcept, DynMyConceptTyped, v); + + // Virtual concept API implementation. + void concept_fn() const override { return v.concept_fn(); }; +}; + +// A type which satiesfies `MyConcept`. +struct MyConceptType { + void concept_fn() const {} +}; + +TEST(Dyn, Example_Macro) { + // Verifies that DynMyConcept is functioning correctly, testing it against + // a type that satisfies MyConcept. + static_assert(sus::boxed::DynConcept); + + auto b = [](Box c) { c->concept_fn(); }; + // `Box` constructs from `MyConceptType`. + b(sus::into(MyConceptType())); + + auto d = [](const DynMyConcept& c) { c.concept_fn(); }; + // `MyConceptType` converts to `const MyConcept&` with `sus::dyn()`. + d(sus::dyn(MyConceptType())); +} + +} // namespace example_macro + +} // namespace diff --git a/sus/error/error.h b/sus/error/error.h index f739f85d0..573a01244 100644 --- a/sus/error/error.h +++ b/sus/error/error.h @@ -15,6 +15,7 @@ #pragma once #include "fmt/core.h" +#include "sus/boxed/dyn.h" #include "sus/macros/lifetimebound.h" #include "sus/option/option.h" @@ -224,6 +225,9 @@ concept HasErrorSource = requires(const T& t) { /// single type, as does passing error types through virtual methods or dylib /// ABI boundaries. /// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// [`Error`]($sus::error::Error) types. +/// /// ## Opaque erasure /// When an application just wants to return an error without exposing the /// actual type behind it, use the [`DynError`]($sus::error::DynError) type. @@ -239,7 +243,7 @@ concept HasErrorSource = requires(const T& t) { /// `error` that satisfies [`Error`]($sus::error::Error). /// /// This is similar to -/// `&dyn Error` when working with the Rust +/// `Box` when working with the Rust /// [`Error`](https://doc.rust-lang.org/stable/std/error/trait.Error.html) /// trait. However with `DynError`, the error type can be printed/displayed but /// no further information can be extracted from the error. Nonetheless this is @@ -419,7 +423,14 @@ concept Error = requires(const T& t) { }; }; -/// A type-erased [`Error`]($sus::error::Error) object. +template +struct DynErrorTyped; + +/// A type-erased [`Error`]($sus::error::Error) object +/// which satisfies [`DynConcept`]($sus::boxed::DynConcept) +/// for all [`Error`]($sus::error::Error)s. +/// +/// `DynError` also satisfies [`Error`]($sus::error::Error) itself. /// /// Using this allows the error type to be placed in heap-allocated smart /// pointers without templates, and thus without knowing the concrete type. @@ -438,10 +449,22 @@ struct DynError { /// Forwards to the [`Error`]($sus::error::Error) implementation of `E`. virtual sus::Option source() const noexcept = 0; + // `DynConcept` machinery for type-erasing the `Error` concept. + + /// #[doc.hidden] constexpr DynError() = default; + /// #[doc.hidden] constexpr virtual ~DynError() = default; + /// #[doc.hidden] DynError(DynError&&) = delete; + /// #[doc.hidden] DynError&& operator=(DynError&&) = delete; + /// #[doc.hidden] + template + static constexpr bool SatisfiesConcept = Error; + /// #[doc.hidden] + template + using DynTyped = DynErrorTyped; }; /// Gets a string describing the `error` from an [`Error`]($sus::error::Error) @@ -463,10 +486,12 @@ constexpr inline sus::Option error_source( } } -/// The wrapper around an [`Error`]($sus::error::Error) object that allows it -/// to be type-erased as [`DynError`]($sus::error::DynError). -template -struct DynErrorTyped : public DynError { +/// The type-aware subclass of `DynError` for type-erasing an `Error` concept +/// type through `DynConcept`. +/// +/// #[doc.hidden] +template +struct DynErrorTyped final : public DynError { std::string display() const noexcept override { return error_display(error_); } @@ -474,15 +499,10 @@ struct DynErrorTyped : public DynError { return error_source(error_); } - constexpr DynErrorTyped(E&& error) : error_(::sus::move(error)) {} - constexpr ~DynErrorTyped() override = default; - - /// Unwraps and returns the inner error type `E`, discarding the - /// `DynErrorTyped`. - constexpr E into_inner() && noexcept { return ::sus::move(error_); } + constexpr DynErrorTyped(Store&& c) : error_(std::forward(c)) {} private: - E error_; + Store error_; }; } // namespace sus::error diff --git a/sus/error/error_unittest.cc b/sus/error/error_unittest.cc index 0f5998112..e1f286255 100644 --- a/sus/error/error_unittest.cc +++ b/sus/error/error_unittest.cc @@ -88,6 +88,13 @@ static_assert(sus::error::Error); namespace { using sus::error::Error; +using sus::error::DynError; + +// Tests that DynError functions as a DynConcept. +static_assert(sus::boxed::DynConcept); +static_assert(sus::boxed::DynConcept); +static_assert(sus::boxed::DynConcept); +static_assert(sus::boxed::DynConcept); TEST(Error, Example_ToString) { auto err = u32::try_from(-1).unwrap_err(); @@ -96,7 +103,7 @@ TEST(Error, Example_ToString) { TEST(Error, Example_Result) { auto f = - [](i32 i) -> sus::result::Result> { + [](i32 i) -> sus::result::Result> { if (i > 10) return sus::err(sus::into(ErrorReason::SomeReason)); if (i < -10) return sus::err(sus::into(ErrorString("too low"))); return sus::ok(); @@ -145,11 +152,11 @@ TEST(Error, Source) { auto super_error = SuperError{.source = sus::into(SuperErrorSideKick())}; decltype(auto) source = sus::error::error_source(super_error); static_assert( - std::same_as>); + std::same_as>); EXPECT_EQ(source.is_some(), true); decltype(auto) inner_source = sus::error::error_source(source.as_value()); static_assert(std::same_as>); + sus::Option>); EXPECT_EQ(inner_source.is_some(), false); EXPECT_EQ(sus::error::error_display(*source), "SuperErrorSideKick is here!"); From 6ee2017c37607eb9a9238e29a74240187261b0a9 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 04:01:40 -0400 Subject: [PATCH 09/31] Add DynFn type-erased virtual classes --- sus/CMakeLists.txt | 1 + sus/boxed/box.h | 32 ++++++++ sus/boxed/dyn.h | 6 ++ sus/boxed/dyn_unittest.cc | 18 ++++- sus/fn/fn.h | 1 + sus/fn/fn_dyn.h | 161 ++++++++++++++++++++++++++++++++++++++ sus/fn/fn_dyn_unittest.cc | 84 ++++++++++++++++++++ 7 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 sus/fn/fn_dyn.h create mode 100644 sus/fn/fn_dyn_unittest.cc diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index f96a28fc0..6d4d2c2ad 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -279,6 +279,7 @@ if(${SUBSPACE_BUILD_TESTS}) "error/error_unittest.cc" "fn/fn_box_unittest.cc" "fn/fn_concepts_unittest.cc" + "fn/fn_dyn_unittest.cc" "fn/fn_ref_unittest.cc" "iter/compat_ranges_unittest.cc" "iter/empty_unittest.cc" diff --git a/sus/boxed/box.h b/sus/boxed/box.h index 61176a574..bc09f4f69 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -20,6 +20,8 @@ #include "sus/boxed/__private/string_error.h" #include "sus/error/error.h" +#include "sus/fn/fn_concepts.h" +#include "sus/fn/fn_dyn.h" #include "sus/macros/no_unique_address.h" #include "sus/macros/pure.h" #include "sus/mem/clone.h" @@ -270,6 +272,36 @@ class [[sus_trivial_abi]] Box final { return ::sus::mem::replace(t_, nullptr); } + /// Consumes the `Box`, calling `f` with the wrapped value before destroying + /// it. + /// + /// This allows the caller to make use of the wrapped object as an rvalue + /// without moving out of the wrapped object in a way that leaves the Box with + /// a moved-from object within. + template <::sus::fn::FnOnce F> + constexpr ::sus::fn::ReturnOnce consume(F f) && noexcept { + ::sus::check_with_message(t_, "Box used after move"); + ::sus::fn::ReturnOnce ret = + ::sus::fn::call_once(::sus::move(f), sus::move(*t_)); + delete ::sus::mem::replace(t_, nullptr); + return ret; + } + + template + constexpr sus::fn::Return operator()( + Args&&... args) const noexcept + requires( + std::same_as(Args...)>>) + { + ::sus::check_with_message(t_, "Box used after move"); + struct Cleanup { + constexpr ~Cleanup() noexcept { delete t_; } + T* t; + }; + auto cleanup = Cleanup(::sus::mem::replace(t_, nullptr)); + return ::sus::fn::call(*cleanup.t, ::sus::forward(args)...); + } + private: enum FromPointer { FROM_POINTER }; Box(FromPointer, T* t) : t_(t) {} diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h index dcc4c8625..2c2b77f85 100644 --- a/sus/boxed/dyn.h +++ b/sus/boxed/dyn.h @@ -19,6 +19,7 @@ #include "sus/macros/lifetimebound.h" #include "sus/mem/forward.h" +#include "sus/mem/move.h" namespace sus::boxed { @@ -282,6 +283,11 @@ class [[nodiscard]] Dyn { { return dyn_; } + operator DynC&&() && + requires(!std::is_const_v) + { + return ::sus::move(dyn_); + } /// `Dyn` can not be moved. /// diff --git a/sus/boxed/dyn_unittest.cc b/sus/boxed/dyn_unittest.cc index 5cd997924..a0f436243 100644 --- a/sus/boxed/dyn_unittest.cc +++ b/sus/boxed/dyn_unittest.cc @@ -76,6 +76,7 @@ static_assert(C); /// These act on the `C` concept but without being templated. i32 GiveC(const DynC& c) { return c.concept_fn(); } i32 GiveCMut(DynC& c) { return c.concept_fn_mut(); } +i32 GiveCRval(DynC&& c) { return c.concept_fn_mut(); } Box GiveBoxC(Box c) { return c; } /// `DynC` satisfies `DynConcept` with an implementation of `C` (which is `Foo` @@ -103,13 +104,17 @@ TEST(Dyn, Box) { } } +template +concept CanGiveCFromStruct = requires(Ref r) { + { GiveC(Dyn(r)) }; +}; template concept CanGiveCMutFromStruct = requires(Ref r) { { GiveCMut(Dyn(r)) }; }; template -concept CanGiveCFromStruct = requires(Ref r) { - { GiveC(Dyn(r)) }; +concept CanGiveCRvalFromStruct = requires(Ref r) { + { GiveCMut(Dyn(r)) }; }; // Tests the semantics of the struct itself, though it's not used directly. @@ -120,9 +125,12 @@ TEST(Dyn, DynStruct) { static_assert(CanGiveCFromStruct); static_assert(CanGiveCMutFromStruct); + static_assert(CanGiveCRvalFromStruct); static_assert(CanGiveCFromStruct); // Can't give a const `DynC` to a mutable reference `DynC&`. static_assert(!CanGiveCMutFromStruct); + // Can't give a const `DynC` to an rvalue reference `DynC&&` + static_assert(!CanGiveCRvalFromStruct); EXPECT_EQ(GiveC(Dyn(f)), 1); EXPECT_EQ(GiveC(Dyn(f)), 2); @@ -131,6 +139,8 @@ TEST(Dyn, DynStruct) { EXPECT_EQ(GiveC(Dyn(f)), 4); // ERROR: Can't give a const `DynC` to a mutable reference `DynC&`. // GiveCMut(Dyn(f)); + + EXPECT_EQ(GiveCRval(Dyn(Foo())), 1); } // Const. { @@ -140,9 +150,13 @@ TEST(Dyn, DynStruct) { static_assert(!CanGiveCFromStruct); // Can't use a const `Foo` to construct a mutable `DynC&`. static_assert(!CanGiveCMutFromStruct); + // Can't use a const `Foo` to construct an rvalue reference`DynC&&`. + static_assert(!CanGiveCRvalFromStruct); static_assert(CanGiveCFromStruct); // Can't give a const `DynC` to a mutable reference `DynC&`. static_assert(!CanGiveCMutFromStruct); + // Can't give a const `DynC` to an rvalue reference `DynC&&` + static_assert(!CanGiveCRvalFromStruct); EXPECT_EQ(GiveC(Dyn(f)), 1); EXPECT_EQ(GiveC(Dyn(f)), 2); diff --git a/sus/fn/fn.h b/sus/fn/fn.h index e1d31d206..f2a41a109 100644 --- a/sus/fn/fn.h +++ b/sus/fn/fn.h @@ -20,4 +20,5 @@ #include "sus/fn/fn_box_impl.h" #include "sus/fn/fn_concepts.h" #include "sus/fn/fn_ref.h" +#include "sus/fn/fn_dyn.h" // IWYU pragma: end_exports diff --git a/sus/fn/fn_dyn.h b/sus/fn/fn_dyn.h new file mode 100644 index 000000000..bee46ec70 --- /dev/null +++ b/sus/fn/fn_dyn.h @@ -0,0 +1,161 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IWYU pragma: private, include "sus/fn/fn.h" +// IWYU pragma: friend "sus/.*" +#pragma once + +#include "sus/boxed/dyn.h" +#include "sus/fn/fn_concepts.h" + +namespace sus::fn { + +template +struct DynFn; +template +struct DynFnMut; +template +struct DynFnOnce; + +template + requires(Fn) +struct DynFnTyped; +template + requires(FnMut) +struct DynFnMutTyped; +template + requires(FnOnce) +struct DynFnOnceTyped; + +/// A type-erased object which satisifies the concept [`Fn`]( +/// $sus::fn::Fn). +template +struct DynFn { + template + static constexpr bool SatisfiesConcept = Fn; + template + using DynTyped = DynFnTyped; + + DynFn() = default; + virtual ~DynFn() = default; + DynFn(DynFn&&) = delete; + DynFn& operator=(DynFn&&) = delete; + + // Virtual concept API. + virtual R operator()(Args&&...) const = 0; +}; + +/// The implementation of type-erasure for the Fn concept. +/// #[doc.hidden] +template + requires(Fn) +struct DynFnTyped final : public DynFn { + constexpr DynFnTyped(Store&& c) : c_(sus::forward(c)) {} + + R operator()(Args&&... args) const override { + return c_(::sus::forward(args)...); + } + + private: + Store c_; +}; + +/// A type-erased object which satisifies the concept [`FnMut`]( +/// $sus::fn::FnMut). +template +struct DynFnMut { + template + static constexpr bool SatisfiesConcept = FnMut; + template + using DynTyped = DynFnMutTyped; + + DynFnMut() = default; + virtual ~DynFnMut() = default; + DynFnMut(DynFnMut&&) = delete; + DynFnMut& operator=(DynFnMut&&) = delete; + + // Virtual concept API. + virtual R operator()(Args&&...) = 0; +}; + +/// The implementation of type-erasure for the DynFnMut concept. +/// #[doc.hidden] +template + requires(FnMut) +struct DynFnMutTyped final : public DynFnMut { + constexpr DynFnMutTyped(Store&& c) : c_(sus::forward(c)) {} + + R operator()(Args&&... args) override { + return c_(::sus::forward(args)...); + } + + private: + Store c_; +}; + +/// A type-erased object which satisifies the concept [`FnOnce`]( +/// $sus::fn::FnOnce). +template +struct DynFnOnce { + template + static constexpr bool SatisfiesConcept = FnOnce; + template + using DynTyped = DynFnOnceTyped; + + DynFnOnce() = default; + virtual ~DynFnOnce() = default; + DynFnOnce(DynFnOnce&&) = delete; + DynFnOnce& operator=(DynFnOnce&&) = delete; + + // Virtual concept API. + virtual R operator()(Args&&...) && = 0; +}; + +/// The implementation of type-erasure for the DynFnOnce concept. +/// #[doc.hidden] +template + requires(FnOnce) +struct DynFnOnceTyped final : public DynFnOnce { + constexpr DynFnOnceTyped(Store&& c) : c_(sus::forward(c)) {} + + R operator()(Args&&... args) && override { + return c_(::sus::forward(args)...); + } + + private: + Store c_; +}; + +// `DynFn` satisfies `Fn`. +static_assert(Fn, int(double)>); +// `DynFn` satisfies `DynConcept`, meaning it type-erases correctly and can +// interact with `Dyn` and `Box`. +static_assert(::sus::boxed::DynConcept, + decltype([](float) { return 0; })>); + +// `DynFnMut` satisfies `FnMut`. +static_assert(FnMut, int(double)>); +// `DynFnMut` satisfies `DynConcept`, meaning it type-erases correctly and can +// interact with `Dyn` and `Box`. +static_assert(::sus::boxed::DynConcept, + decltype([](float) { return 0; })>); + +// `DynFnOnce` satisfies `FnOnce`. +static_assert(FnOnce, int(double)>); +// `DynFnOnce` satisfies `DynConcept`, meaning it type-erases correctly and can +// interact with `Dyn` and `Box`. +static_assert(::sus::boxed::DynConcept, + decltype([](float) { return 0; })>); + +} // namespace sus::fn diff --git a/sus/fn/fn_dyn_unittest.cc b/sus/fn/fn_dyn_unittest.cc new file mode 100644 index 000000000..0c2c72167 --- /dev/null +++ b/sus/fn/fn_dyn_unittest.cc @@ -0,0 +1,84 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sus/fn/fn_dyn.h" + +#include "googletest/include/gtest/gtest.h" +#include "sus/boxed/box.h" +#include "sus/prelude.h" + +namespace { +using namespace sus::fn; + +TEST(FnDyn, Fn) { + auto x = [](const DynFn& f) { return call(f, 1, 2); }; + i32 c = x(sus::dyn>([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = x(sus::dyn>([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +TEST(FnDyn, FnBox) { + auto x = [](sus::Box> f) { return call(*f, 1, 2); }; + i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = x(sus::into([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +TEST(FnMutDyn, FnMut) { + auto x = [](DynFnMut& f) { return call_mut(f, 1, 2); }; + i32 c = + x(sus::dyn>([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = + x(sus::dyn>([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +TEST(FnMutDyn, FnMutBox) { + auto x = [](sus::Box> f) { + return call_mut(*f, 1, 2); + }; + i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = x(sus::into([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +TEST(FnDyn, FnOnce) { + auto x = [](DynFnOnce&& f) { + return call_once(sus::move(f), 1, 2); + }; + i32 c = + x(sus::dyn>([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = + x(sus::dyn>([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +TEST(FnOnceDyn, FnOnceBox) { + auto x = [](sus::Box> f) { + return sus::move(f).consume([](auto&& dynfn) { + return ::sus::fn::call_once(sus::move(dynfn), 1, 2); + }); + }; + i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); + EXPECT_EQ(c, 1 + 2); + i32 d = x(sus::into([](i32 a, i32 b) { return a * b; })); + EXPECT_EQ(d, 1 * 2); +} + +} // namespace From 42eafa784f84690bd353dc153e6f75de18217a86 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 04:58:18 -0400 Subject: [PATCH 10/31] When building the storage reference, work with rvalue correctly The reference may be an rvalue and it may need to be used as an rvalue in order to get the desired type from it. --- sus/option/option.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sus/option/option.h b/sus/option/option.h index 7b1964b98..0b868246b 100644 --- a/sus/option/option.h +++ b/sus/option/option.h @@ -1887,10 +1887,11 @@ class Option final { // storage from a `T` object or a `T&&` (which is received as `T&`). template static constexpr inline decltype(auto) move_to_storage(U&& t) { - if constexpr (std::is_reference_v) - return StoragePointer(t); - else + if constexpr (std::is_reference_v) { + return StoragePointer(::sus::forward(t)); + } else { return ::sus::move(t); + } } /// Constructors for `Some`. From 296818da5a17e850401956026d83e2eb8836a0ab Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 04:59:15 -0400 Subject: [PATCH 11/31] Support getting an lvalue ref from Dyn when it's an lvalue. This can be useful when it lives on the stack in order to extend its lifetime. --- sus/boxed/dyn.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h index 2c2b77f85..92eb3ef5b 100644 --- a/sus/boxed/dyn.h +++ b/sus/boxed/dyn.h @@ -277,7 +277,12 @@ class [[nodiscard]] Dyn { : dyn_(const_cast(concrete)) {} /// Converts the reference to `ConcreteT` into a `DynC` reference. - operator const DynC&() const&& { return dyn_; } + operator const DynC&() const& { return dyn_; } + operator DynC&() & + requires(!std::is_const_v) + { + return dyn_; + } operator DynC&() && requires(!std::is_const_v) { From 4a449009f270957658a3b434fd1f249e814ec47c Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 04:59:47 -0400 Subject: [PATCH 12/31] Avoid needing to be able to construct in order to look for NeverValue --- sus/mem/never_value.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sus/mem/never_value.h b/sus/mem/never_value.h index e230e1c65..88a5b791f 100644 --- a/sus/mem/never_value.h +++ b/sus/mem/never_value.h @@ -72,14 +72,17 @@ struct NeverValueAccess { static constexpr bool has_field = false; }; +/// Whether the type `T` has a never-value field. template -struct NeverValueAccess { - /// Whether the type `T` has a never-value field. +struct NeverValueChecker { static constexpr bool has_field = requires { std::declval()._sus_Unsafe_NeverValueIsConstructed( ::sus::marker::unsafe_fn); }; +}; +template +struct NeverValueAccess { constexpr NeverValueAccess() = default; template @@ -89,7 +92,7 @@ struct NeverValueAccess { /// Checks if the never-value field is set to the never-value, returning false /// if it is. sus_pure constexpr sus_always_inline bool is_constructed() const noexcept - requires(has_field) + requires(NeverValueChecker::has_field) { return t_._sus_Unsafe_NeverValueIsConstructed(::sus::marker::unsafe_fn); } @@ -97,7 +100,7 @@ struct NeverValueAccess { /// Sets the never-value field to the destroy-value. constexpr sus_always_inline void set_destroy_value( ::sus::marker::UnsafeFnMarker) noexcept - requires(has_field) + requires(NeverValueChecker::has_field) { t_._sus_Unsafe_NeverValueSetDestroyValue(::sus::marker::unsafe_fn); } @@ -126,7 +129,7 @@ struct NeverValueAccess { /// the field will be set to a special destroy-value before the destructor is /// called. template -concept NeverValueField = __private::NeverValueAccess::has_field; +concept NeverValueField = __private::NeverValueChecker::has_field; } // namespace sus::mem @@ -151,6 +154,8 @@ concept NeverValueField = __private::NeverValueAccess::has_field; \ template \ friend struct ::sus::mem::__private::NeverValueAccess; \ + template \ + friend struct ::sus::mem::__private::NeverValueChecker; \ \ sus_pure constexpr bool _sus_Unsafe_NeverValueIsConstructed( \ ::sus::marker::UnsafeFnMarker) const noexcept { \ From 47c71848728fff4c02010af58a4429e8cdce8af1 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 05:00:08 -0400 Subject: [PATCH 13/31] Avoid conflicting comments on constructors --- sus/error/error.h | 1 - 1 file changed, 1 deletion(-) diff --git a/sus/error/error.h b/sus/error/error.h index 573a01244..b96f087cc 100644 --- a/sus/error/error.h +++ b/sus/error/error.h @@ -455,7 +455,6 @@ struct DynError { constexpr DynError() = default; /// #[doc.hidden] constexpr virtual ~DynError() = default; - /// #[doc.hidden] DynError(DynError&&) = delete; /// #[doc.hidden] DynError&& operator=(DynError&&) = delete; From 46a9f377e51e17e2ed8a1ea9401bea51e7db8af6 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 05:00:34 -0400 Subject: [PATCH 14/31] Remove use of FnOnceRef in subdoc --- subdoc/lib/gen/generate_function.cc | 9 ++++++--- subdoc/lib/gen/generate_record.cc | 16 +++++++++------- subdoc/lib/gen/generate_type.cc | 20 +++++++++++--------- subdoc/lib/gen/generate_type.h | 8 ++++---- subdoc/lib/type.cc | 20 ++++++++++---------- subdoc/lib/type.h | 12 ++++++------ subdoc/tests/type_unittest.cc | 20 ++++++++++++-------- 7 files changed, 58 insertions(+), 47 deletions(-) diff --git a/subdoc/lib/gen/generate_function.cc b/subdoc/lib/gen/generate_function.cc index 567918ccc..04be79c39 100644 --- a/subdoc/lib/gen/generate_function.cc +++ b/subdoc/lib/gen/generate_function.cc @@ -49,9 +49,12 @@ void generate_function_params(HtmlWriter::OpenDiv& div, if (p.parameter_name.empty()) { generate_type(div, p.type, sus::none()); } else { - generate_type(div, p.type, sus::some([&](HtmlWriter::OpenDiv& div) { - div.write_text(p.parameter_name); - })); + generate_type( + div, p.type, + sus::some(sus::dyn>( + [&](HtmlWriter::OpenDiv& div) { + div.write_text(p.parameter_name); + }))); } if (p.default_value.is_some()) { diff --git a/subdoc/lib/gen/generate_record.cc b/subdoc/lib/gen/generate_record.cc index b3399cc42..d44d92d64 100644 --- a/subdoc/lib/gen/generate_record.cc +++ b/subdoc/lib/gen/generate_record.cc @@ -243,13 +243,15 @@ sus::Result generate_record_fields( } name_div.write_text(" "); } - generate_type(name_div, fe.type, - sus::some([&](HtmlWriter::OpenDiv& div) { - auto anchor = div.open_a(); - anchor.add_href(construct_html_url_for_field(fe)); - anchor.add_class("field-name"); - anchor.write_text(fe.name); - })); + generate_type( + name_div, fe.type, + sus::some(sus::dyn>( + [&](HtmlWriter::OpenDiv& div) { + auto anchor = div.open_a(); + anchor.add_href(construct_html_url_for_field(fe)); + anchor.add_class("field-name"); + anchor.write_text(fe.name); + }))); } { auto desc_div = field_div.open_div(); diff --git a/subdoc/lib/gen/generate_type.cc b/subdoc/lib/gen/generate_type.cc index 5be1b2c0c..21da172d0 100644 --- a/subdoc/lib/gen/generate_type.cc +++ b/subdoc/lib/gen/generate_type.cc @@ -34,7 +34,7 @@ std::string make_title_string(TypeToStringQuery q) { } // namespace void generate_type(HtmlWriter::OpenDiv& div, const LinkedType& linked_type, - sus::Option> + sus::Option&> var_name_fn) noexcept { auto text_fn = [&](std::string_view text) { div.write_text(text); }; auto type_fn = [&, i_ = 0_usize](TypeToStringQuery q) mutable { @@ -79,15 +79,17 @@ void generate_type(HtmlWriter::OpenDiv& div, const LinkedType& linked_type, span.write_text("volatile"); }; auto var_fn = [&]() { sus::move(var_name_fn).unwrap()(div); }; + // Construct our DynFnMut reference to `var_fn` in case we need it so that + // it can outlive the Option holding a ref to it. + auto dyn_fn = sus::dyn>(var_fn); + auto opt_dyn_fn = sus::Option&>(dyn_fn); - type_to_string(linked_type.type, text_fn, type_fn, const_fn, volatile_fn, - [&]() -> sus::Option> { - if (var_name_fn.is_some()) { - return sus::some(sus::move(var_fn)); - } else { - return sus::none(); - } - }()); + type_to_string(linked_type.type, + sus::dyn>(text_fn), + sus::dyn>(type_fn), + sus::dyn>(const_fn), + sus::dyn>(volatile_fn), + var_name_fn.and_that(sus::move(opt_dyn_fn))); } } // namespace subdoc::gen diff --git a/subdoc/lib/gen/generate_type.h b/subdoc/lib/gen/generate_type.h index 36a3085a3..d57ddf63a 100644 --- a/subdoc/lib/gen/generate_type.h +++ b/subdoc/lib/gen/generate_type.h @@ -17,13 +17,13 @@ #include "subdoc/lib/database.h" #include "subdoc/lib/gen/html_writer.h" #include "subdoc/lib/type.h" -#include "sus/prelude.h" #include "sus/fn/fn_ref.h" +#include "sus/prelude.h" namespace subdoc::gen { -void generate_type( - HtmlWriter::OpenDiv& div, const LinkedType& linked_type, - sus::Option> var_name_fn) noexcept; +void generate_type(HtmlWriter::OpenDiv& div, const LinkedType& linked_type, + sus::Option&> + var_name_fn) noexcept; } // namespace subdoc::gen diff --git a/subdoc/lib/type.cc b/subdoc/lib/type.cc index 361d9fa26..3a0830cdb 100644 --- a/subdoc/lib/type.cc +++ b/subdoc/lib/type.cc @@ -528,11 +528,11 @@ Type build_local_type(clang::QualType qualtype, const clang::SourceManager& sm, namespace { void type_to_string_internal( - const Type& type, sus::fn::FnMutRef& text_fn, - sus::fn::FnMutRef& type_fn, - sus::fn::FnMutRef& const_qualifier_fn, - sus::fn::FnMutRef& volatile_qualifier_fn, - sus::Option> var_name_fn) noexcept { + const Type& type, sus::fn::DynFnMut& text_fn, + sus::fn::DynFnMut& type_fn, + sus::fn::DynFnMut& const_qualifier_fn, + sus::fn::DynFnMut& volatile_qualifier_fn, + sus::Option&> var_name_fn) noexcept { if (type.qualifier.is_const) { const_qualifier_fn(); text_fn(" "); @@ -730,11 +730,11 @@ void type_walk_types_internal( } // namespace void type_to_string( - const Type& type, sus::fn::FnMutRef text_fn, - sus::fn::FnMutRef type_fn, - sus::fn::FnMutRef const_qualifier_fn, - sus::fn::FnMutRef volatile_qualifier_fn, - sus::Option> var_name_fn) noexcept { + const Type& type, sus::fn::DynFnMut& text_fn, + sus::fn::DynFnMut& type_fn, + sus::fn::DynFnMut& const_qualifier_fn, + sus::fn::DynFnMut& volatile_qualifier_fn, + sus::Option&> var_name_fn) noexcept { return type_to_string_internal(type, text_fn, type_fn, const_qualifier_fn, volatile_qualifier_fn, sus::move(var_name_fn)); } diff --git a/subdoc/lib/type.h b/subdoc/lib/type.h index 3431ebc23..426d2d23e 100644 --- a/subdoc/lib/type.h +++ b/subdoc/lib/type.h @@ -17,7 +17,7 @@ #include "subdoc/llvm.h" #include "sus/choice/choice.h" #include "sus/collections/vec.h" -#include "sus/fn/fn_ref.h" +#include "sus/fn/fn.h" #include "sus/prelude.h" namespace subdoc { @@ -242,11 +242,11 @@ struct TypeToStringQuery { /// The `var_name_fn` is called at the place where the variable name (if any) /// would appear. void type_to_string( - const Type& type, sus::fn::FnMutRef text_fn, - sus::fn::FnMutRef type_fn, - sus::fn::FnMutRef const_qualifier_fn, - sus::fn::FnMutRef volatile_qualifier_fn, - sus::Option> var_name_fn) noexcept; + const Type& type, sus::fn::DynFnMut& text_fn, + sus::fn::DynFnMut& type_fn, + sus::fn::DynFnMut& const_qualifier_fn, + sus::fn::DynFnMut& volatile_qualifier_fn, + sus::Option&> var_name_fn) noexcept; /// Like `type_to_string` but just walks through the types and does not produce /// any output. diff --git a/subdoc/tests/type_unittest.cc b/subdoc/tests/type_unittest.cc index 7b1778701..7c5a173cc 100644 --- a/subdoc/tests/type_unittest.cc +++ b/subdoc/tests/type_unittest.cc @@ -34,14 +34,18 @@ std::string make_string(std::string_view var_name, const subdoc::Type& type) { auto const_fn = [&]() { str << "const"; }; auto volatile_fn = [&]() { str << "volatile"; }; auto var_fn = [&]() { str << var_name; }; - - subdoc::type_to_string(type, text_fn, type_fn, const_fn, volatile_fn, - [&]() -> sus::Option> { - if (var_name.empty()) - return sus::none(); - else - return sus::some(sus::move(var_fn)); - }()); + // Construct our DynFnMut reference to `var_fn` in case we need it so that + // it can outlive the Option holding a ref to it. + auto dyn_fn = sus::dyn>(var_fn); + sus::Option&> opt_dyn_fn; + if (!var_name.empty()) opt_dyn_fn.insert(dyn_fn); + + subdoc::type_to_string( + type, sus::dyn>(text_fn), + sus::dyn>(type_fn), + sus::dyn>(const_fn), + sus::dyn>(volatile_fn), + sus::move(opt_dyn_fn)); return sus::move(str).str(); } From 4b6d36cfca964b531ba6e5c8e745066b5f48d5d4 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 05:05:05 -0400 Subject: [PATCH 15/31] Remove use of FnMutRef and FnMutBox in subdoc --- subdoc/lib/database.h | 95 ++++++++++++++++++++++--------------------- subdoc/lib/type.cc | 4 +- subdoc/lib/type.h | 2 +- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/subdoc/lib/database.h b/subdoc/lib/database.h index 287f8ca05..a8b2018ee 100644 --- a/subdoc/lib/database.h +++ b/subdoc/lib/database.h @@ -18,13 +18,13 @@ #include "subdoc/lib/doc_attributes.h" #include "subdoc/lib/friendly_names.h" +#include "subdoc/lib/linked_type.h" #include "subdoc/lib/method_qualifier.h" #include "subdoc/lib/parse_comment.h" #include "subdoc/lib/path.h" #include "subdoc/lib/record_type.h" #include "subdoc/lib/requires.h" #include "subdoc/lib/type.h" -#include "subdoc/lib/linked_type.h" #include "subdoc/lib/unique_symbol.h" #include "subdoc/llvm.h" #include "sus/assertions/check.h" @@ -226,7 +226,7 @@ struct FunctionElement : public CommentElement { return sus::none(); } - void for_each_comment(sus::fn::FnMutRef fn) { fn(comment); } + void for_each_comment(sus::fn::FnMut auto fn) { fn(comment); } }; struct ConceptElement : public CommentElement { @@ -262,7 +262,7 @@ struct ConceptElement : public CommentElement { return sus::none(); } - void for_each_comment(sus::fn::FnMutRef fn) { fn(comment); } + void for_each_comment(sus::fn::FnMut auto fn) { fn(comment); } }; struct FieldElement : public CommentElement { @@ -310,7 +310,7 @@ struct FieldElement : public CommentElement { return sus::none(); } - void for_each_comment(sus::fn::FnMutRef fn) { fn(comment); } + void for_each_comment(sus::fn::FnMut auto fn) { fn(comment); } }; struct ConceptId { @@ -552,7 +552,7 @@ struct RecordElement : public TypeElement { return out; } - void for_each_comment(sus::fn::FnMutRef fn) { + void for_each_comment(sus::fn::FnMut auto fn) { fn(comment); for (auto& [k, e] : records) e.for_each_comment(fn); for (auto& [k, e] : fields) e.for_each_comment(fn); @@ -736,7 +736,7 @@ struct NamespaceElement : public CommentElement { return out; } - void for_each_comment(sus::fn::FnMutRef fn) { + void for_each_comment(sus::fn::FnMut auto fn) { fn(comment); for (auto& [k, e] : concepts) e.for_each_comment(fn); for (auto& [k, e] : namespaces) e.for_each_comment(fn); @@ -777,13 +777,11 @@ struct Database { sus::Vec to_resolve; { sus::Vec* to_resolve_ptr = &to_resolve; - sus::fn::FnMutBox fn = sus_bind_mut( - sus_store(sus_unsafe_pointer(to_resolve_ptr)), [&](Comment& c) { - if (c.attrs.inherit.is_some()) { - to_resolve_ptr->push(&c); - } - }); - global.for_each_comment(fn); + global.for_each_comment([&](Comment& c) { + if (c.attrs.inherit.is_some()) { + to_resolve_ptr->push(&c); + } + }); } while (!to_resolve.is_empty()) { @@ -931,41 +929,44 @@ struct Database { sus::Vec> collect_type_element_refs( const Type& type) const noexcept { sus::Vec> vec; - type_walk_types(type, [&](TypeToStringQuery q) { - const NamespaceElement* ns_cursor = &global; - for (const std::string& name : q.namespace_path) { - auto it = ns_cursor->namespaces.find(NamespaceId(name)); - if (it == ns_cursor->namespaces.end()) { - vec.push(sus::none()); - return; - } - ns_cursor = &it->second; - } - if (q.record_path.is_empty()) { - vec.push(ns_cursor->get_local_type_element_ref_by_name(q.name)); - return; - } + type_walk_types( + type, + sus::dyn>( + [&](TypeToStringQuery q) { + const NamespaceElement* ns_cursor = &global; + for (const std::string& name : q.namespace_path) { + auto it = ns_cursor->namespaces.find(NamespaceId(name)); + if (it == ns_cursor->namespaces.end()) { + vec.push(sus::none()); + return; + } + ns_cursor = &it->second; + } + if (q.record_path.is_empty()) { + vec.push(ns_cursor->get_local_type_element_ref_by_name(q.name)); + return; + } - const RecordElement* rec_cursor = nullptr; - for (const auto& [i, name] : q.record_path.iter().enumerate()) { - if (i == 0u) { - auto it = ns_cursor->records.find(RecordId(name)); - if (it == ns_cursor->records.end()) { - vec.push(sus::none()); - return; - } - rec_cursor = &it->second; - } else { - auto it = rec_cursor->records.find(RecordId(name)); - if (it == rec_cursor->records.end()) { - vec.push(sus::none()); - return; - } - rec_cursor = &it->second; - } - } - vec.push(rec_cursor->get_local_type_element_ref_by_name(q.name)); - }); + const RecordElement* rec_cursor = nullptr; + for (const auto& [i, name] : q.record_path.iter().enumerate()) { + if (i == 0u) { + auto it = ns_cursor->records.find(RecordId(name)); + if (it == ns_cursor->records.end()) { + vec.push(sus::none()); + return; + } + rec_cursor = &it->second; + } else { + auto it = rec_cursor->records.find(RecordId(name)); + if (it == rec_cursor->records.end()) { + vec.push(sus::none()); + return; + } + rec_cursor = &it->second; + } + } + vec.push(rec_cursor->get_local_type_element_ref_by_name(q.name)); + })); return vec; } diff --git a/subdoc/lib/type.cc b/subdoc/lib/type.cc index 3a0830cdb..9a95dcfcb 100644 --- a/subdoc/lib/type.cc +++ b/subdoc/lib/type.cc @@ -681,7 +681,7 @@ void type_to_string_internal( void type_walk_types_internal( const Type& type, - sus::fn::FnMutRef& type_fn) noexcept { + sus::fn::DynFnMut& type_fn) noexcept { for (const TypeOrValue& tv : type.nested_names) { switch (tv.choice) { case TypeOrValueTag::Type: { @@ -741,7 +741,7 @@ void type_to_string( void type_walk_types( const Type& type, - sus::fn::FnMutRef type_fn) noexcept { + sus::fn::DynFnMut& type_fn) noexcept { return type_walk_types_internal(type, type_fn); } diff --git a/subdoc/lib/type.h b/subdoc/lib/type.h index 426d2d23e..9610c016d 100644 --- a/subdoc/lib/type.h +++ b/subdoc/lib/type.h @@ -252,6 +252,6 @@ void type_to_string( /// any output. void type_walk_types( const Type& type, - sus::fn::FnMutRef type_fn) noexcept; + sus::fn::DynFnMut& type_fn) noexcept; } // namespace subdoc From 7a9d7c37cd3f7e49752a858446437993ec18f743 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 12:23:58 -0400 Subject: [PATCH 16/31] Remove FnBoxMut from RepeatWith and make it constexpr --- sus/iter/repeat_with.h | 42 ++++++++++++++++++++------------ sus/iter/repeat_with_unittest.cc | 4 +++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/sus/iter/repeat_with.h b/sus/iter/repeat_with.h index 08ab06b4f..5b12b9e51 100644 --- a/sus/iter/repeat_with.h +++ b/sus/iter/repeat_with.h @@ -23,7 +23,7 @@ namespace sus::iter { using ::sus::option::Option; -template +template class RepeatWith; /// Creates a new iterator that repeats elements of type `Item` endlessly by @@ -46,40 +46,52 @@ class RepeatWith; /// sus::check(r.next().unwrap() == 3_u16); /// sus::check(r.next().unwrap() == 3_u16); /// ``` -template -inline RepeatWith repeat_with(::sus::fn::FnMutBox gen) noexcept { - return RepeatWith(::sus::move(gen)); +template GenFn> +constexpr inline RepeatWith repeat_with(GenFn gen) noexcept { + return RepeatWith(::sus::move(gen)); } /// An Iterator that walks over at most a single Item. -template -class [[nodiscard]] [[sus_trivial_abi]] RepeatWith final - : public IteratorBase, ItemT> { +template +class [[nodiscard]] RepeatWith final + : public IteratorBase, ItemT> { public: using Item = ItemT; + // Type is Move and (can be) Clone. + RepeatWith(RepeatWith&&) = default; + RepeatWith& operator=(RepeatWith&&) = default; + + // sus::mem::Clone trait. + constexpr RepeatWith clone() const noexcept + requires(::sus::mem::Clone) + { + return RepeatWith(sus::clone(gen_)); + } + // sus::iter::Iterator trait. - Option next() noexcept { + constexpr Option next() noexcept { return ::sus::some(::sus::fn::call_mut(gen_)); } /// sus::iter::Iterator trait. - SizeHint size_hint() const noexcept { + constexpr SizeHint size_hint() const noexcept { return SizeHint(usize::MAX, ::sus::Option<::sus::num::usize>()); } // sus::iter::DoubleEndedIterator trait. - Option next_back() noexcept { + constexpr Option next_back() noexcept { return ::sus::some(::sus::fn::call_mut(gen_)); } private: - friend RepeatWith sus::iter::repeat_with( - ::sus::fn::FnMutBox gen) noexcept; + friend constexpr RepeatWith sus::iter::repeat_with( + GenFn gen) noexcept; - RepeatWith(::sus::fn::FnMutBox gen) : gen_(::sus::move(gen)) {} + constexpr RepeatWith(GenFn gen) : gen_(::sus::move(gen)) {} - ::sus::fn::FnMutBox gen_; + GenFn gen_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(gen_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(gen_)); }; } // namespace sus::iter diff --git a/sus/iter/repeat_with_unittest.cc b/sus/iter/repeat_with_unittest.cc index 17074ecf3..021706646 100644 --- a/sus/iter/repeat_with_unittest.cc +++ b/sus/iter/repeat_with_unittest.cc @@ -46,4 +46,8 @@ TEST(RepeatWith, NextBack) { EXPECT_EQ(o.next_back(), sus::some(3_u16)); } +// constexpr +static_assert(sus::iter::repeat_with([] { return 3; }).take(4u).sum() == + 3 * 4); + } // namespace From b9fa1136e38b1711a5596cd9518e7b3c41fc22e8 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 12:43:27 -0400 Subject: [PATCH 17/31] Implement iterator max_by_key and min_by_key more efficiently Now that everything can be constexpr we can just max_by and min_by in the implementation of them. --- sus/iter/iterator_defn.h | 74 +++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/sus/iter/iterator_defn.h b/sus/iter/iterator_defn.h index 68350bd8b..c6f46ab7f 100644 --- a/sus/iter/iterator_defn.h +++ b/sus/iter/iterator_defn.h @@ -1504,29 +1504,24 @@ template < !std::is_reference_v) constexpr Option IteratorBase::max_by_key( KeyFn fn) && noexcept { - auto fold = [&fn](sus::Tuple&& acc, Item&& item) { - Key key = ::sus::fn::call_mut(fn, item); - if (key >= acc.template at<0>()) - return sus::Tuple(::sus::move(key), - ::sus::forward(item)); - return ::sus::move(acc); + // Builds a map function that turns an Item into a Tuple by using + // `keyfn`. + auto key = [](KeyFn keyfn) { + return [keyfn](Item&& item) { + return sus::Tuple(keyfn(item), sus::forward(item)); + }; }; - // TODO: We could do .map() to make the tuple and use max_by(), and not need - // the if statement but for that .map() would need to take a reference - // on/ownership of `fn` and that requires heap allocations for FnMutBox. - auto first = as_subclass_mut().next(); - if (first.is_none()) return Option(); - Key first_key = fn(first.as_value()); - return Option( - // Run fold() over a Tuple to find the max Key. - static_cast(*this) - .fold(sus::Tuple(first_key, - ::sus::move(first).unwrap_unchecked( - ::sus::marker::unsafe_fn)), - fold) - // Pull out the Item for the max Key. - .template into_inner<1>()); + // Compares just the keys. + auto compare = [](const sus::Tuple& x, + const sus::Tuple& y) { + return x.at<0>() <=> y.at<0>(); + }; + + return static_cast(*this).map(key(fn)).max_by(compare).map( + [](auto&& key_item) -> Item { + return sus::move(key_item).into_inner<1>(); + }); } template @@ -1562,29 +1557,24 @@ template < !std::is_reference_v) constexpr Option IteratorBase::min_by_key( KeyFn fn) && noexcept { - auto fold = [&fn](sus::Tuple&& acc, Item&& item) { - Key key = ::sus::fn::call_mut(fn, item); - if (key < acc.template at<0>()) - return sus::Tuple(::sus::move(key), - ::sus::forward(item)); - return ::sus::move(acc); + // Builds a map function that turns an Item into a Tuple by using + // `keyfn`. + auto key = [](KeyFn keyfn) { + return [keyfn](Item&& item) { + return sus::Tuple(keyfn(item), sus::forward(item)); + }; }; - // TODO: We could do .map() to make the tuple and use min_by(), and not need - // the if statement but for that .map() would need to take a reference - // on/ownership of `fn` and that requires heap allocations for FnMutBox. - auto first = as_subclass_mut().next(); - if (first.is_none()) return Option(); - Key first_key = fn(first.as_value()); - return Option( - // Run fold() over a Tuple to find the min Key. - static_cast(*this) - .fold(sus::Tuple(first_key, - ::sus::move(first).unwrap_unchecked( - ::sus::marker::unsafe_fn)), - fold) - // Pull out the Item for the min Key. - .template into_inner<1>()); + // Compares just the keys. + auto compare = [](const sus::Tuple& x, + const sus::Tuple& y) { + return x.at<0>() <=> y.at<0>(); + }; + + return static_cast(*this).map(key(fn)).min_by(compare).map( + [](auto&& key_item) -> Item { + return sus::move(key_item).into_inner<1>(); + }); } template From 39ab44ec8ef773cdd2cbe147c71fd8ab5f8a5576 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:17:45 -0400 Subject: [PATCH 18/31] Box now is a Fn itself and add tons of docs for type-erasure and Box --- sus/boxed/box.h | 138 +++++++++++++++++++++++++++++++++++--- sus/boxed/box_unittest.cc | 26 +++++++ sus/error/error.h | 7 +- sus/fn/fn_dyn.h | 21 +++++- sus/fn/fn_dyn_unittest.cc | 10 +-- 5 files changed, 178 insertions(+), 24 deletions(-) diff --git a/sus/boxed/box.h b/sus/boxed/box.h index bc09f4f69..add44c02c 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -41,14 +41,73 @@ namespace sus::boxed { /// A heap allocated object. /// +/// A `Box` holds ownership of an object of type `T` on the heap. When `Box` +/// is destroyed, or an rvalue-qualified method is called, the inner heap +/// object is freed. +/// +/// `Box` is similar to [`std::unique_ptr`]( +/// https://en.cppreference.com/w/cpp/memory/unique_ptr) with some differences, +/// and should be preferred when those differences will benefit you: +/// * Const correctness. A `const Box` treats the inner object as `const` and +/// does not expose mutable access to it. +/// * Never null. A `Box` always holds a value until it is moved-from. It is +/// constructed from a value, not a pointer. +/// A moved-from `Box` may not be used except to be assigned to or destroyed. +/// Using a moved-from `Box` will [`panic`]($sus::assertions::panic) and +/// terminate the program rather than operate on a null. This prevents +/// Undefined Behaviour and memory bugs caused by dereferencing null or using +/// null in unintended ways. +/// * Supports up-casting (TODO: and [`down-casting`]( +/// https://github.com/chromium/subspace/issues/333)) without leaving +/// requiring a native pointer to be pulled out of the `Box`. +/// * No native arrays, `Box` can hold [`Array`]($sus::collection::Array) or +/// [`std::array`](https://en.cppreference.com/w/cpp/container/array) but +/// avoids API complexity for supporting pointers and native arrays (which +/// decay to pointers) by rejecting native arrays. +/// * Integration with [concept type-erasure]($sus::boxed::DynConcept) for +/// holding and constructing from type-erased objects which satisfy a given +/// concept in a type-safe way and without requiring inheritence. This +/// construction is done through [`From`]($sus::construct::From) and thus +/// can `Box` can be constructed in a type-deduced way from any object +/// that satisfies the concept `C` via [`sus::into()`]($sus::construct::into). +/// This only requires that `DynC` is compatible with +/// [`DynConcept`]($sus::boxed::DynConcept) which is the case for all +/// type-erasable concepts in the Subspace library. +/// * Additional integration with Subspace library concepts like +/// [`Error`]($sus::error::Error) and [`Fn`]($sus::fn::Fn) such that `Box` +/// [will satisfy those concepts itself]( +/// #box-implements-some-concepts-for-its-inner-type) when holding +/// a type-erased object that satisfies those concepts. +/// /// # Box implements some concepts for its inner type -/// For some concepts in the Subspace library, if `T` satisfies the concept, -/// then `Box` will as well, forwarding through to the inner heap-allocated -/// object. Those concepts are: -/// * [`Error`]($sus::error::Error). +/// +/// The Subspace library provides a number of concepts which support +/// type-erasure through [`DynConcept`]($sus::boxed::DynConcept), and when +/// `Box` is holding these as its value, it may itself implement the concept, +/// forwarding use of the concept through to the inner type. +/// +/// The canonical example of this is +/// [`Result`]($sus::result::Result)`>`, which allows construction via +/// `sus::err(sus::into(e))` for any `e` that satisfies +/// [`Error`]($sus::error::Error). The error field, now being a `Box` is still +/// usable as an [`Error`]($sus::error::Error) allowing generic code that +/// expects the [`Result`]($sus::result::Result) to hold an +/// [`Error`]($sus::error::Error) to function even though the actual error has +/// been type-erased and no longer needs the functions to be templated on it. +/// +/// The following concepts, when type-erased in `Box` will also be satisfied +/// by the `Box` itself, avoiding the need to unwrap the inner type and allowing +/// the `Box` to be used in templated code requiring that concept: +/// * [`Error`]($sus::error::Error) +/// * [`Fn`]($sus::fn::Fn) +/// * [`FnMut`]($sus::fn::FnMut) +/// * [`FnOnce`]($sus::fn::FnOnce) template class [[sus_trivial_abi]] Box final { static_assert(!std::is_reference_v, "Box of a reference is not allowed."); + static_assert(!std::is_array_v, + "Box is not allowed, use Box>"); static_assert(sus::mem::size_of() != 0u); // Ensure that `T` is defined. public: @@ -287,20 +346,77 @@ class [[sus_trivial_abi]] Box final { return ret; } + /// A `Box` holding a type-erased function type will satisfy the fn concepts + /// and can be used as a function type. It will forward the call through + /// to the inner type. + /// + /// `Box<`[`DynFn`]($sus::fn::DynFn)`>` satisfies [`Fn`]($sus::fn::Fn), + /// `Box<`[`DynFnMut`]($sus::fn::DynFnMut)`>` satisfies [`FnMut`]($sus::fn::FnMut), + /// and `Box<`[`DynFnOnce`]($sus::fn::DynFnOnce)`>` satisfies [`FnOnce`]($sus::fn::FnOnce). + /// The usual compatibility rules apply, allowing [`DynFn`]($sus::fn::DynFn) + /// to be treated like [`DynFnMut`]($sus::fn::DynFnMut) + /// and both of them to be treated like [`DynFnOnce`]($sus::fn::DynFnOnce). + /// + /// A `Box<`[`DynFnOnce`]($sus::fn::DynFnOnce)`>` must be moved from + /// when called, and will destroy the + /// underlying function object after the call completes. + /// + /// # Examples + /// + /// A `Box` being used as a function object: + /// ``` + /// const auto b = Box>::from( + /// &std::string_view::size); + /// sus::check(b("hello world") == 11u); + /// + /// auto mut_b = Box>::from( + /// &std::string_view::size); + /// sus::check(mut_b("hello world") == 11u); + /// + /// sus::check(sus::move(mut_b)("hello world") == 11u); + /// // The object inside `mut_b` is now destroyed. + /// ``` + /// + /// A `Box` being used as a function object. It can not be called + /// on a `const Box` as it requies the ability to mutate, and `const Box` + /// treats its inner object as const. + /// + // TODO: https://github.com/llvm/llvm-project/issues/65904 + // clang-format off + template + constexpr sus::fn::Return operator()(Args&&... args) const& + requires(T::IsDynFn && + ::sus::fn::Fn(Args...)>) + { + ::sus::check_with_message(t_, "Box used after move"); + return ::sus::fn::call(*t_, ::sus::forward(args)...); + } + + template + constexpr sus::fn::ReturnMut operator()(Args&&... args) & + requires(T::IsDynFn && + ::sus::fn::FnMut(Args...)>) + { + ::sus::check_with_message(t_, "Box used after move"); + return ::sus::fn::call_mut(*t_, ::sus::forward(args)...); + } + template - constexpr sus::fn::Return operator()( - Args&&... args) const noexcept - requires( - std::same_as(Args...)>>) + constexpr sus::fn::ReturnOnce operator()(Args&&... args) && + requires(T::IsDynFn && // + ::sus::fn::FnOnce(Args...)>) { ::sus::check_with_message(t_, "Box used after move"); struct Cleanup { - constexpr ~Cleanup() noexcept { delete t_; } + constexpr ~Cleanup() noexcept { delete t; } T* t; }; - auto cleanup = Cleanup(::sus::mem::replace(t_, nullptr)); - return ::sus::fn::call(*cleanup.t, ::sus::forward(args)...); + return ::sus::fn::call_once( + sus::move(*Cleanup(::sus::mem::replace(t_, nullptr)).t), + ::sus::forward(args)...); } + ; + // clang-format on private: enum FromPointer { FROM_POINTER }; diff --git a/sus/boxed/box_unittest.cc b/sus/boxed/box_unittest.cc index f51b48d71..aab2d639f 100644 --- a/sus/boxed/box_unittest.cc +++ b/sus/boxed/box_unittest.cc @@ -305,6 +305,32 @@ TEST(BoxDynError, FromString) { } } +TEST(BoxDynFn, Example_Call) { + { + const auto b = Box>::from( + &std::string_view::size); + sus::check(b("hello world") == 11u); + + auto mut_b = Box>::from( + &std::string_view::size); + sus::check(mut_b("hello world") == 11u); + + sus::check(sus::move(mut_b)("hello world") == 11u); + // The object inside `mut_b` is now destroyed. + } + { + auto b = Box>::from( + &std::string_view::size); + EXPECT_EQ((*b)("hello world"), 11u); + EXPECT_EQ(b("hello world"), 11u); + } + { + auto b = Box>::from( + &std::string_view::size); + EXPECT_EQ(sus::move(b)("hello world"), 11u); + } +} + TEST(Box, fmt) { static_assert(fmt::is_formattable, char>::value); EXPECT_EQ(fmt::format("{}", Box(12345)), "12345"); diff --git a/sus/error/error.h b/sus/error/error.h index b96f087cc..1179e4c71 100644 --- a/sus/error/error.h +++ b/sus/error/error.h @@ -426,9 +426,7 @@ concept Error = requires(const T& t) { template struct DynErrorTyped; -/// A type-erased [`Error`]($sus::error::Error) object -/// which satisfies [`DynConcept`]($sus::boxed::DynConcept) -/// for all [`Error`]($sus::error::Error)s. +/// A type-erased [`Error`]($sus::error::Error) object. /// /// `DynError` also satisfies [`Error`]($sus::error::Error) itself. /// @@ -443,6 +441,9 @@ struct DynErrorTyped; /// and it can not be moved similar to /// [`Pin`](https://doc.rust-lang.org/std/pin/struct.Pin.html) types in /// Rust. +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. struct DynError { /// Forwards to the [`Error`]($sus::error::Error) implementation of `E`. virtual std::string display() const noexcept = 0; diff --git a/sus/fn/fn_dyn.h b/sus/fn/fn_dyn.h index bee46ec70..2244c9390 100644 --- a/sus/fn/fn_dyn.h +++ b/sus/fn/fn_dyn.h @@ -40,6 +40,9 @@ struct DynFnOnceTyped; /// A type-erased object which satisifies the concept [`Fn`]( /// $sus::fn::Fn). +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. template struct DynFn { template @@ -47,6 +50,8 @@ struct DynFn { template using DynTyped = DynFnTyped; + static constexpr bool IsDynFn = true; + DynFn() = default; virtual ~DynFn() = default; DynFn(DynFn&&) = delete; @@ -64,7 +69,7 @@ struct DynFnTyped final : public DynFn { constexpr DynFnTyped(Store&& c) : c_(sus::forward(c)) {} R operator()(Args&&... args) const override { - return c_(::sus::forward(args)...); + return sus::fn::call(c_, ::sus::forward(args)...); } private: @@ -73,6 +78,9 @@ struct DynFnTyped final : public DynFn { /// A type-erased object which satisifies the concept [`FnMut`]( /// $sus::fn::FnMut). +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. template struct DynFnMut { template @@ -80,6 +88,8 @@ struct DynFnMut { template using DynTyped = DynFnMutTyped; + static constexpr bool IsDynFn = true; + DynFnMut() = default; virtual ~DynFnMut() = default; DynFnMut(DynFnMut&&) = delete; @@ -97,7 +107,7 @@ struct DynFnMutTyped final : public DynFnMut { constexpr DynFnMutTyped(Store&& c) : c_(sus::forward(c)) {} R operator()(Args&&... args) override { - return c_(::sus::forward(args)...); + return call_mut(c_, ::sus::forward(args)...); } private: @@ -106,6 +116,9 @@ struct DynFnMutTyped final : public DynFnMut { /// A type-erased object which satisifies the concept [`FnOnce`]( /// $sus::fn::FnOnce). +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types. template struct DynFnOnce { template @@ -113,6 +126,8 @@ struct DynFnOnce { template using DynTyped = DynFnOnceTyped; + static constexpr bool IsDynFn = true; + DynFnOnce() = default; virtual ~DynFnOnce() = default; DynFnOnce(DynFnOnce&&) = delete; @@ -130,7 +145,7 @@ struct DynFnOnceTyped final : public DynFnOnce { constexpr DynFnOnceTyped(Store&& c) : c_(sus::forward(c)) {} R operator()(Args&&... args) && override { - return c_(::sus::forward(args)...); + return call_once(::sus::move(c_), ::sus::forward(args)...); } private: diff --git a/sus/fn/fn_dyn_unittest.cc b/sus/fn/fn_dyn_unittest.cc index 0c2c72167..29ccc9433 100644 --- a/sus/fn/fn_dyn_unittest.cc +++ b/sus/fn/fn_dyn_unittest.cc @@ -30,7 +30,7 @@ TEST(FnDyn, Fn) { } TEST(FnDyn, FnBox) { - auto x = [](sus::Box> f) { return call(*f, 1, 2); }; + auto x = [](sus::Box> f) { return f(1, 2); }; i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); EXPECT_EQ(c, 1 + 2); i32 d = x(sus::into([](i32 a, i32 b) { return a * b; })); @@ -48,9 +48,7 @@ TEST(FnMutDyn, FnMut) { } TEST(FnMutDyn, FnMutBox) { - auto x = [](sus::Box> f) { - return call_mut(*f, 1, 2); - }; + auto x = [](sus::Box> f) { return f(1, 2); }; i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); EXPECT_EQ(c, 1 + 2); i32 d = x(sus::into([](i32 a, i32 b) { return a * b; })); @@ -71,9 +69,7 @@ TEST(FnDyn, FnOnce) { TEST(FnOnceDyn, FnOnceBox) { auto x = [](sus::Box> f) { - return sus::move(f).consume([](auto&& dynfn) { - return ::sus::fn::call_once(sus::move(dynfn), 1, 2); - }); + return sus::move(f)(1, 2); }; i32 c = x(sus::into([](i32 a, i32 b) { return a + b; })); EXPECT_EQ(c, 1 + 2); From 2ee325d8c8faa14bd931e0a4b399071b13808059 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:19:51 -0400 Subject: [PATCH 19/31] Add some missing template keywords --- sus/iter/iterator_defn.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sus/iter/iterator_defn.h b/sus/iter/iterator_defn.h index c6f46ab7f..5de27dec9 100644 --- a/sus/iter/iterator_defn.h +++ b/sus/iter/iterator_defn.h @@ -1515,12 +1515,12 @@ constexpr Option IteratorBase::max_by_key( // Compares just the keys. auto compare = [](const sus::Tuple& x, const sus::Tuple& y) { - return x.at<0>() <=> y.at<0>(); + return x.template at<0>() <=> y.template at<0>(); }; return static_cast(*this).map(key(fn)).max_by(compare).map( [](auto&& key_item) -> Item { - return sus::move(key_item).into_inner<1>(); + return sus::move(key_item).template into_inner<1>(); }); } @@ -1568,12 +1568,12 @@ constexpr Option IteratorBase::min_by_key( // Compares just the keys. auto compare = [](const sus::Tuple& x, const sus::Tuple& y) { - return x.at<0>() <=> y.at<0>(); + return x.template at<0>() <=> y.template at<0>(); }; return static_cast(*this).map(key(fn)).min_by(compare).map( [](auto&& key_item) -> Item { - return sus::move(key_item).into_inner<1>(); + return sus::move(key_item).template into_inner<1>(); }); } From b4c2d8f7d9baa1e7f9a99dc65d0cb1f46eb91f49 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:21:25 -0400 Subject: [PATCH 20/31] Fix from overload tags to fix links --- sus/boxed/box.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sus/boxed/box.h b/sus/boxed/box.h index add44c02c..aa893376f 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -138,7 +138,7 @@ class [[sus_trivial_abi]] Box final { /// Satisfies the [`From`]($sus::construct::From) concept for /// [`Box`]($sus::boxed::Box) where `U` is convertible to `T` and is not a /// subclass of `T`. - /// #[doc.overloads=from.convert] + /// #[doc.overloads=convert] template U> requires(!::sus::ptr::SameOrSubclassOf) constexpr static Box from(U u) noexcept @@ -152,7 +152,7 @@ class [[sus_trivial_abi]] Box final { /// /// Satisfies the [`From`]($sus::construct::From) concept for /// [`Box`]($sus::boxed::Box) where U is `T` or a subclass of `T`. - /// #[doc.overloads=from.inherit] + /// #[doc.overloads=inherit] template requires(::sus::ptr::SameOrSubclassOf) constexpr static Box from(U u) noexcept @@ -246,6 +246,8 @@ class [[sus_trivial_abi]] Box final { /// /// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of /// concept-satisfying types. + /// + /// #[doc.overloads=dync] template constexpr static Box from(U u) noexcept requires(std::same_as> && // @@ -265,7 +267,7 @@ class [[sus_trivial_abi]] Box final { /// conversion moves and type-erases the `std::string` into a heap-alloocated /// [`DynError`]($sus::error::DynError). /// - /// #[doc.overloads=dynerror.from.string] + /// #[doc.overloads=dynerror] constexpr static Box from(std::string s) noexcept requires(std::same_as) { From 3ee7b6fbe45d187ed212b95f264c27d00fd980a9 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:30:29 -0400 Subject: [PATCH 21/31] Improve Box docs --- sus/boxed/box.h | 35 ++++++++++++++++++++++++++++++----- sus/boxed/box_unittest.cc | 16 ++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/sus/boxed/box.h b/sus/boxed/box.h index aa893376f..08a9d192c 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -352,9 +352,12 @@ class [[sus_trivial_abi]] Box final { /// and can be used as a function type. It will forward the call through /// to the inner type. /// - /// `Box<`[`DynFn`]($sus::fn::DynFn)`>` satisfies [`Fn`]($sus::fn::Fn), - /// `Box<`[`DynFnMut`]($sus::fn::DynFnMut)`>` satisfies [`FnMut`]($sus::fn::FnMut), - /// and `Box<`[`DynFnOnce`]($sus::fn::DynFnOnce)`>` satisfies [`FnOnce`]($sus::fn::FnOnce). + /// * `Box<`[`DynFn`]($sus::fn::DynFn)`>` satisfies [`Fn`]($sus::fn::Fn). + /// * `Box<`[`DynFnMut`]($sus::fn::DynFnMut)`>` satisfies + /// [`FnMut`]($sus::fn::FnMut). + /// * `Box<`[`DynFnOnce`]($sus::fn::DynFnOnce)`>` satisfies + /// [`FnOnce`]($sus::fn::FnOnce). + /// /// The usual compatibility rules apply, allowing [`DynFn`]($sus::fn::DynFn) /// to be treated like [`DynFnMut`]($sus::fn::DynFnMut) /// and both of them to be treated like [`DynFnOnce`]($sus::fn::DynFnOnce). @@ -379,10 +382,32 @@ class [[sus_trivial_abi]] Box final { /// // The object inside `mut_b` is now destroyed. /// ``` /// - /// A `Box` being used as a function object. It can not be called + /// A `Box` being used as a function object. It can not be called /// on a `const Box` as it requies the ability to mutate, and `const Box` - /// treats its inner object as const. + /// treats its inner object as const: + /// ``` + /// auto mut_b = Box>::from( + /// &std::string_view::size); + /// sus::check(mut_b("hello world") == 11u); + /// + /// sus::check(sus::move(mut_b)("hello world") == 11u); + /// // The object inside `mut_b` is now destroyed. + /// ``` + /// + /// A `Box` being used as a function object. It must be an rvalue + /// (either a return from a call or moved with [`move`]($sus::mem::move)) + /// to call through it: + /// ``` + /// auto b = Box>::from( + /// &std::string_view::size); + /// sus::check(sus::move(b)("hello world") == 11u); /// + /// auto x = [] { + /// return Box>::from( + /// &std::string_view::size); + /// }; + /// sus::check(x()("hello world") == 11u); + /// ``` // TODO: https://github.com/llvm/llvm-project/issues/65904 // clang-format off template diff --git a/sus/boxed/box_unittest.cc b/sus/boxed/box_unittest.cc index aab2d639f..4c9715504 100644 --- a/sus/boxed/box_unittest.cc +++ b/sus/boxed/box_unittest.cc @@ -319,15 +319,23 @@ TEST(BoxDynFn, Example_Call) { // The object inside `mut_b` is now destroyed. } { - auto b = Box>::from( + auto mut_b = Box>::from( &std::string_view::size); - EXPECT_EQ((*b)("hello world"), 11u); - EXPECT_EQ(b("hello world"), 11u); + sus::check(mut_b("hello world") == 11u); + + sus::check(sus::move(mut_b)("hello world") == 11u); + // The object inside `mut_b` is now destroyed. } { auto b = Box>::from( &std::string_view::size); - EXPECT_EQ(sus::move(b)("hello world"), 11u); + sus::check(sus::move(b)("hello world") == 11u); + + auto x = [] { + return Box>::from( + &std::string_view::size); + }; + sus::check(x()("hello world") == 11u); } } From 6de324dbb8eddb7d99b72c1b3325d270d086ea90 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:43:42 -0400 Subject: [PATCH 22/31] Fix type in codelink --- sus/boxed/box.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sus/boxed/box.h b/sus/boxed/box.h index 08a9d192c..bbf02b726 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -60,7 +60,7 @@ namespace sus::boxed { /// * Supports up-casting (TODO: and [`down-casting`]( /// https://github.com/chromium/subspace/issues/333)) without leaving /// requiring a native pointer to be pulled out of the `Box`. -/// * No native arrays, `Box` can hold [`Array`]($sus::collection::Array) or +/// * No native arrays, `Box` can hold [`Array`]($sus::collections::Array) or /// [`std::array`](https://en.cppreference.com/w/cpp/container/array) but /// avoids API complexity for supporting pointers and native arrays (which /// decay to pointers) by rejecting native arrays. From dd02c0e6202ca557b9fd7ac322cc9ec2906a0c14 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 15:59:04 -0400 Subject: [PATCH 23/31] Make all overloads with the same parameters but different method qualifiers appear in subdoc --- subdoc/gen_tests/struct-complex/S.html | 28 +++++++++++++++++++++ subdoc/gen_tests/struct-complex/test.cc | 15 +++++++---- subdoc/lib/visit.cc | 33 +++++++++++++++++++------ tools/run_subdoc.bat | 2 +- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/subdoc/gen_tests/struct-complex/S.html b/subdoc/gen_tests/struct-complex/S.html index f1b8690d5..ccbbd8fc2 100644 --- a/subdoc/gen_tests/struct-complex/S.html +++ b/subdoc/gen_tests/struct-complex/S.html @@ -73,6 +73,12 @@
  • void_method
  • +
  • + Operators +
  • +
  • + operator() +
  • Data Members
  • @@ -190,6 +196,28 @@ +
    +
    + Operators +
    +
    +
    +
    +
    auto operator()() const& -> int
    +
    +
    +
    auto operator()() & -> bool
    +
    +
    +
    auto operator()() && -> float
    +
    +
    +
    +

    Call operator with two overloads.

    + +
    +
    +
    Data Members diff --git a/subdoc/gen_tests/struct-complex/test.cc b/subdoc/gen_tests/struct-complex/test.cc index 55a61e2fe..e27b5ee25 100644 --- a/subdoc/gen_tests/struct-complex/test.cc +++ b/subdoc/gen_tests/struct-complex/test.cc @@ -6,16 +6,16 @@ struct OtherType {}; /// Comment headline S struct S { /// Comment headline void_method - void void_method() const& {} + void void_method() const&; /// Comment headline static_type_method - static OtherType static_type_method() {} + static OtherType static_type_method(); /// Comment headline type_method - OtherType type_method() {} + OtherType type_method(); /// Comment headline static_bool_method - static bool static_bool_method() {} + static bool static_bool_method(); // Overload should be grouped with the other int_method(). - void int_method() volatile {} + void int_method() volatile; /// Comment headline type_field const OtherType type_field; @@ -25,4 +25,9 @@ struct S { bool bool_field; /// Comment headline static_bool_member static bool static_bool_member; + + /// Call operator with two overloads. + int operator()() const&; + bool operator()() &; + float operator()() &&; }; diff --git a/subdoc/lib/visit.cc b/subdoc/lib/visit.cc index a24da4a88..7eb64b60e 100644 --- a/subdoc/lib/visit.cc +++ b/subdoc/lib/visit.cc @@ -517,9 +517,10 @@ class Visitor : public clang::RecursiveASTVisitor { ->getNameAsString(); } else if (auto* convdecl = clang::dyn_cast(decl)) { - Type t = build_local_type(convdecl->getReturnType(), - convdecl->getASTContext().getSourceManager(), - preprocessor_); + Type t = build_local_type( + convdecl->getReturnType(), + convdecl->getASTContext().getSourceManager(), + preprocessor_); return std::string("operator ") + t.name; } else { return decl->getNameAsString(); @@ -538,10 +539,27 @@ class Visitor : public clang::RecursiveASTVisitor { sus::Option overload_set = sus::clone(comment.attrs.overload_set); - std::string signature; + std::string signature = "("; for (clang::ParmVarDecl* p : decl->parameters()) { signature += p->getOriginalType().getAsString(); } + signature += ")"; + if (auto* mdecl = clang::dyn_cast(decl)) { + // Prevent a parameter and return qualifier from possibly being + // confused for eachother in the string by putting a delimiter in + // here that can't appear in the parameter list. + signature += " -> "; + signature += mdecl->getMethodQualifiers().getAsString(); + switch (mdecl->getRefQualifier()) { + case clang::RefQualifierKind::RQ_None: break; + case clang::RefQualifierKind::RQ_LValue: + signature += "&"; + break; + case clang::RefQualifierKind::RQ_RValue: + signature += "&&"; + break; + } + } auto linked_return_type = LinkedType::with_type( build_local_type(decl->getReturnType(), @@ -561,7 +579,7 @@ class Visitor : public clang::RecursiveASTVisitor { decl->getASTContext().getSourceManager().getFileOffset( decl->getLocation())); - if (clang::isa(decl)) { + if (auto* mdecl = clang::dyn_cast(decl)) { sus::check(clang::isa(context)); // TODO: It's possible to overload a method in a base class. What @@ -572,7 +590,6 @@ class Visitor : public clang::RecursiveASTVisitor { if (sus::Option parent = docs_db_.find_record_mut( clang::cast(context)); parent.is_some()) { - auto* mdecl = clang::cast(decl); fe.overloads[0u].method = sus::some(MethodSpecific{ .is_static = mdecl->isStatic(), .is_volatile = mdecl->isVolatile(), @@ -644,7 +661,9 @@ class Visitor : public clang::RecursiveASTVisitor { // We already visited this thing, from another translation unit. add_overload = false; } else { - add_overload = true; + // The comment is ambiguous, there's another comment for the same overload + // set. This is an error. + add_overload = false; auto& ast_cx = decl->getASTContext(); const auto& old_comment_element = it->second; ast_cx.getDiagnostics() diff --git a/tools/run_subdoc.bat b/tools/run_subdoc.bat index 25cd41264..0767f554e 100644 --- a/tools/run_subdoc.bat +++ b/tools/run_subdoc.bat @@ -9,4 +9,4 @@ out\subdoc\subdoc -p out --out docs ^ --project-logo logo.png ^ --project-name Subspace ^ --ignore-bad-code-links ^ - subspace/sus/num/uptr + subspace/sus/num/uptr sus/boxed/box_ From b9c3d3a4c1383833ada4e3da910cee4ce5a0ba52 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 16:24:13 -0400 Subject: [PATCH 24/31] Change FnMut concept to use `requires (F&)` not `requires (F)` On clang 16 (but not after for some reason) the concept will not match any type `F` that is pure virtual. This prevented FnMut from matching against DynFnMut. More precisely: ``` template concept C = requires(T t) { t.foo(); } ``` This won't match a pure virtual `T` with a mutable `foo()` on clang 16. It works on 17+ and with GCC 13 and MSVC (at least one version). `requires(T& t)` does the right thing universally, as in: ``` template concept C = requires(T& t) { t.foo(); } ``` Add a rule to STYLE.md and update all concepts that wrote a value type in a rule to be reference types. --- STYLE.md | 6 ++++++ sus/choice/choice_unittest.cc | 6 +++--- sus/collections/array_unittest.cc | 4 ++-- sus/collections/slice_unittest.cc | 4 ++-- sus/collections/vec_unittest.cc | 4 ++-- sus/fn/__private/callable_types.h | 4 ++-- sus/fn/__private/signature.h | 2 +- sus/fn/callable.h | 10 +++++----- sus/mem/move_unittest.cc | 2 +- sus/ops/range.h | 2 +- sus/ptr/nonnull_unittest.cc | 4 ++-- 11 files changed, 27 insertions(+), 21 deletions(-) diff --git a/STYLE.md b/STYLE.md index 1a3ad8358..0dfa46741 100644 --- a/STYLE.md +++ b/STYLE.md @@ -60,3 +60,9 @@ footguns, crashes, bugs, and UB. * But don't follow the IWYU rules _inside_ the library. We include the minimal internal headers internally to reduce the amount of textual parsing needed to satisfy including one public header. +1. All types in requires clauses should be references or pointers. + * For mutable access always write it as `requires(T& t)` instead of + `requires (T t)`, as the latter will not match pure virtual types in + clang-16. + * Similarly, for const usage, write `requires (const T& t)` instead of + `requires (const T t)`. diff --git a/sus/choice/choice_unittest.cc b/sus/choice/choice_unittest.cc index d796bc38f..bfb47156e 100644 --- a/sus/choice/choice_unittest.cc +++ b/sus/choice/choice_unittest.cc @@ -619,12 +619,12 @@ TEST(Choice, Clone) { } template -concept CanGetRef = requires(T t) { t.template as(); }; +concept CanGetRef = requires(T& t) { t.template as(); }; template -concept CanGetMut = requires(T t) { t.template as_mut(); }; +concept CanGetMut = requires(T& t) { t.template as_mut(); }; template concept CanIntoInner = - requires(T t) { sus::move(t).template into_inner(); }; + requires(T& t) { sus::move(t).template into_inner(); }; TEST(Choice, Eq) { struct NotEq {}; diff --git a/sus/collections/array_unittest.cc b/sus/collections/array_unittest.cc index 911520415..bb6bce583 100644 --- a/sus/collections/array_unittest.cc +++ b/sus/collections/array_unittest.cc @@ -265,7 +265,7 @@ TEST(Array, GetUnchecked) { } template -concept HasGetMut = requires(T t, U u) { t.get_mut(u); }; +concept HasGetMut = requires(T& t, const U& u) { t.get_mut(u); }; // get_mut() is only available for mutable arrays. static_assert(!HasGetMut, usize>); @@ -289,7 +289,7 @@ TEST(Array, GetMut) { template concept HasGetUncheckedMut = - requires(T t, U u) { t.get_unchecked_mut(unsafe_fn, u); }; + requires(T& t, const U& u) { t.get_unchecked_mut(unsafe_fn, u); }; // get_unchecked_mut() is only available for mutable arrays. static_assert(!HasGetUncheckedMut, usize>); diff --git a/sus/collections/slice_unittest.cc b/sus/collections/slice_unittest.cc index 5e0fa3524..deb9279a2 100644 --- a/sus/collections/slice_unittest.cc +++ b/sus/collections/slice_unittest.cc @@ -105,7 +105,7 @@ TEST(Slice, Get) { } template -concept HasGetMut = requires(T t, U u) { t.get_mut(u); }; +concept HasGetMut = requires(T& t, const U& u) { t.get_mut(u); }; // get_mut() is only available for mutable slices. A "const" mutable slice can // still give mutable access as the mutability is encoded in the type. @@ -138,7 +138,7 @@ TEST(Slice, GetUnchecked) { template concept HasGetUncheckedMut = - requires(T t, U u) { t.get_unchecked_mut(unsafe_fn, u); }; + requires(T& t, const U& u) { t.get_unchecked_mut(unsafe_fn, u); }; // get_unchecked_mut() is only available for mutable slices of mutable types. static_assert(!HasGetUncheckedMut, usize>); diff --git a/sus/collections/vec_unittest.cc b/sus/collections/vec_unittest.cc index 84cdd6c61..0e901d2a5 100644 --- a/sus/collections/vec_unittest.cc +++ b/sus/collections/vec_unittest.cc @@ -211,7 +211,7 @@ TEST(Vec, GetMut) { } template -concept HasGetMut = requires(T t, U u) { t.get_mut(u); }; +concept HasGetMut = requires(T& t, const U& u) { t.get_mut(u); }; // get_mut() is only available for mutable Vec. static_assert(!HasGetMut, usize>); @@ -233,7 +233,7 @@ TEST(Vec, GetUncheckedMut) { template concept HasGetUncheckedMut = - requires(T t, U u) { t.get_unchecked_mut(unsafe_fn, u); }; + requires(T& t, const U& u) { t.get_unchecked_mut(unsafe_fn, u); }; // get_unchecked_mut() is only available for mutable Vec. static_assert(!HasGetUncheckedMut, usize>); diff --git a/sus/fn/__private/callable_types.h b/sus/fn/__private/callable_types.h index 1d02212c3..fe2cf9f39 100644 --- a/sus/fn/__private/callable_types.h +++ b/sus/fn/__private/callable_types.h @@ -29,7 +29,7 @@ namespace sus::fn::__private { /// and will return a value that can be stored as `R`. template concept FunctionPointer = - std::is_pointer_v> && requires(F f, Args... args) { + std::is_pointer_v> && requires(F& f, Args... args) { { std::invoke(*f, args...) } -> std::convertible_to; }; @@ -41,7 +41,7 @@ concept IsFunctionPointer = /// Whether a functor `T` can convert to a function pointer, typically this /// means it's a captureless lambda. template -concept ConvertsToFunctionPointer = requires(F f) { +concept ConvertsToFunctionPointer = requires(F& f) { { +f } -> IsFunctionPointer; }; diff --git a/sus/fn/__private/signature.h b/sus/fn/__private/signature.h index 9ffc76491..45cd1e66b 100644 --- a/sus/fn/__private/signature.h +++ b/sus/fn/__private/signature.h @@ -88,7 +88,7 @@ struct InvokedFnMut { }; template - requires requires(F f) { + requires requires(F& f) { { std::invoke(f, std::declval()...) }; } struct InvokedFnMut> { diff --git a/sus/fn/callable.h b/sus/fn/callable.h index 5e59a720e..b996bac90 100644 --- a/sus/fn/callable.h +++ b/sus/fn/callable.h @@ -25,7 +25,7 @@ namespace sus::fn::callable { template -concept FunctionPointer = requires(T t) { +concept FunctionPointer = requires(const T& t) { { std::is_pointer_v }; }; @@ -42,7 +42,7 @@ concept FunctionPointerReturns = ( std::convertible_to, R> && // We verify that `T` can be stored in a function pointer. Types must match // more strictly than just for invoking it. - requires (R(*p)(Args...), T t) { + requires (R(*p)(Args...), T& t) { { p = t }; } ); @@ -63,7 +63,7 @@ concept FunctionPointerMatches = ( FunctionPointer && // We verify that `T` can be stored in a function pointer. Types must match // more strictly than just for invoking it. - requires (R(*p)(Args...), T t) { + requires (R(*p)(Args...), T& t) { { p = t }; } ); @@ -73,7 +73,7 @@ concept FunctionPointerMatches = ( template concept FunctionPointerWith = ( FunctionPointer && - requires (T t, Args&&... args) { + requires (T& t, Args&&... args) { std::invoke(t, ::sus::mem::forward(args)...); } ); @@ -90,7 +90,7 @@ inline constexpr bool callable_const(R (T::*)(Args...) const) { template concept CallableObjectReturnsOnce = - !FunctionPointer && requires(T t, Args&&... args) { + !FunctionPointer && requires(T& t, Args&&... args) { { std::invoke(static_cast(t), ::sus::mem::forward(args)...) } -> std::convertible_to; diff --git a/sus/mem/move_unittest.cc b/sus/mem/move_unittest.cc index 657123141..46e619620 100644 --- a/sus/mem/move_unittest.cc +++ b/sus/mem/move_unittest.cc @@ -28,7 +28,7 @@ struct NonTrivial { // clang-format off template -concept can_move = requires(T t) { +concept can_move = requires(T& t) { { move(t) }; }; // clang-format on diff --git a/sus/ops/range.h b/sus/ops/range.h index 1bedb475d..715a6fd19 100644 --- a/sus/ops/range.h +++ b/sus/ops/range.h @@ -28,7 +28,7 @@ namespace sus::ops { /// `RangeBounds` is implemented by Subspace's range types, and produced by /// range syntax like `..`, `a..`, `..b`, `..=c`, `d..e`, or `f..=g`. template -concept RangeBounds = requires(const T& t, T v, I i) { +concept RangeBounds = requires(const T& t, T& v, const I& i) { { t.start_bound() } -> std::same_as<::sus::option::Option>; { t.end_bound() } -> std::same_as<::sus::option::Option>; // Rvalue overloads must not exist as they would return a reference to a diff --git a/sus/ptr/nonnull_unittest.cc b/sus/ptr/nonnull_unittest.cc index 5de15b9a5..593c337e4 100644 --- a/sus/ptr/nonnull_unittest.cc +++ b/sus/ptr/nonnull_unittest.cc @@ -153,7 +153,7 @@ TEST(NonNull, AsRef) { } template -concept AsRefMutExists = requires(T t) { t.as_mut(); }; +concept AsRefMutExists = requires(T& t) { t.as_mut(); }; TEST(NonNull, AsRefMut) { int i = 1; @@ -180,7 +180,7 @@ TEST(NonNull, AsPtr) { } template -concept AsPtrMutExists = requires(T t) { t.as_mut_ptr(); }; +concept AsPtrMutExists = requires(T& t) { t.as_mut_ptr(); }; TEST(NonNull, AsPtrMut) { int i = 1; From e4833c980e725af03ba29ac866aec6df0e332cf4 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 16:52:00 -0400 Subject: [PATCH 25/31] Add docs for the boxed namespace --- sus/boxed/__private/string_error.h | 2 ++ sus/boxed/box.h | 1 + sus/boxed/boxed.h | 25 +++++++++++++++++++++++++ sus/boxed/dyn.h | 17 ++++++++++++----- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 sus/boxed/boxed.h diff --git a/sus/boxed/__private/string_error.h b/sus/boxed/__private/string_error.h index 55b19bbd6..7ff6b5c6c 100644 --- a/sus/boxed/__private/string_error.h +++ b/sus/boxed/__private/string_error.h @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// IWYU pragma: private +// IWYU pragma: friend "sus/.*" #pragma once #include "sus/error/error.h" diff --git a/sus/boxed/box.h b/sus/boxed/box.h index bbf02b726..e27664570 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -19,6 +19,7 @@ #include #include "sus/boxed/__private/string_error.h" +#include "sus/boxed/boxed.h" // namespace docs. #include "sus/error/error.h" #include "sus/fn/fn_concepts.h" #include "sus/fn/fn_dyn.h" diff --git a/sus/boxed/boxed.h b/sus/boxed/boxed.h new file mode 100644 index 000000000..646b0e954 --- /dev/null +++ b/sus/boxed/boxed.h @@ -0,0 +1,25 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IWYU pragma: private +// IWYU pragma: friend "sus/boxed/.*" +#pragma once + +namespace sus { + +/// The [`Box`]($sus::boxed::Box) type for heap allocation and other +/// tools for [type-erasure of concepts]($sus::boxed::DynConcept). +namespace boxed {} + +} // namespace sus diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h index 92eb3ef5b..a81472ded 100644 --- a/sus/boxed/dyn.h +++ b/sus/boxed/dyn.h @@ -17,6 +17,7 @@ #include #include +#include "sus/boxed/boxed.h" // namespace docs. #include "sus/macros/lifetimebound.h" #include "sus/mem/forward.h" #include "sus/mem/move.h" @@ -64,7 +65,8 @@ namespace sus::boxed { /// The [`dyn`]($sus::boxed::dyn) function, and its returned type should only /// appear in function call arguments. The returned type /// [`Dyn`]($sus::boxed::Dyn) can not be moved, and it only converts a -/// reference to a `ConcreteT` into a reference to `DynC`. +/// reference to a type `T&` into a reference `DynC&` that also satisfies `C` +/// but without templates. /// This can be used to call functions that accept a type-erased concept /// by reference, such as with a `const DynC&` parameter. /// @@ -233,8 +235,13 @@ concept DynConcept = requires { requires !std::is_move_assignable_v; }; -/// Type erases a reference to a `ConcreteT` which satisfies a concept `C`, -/// into a reference to `DynC`. Returned from [`dyn`]($sus::boxed::dyn). +/// A type erasure of a type satisfying a concept, which can be used as a +/// reference without heap allocation or templates. +/// Returned from [`dyn`]($sus::boxed::dyn). +/// +/// This type is similar to `Box` for purposes of type erasure but does +/// not require heap allocation, +/// and it converts directly to a reference to the erased type. /// /// Use [`dyn`]($sus::boxed::dyn) to convert to a `DynC` reference instead of /// constructing this type directly. @@ -310,8 +317,8 @@ class [[nodiscard]] Dyn { typename DynC::template DynTyped dyn_; }; -/// Type erases a reference to a `ConcreteT` which satisfies a concept `C`, -/// into a reference to `DynC`. +/// Type erases a reference to a type `T&` which satisfies a concept `C`, +/// into a reference `DynC&` that also satisfies `C` but without templates. /// /// Use `dyn(x)` to convert a mutable reference to `x` into `DynC&` and /// `dyn(x)` to convert a const or mutable reference to `x` into From fcbb850d0718e9649c06a5a32edddc4cd3d4dd8e Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 17:12:31 -0400 Subject: [PATCH 26/31] Remove FnMutBox from OnceWith and make it constexpr --- sus/iter/once_with.h | 54 +++++++++++++++++++++------------- sus/iter/once_with_unittest.cc | 4 +++ 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/sus/iter/once_with.h b/sus/iter/once_with.h index 39b2f80a6..e4fe09eda 100644 --- a/sus/iter/once_with.h +++ b/sus/iter/once_with.h @@ -23,7 +23,7 @@ namespace sus::iter { using ::sus::option::Option; -template +template class OnceWith; /// Creates an iterator that lazily generates a value exactly once by invoking @@ -41,45 +41,59 @@ class OnceWith; /// auto ow = sus::iter::once_with([]() { return 3_u16; }); /// sus::check(ow.next().unwrap() == 3_u16); /// ``` -template -inline OnceWith once_with(::sus::fn::FnMutBox gen) noexcept { - return OnceWith(::sus::move(gen)); +template GenFn> +constexpr inline OnceWith once_with(GenFn gen) noexcept { + return OnceWith(::sus::move(gen)); } /// An Iterator that walks over at most a single Item. -template -class [[nodiscard]] [[sus_trivial_abi]] OnceWith final - : public IteratorBase, ItemT> { +template +class [[nodiscard]] OnceWith final + : public IteratorBase, ItemT> { public: using Item = ItemT; + // Type is Move and (can be) Clone. + OnceWith(OnceWith&&) = default; + OnceWith& operator=(OnceWith&&) = default; + + // sus::mem::Clone trait. + constexpr OnceWith clone() const noexcept + requires(::sus::mem::Clone) + { + return OnceWith(sus::clone(gen_)); + } + // sus::iter::Iterator trait. - Option next() noexcept { - return gen_.take().map([](auto&& gen) { return ::sus::fn::call_mut(gen); }); + constexpr Option next() noexcept { + return gen_.take().map( + [](auto&& gen) -> Item { return ::sus::fn::call_mut(gen); }); } /// sus::iter::Iterator trait. - SizeHint size_hint() const noexcept { + constexpr SizeHint size_hint() const noexcept { ::sus::num::usize rem = gen_.is_some() ? 1u : 0u; return SizeHint(rem, ::sus::Option<::sus::num::usize>(rem)); } // sus::iter::DoubleEndedIterator trait. - Option next_back() noexcept { - return gen_.take().map([](auto&& gen) { return ::sus::fn::call_mut(gen); }); + constexpr Option next_back() noexcept { + return gen_.take().map( + [](auto&& gen) -> Item { return ::sus::fn::call_mut(gen); }); } // sus::iter::ExactSizeIterator trait. - usize exact_size_hint() const noexcept { return gen_.is_some() ? 1u : 0u; } + constexpr usize exact_size_hint() const noexcept { + return gen_.is_some() ? 1u : 0u; + } private: - friend OnceWith sus::iter::once_with( - ::sus::fn::FnMutBox gen) noexcept; + friend constexpr OnceWith sus::iter::once_with( + GenFn gen) noexcept; - OnceWith(::sus::fn::FnMutBox gen) - : gen_(sus::Option<::sus::fn::FnMutBox>(::sus::move(gen))) { - } + constexpr OnceWith(GenFn gen) : gen_(sus::Option(::sus::move(gen))) {} - Option<::sus::fn::FnMutBox> gen_; + Option gen_; - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(gen_)); + sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, + decltype(gen_)); }; } // namespace sus::iter diff --git a/sus/iter/once_with_unittest.cc b/sus/iter/once_with_unittest.cc index 5f575dcbf..ed186a459 100644 --- a/sus/iter/once_with_unittest.cc +++ b/sus/iter/once_with_unittest.cc @@ -44,4 +44,8 @@ TEST(OnceWith, NextBack) { EXPECT_EQ(ow.next_back(), sus::none()); } +// constexpr, and verifies that the return type can be converted to the Item +// type +static_assert(sus::iter::once_with([] { return 5; }).sum() == 5); + } // namespace From 88bbcf1354c5218b59be99b885577558b50c660d Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 17:25:41 -0400 Subject: [PATCH 27/31] Remove FnMutBox from Successors and make it constexpr Also test it more! --- sus/iter/once_with_unittest.cc | 2 +- sus/iter/repeat_with_unittest.cc | 3 ++- sus/iter/successors.h | 40 +++++++++++++++++++------------- sus/iter/successors_unittest.cc | 35 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/sus/iter/once_with_unittest.cc b/sus/iter/once_with_unittest.cc index ed186a459..d6e690b8c 100644 --- a/sus/iter/once_with_unittest.cc +++ b/sus/iter/once_with_unittest.cc @@ -45,7 +45,7 @@ TEST(OnceWith, NextBack) { } // constexpr, and verifies that the return type can be converted to the Item -// type +// type. static_assert(sus::iter::once_with([] { return 5; }).sum() == 5); } // namespace diff --git a/sus/iter/repeat_with_unittest.cc b/sus/iter/repeat_with_unittest.cc index 021706646..9dbcb1078 100644 --- a/sus/iter/repeat_with_unittest.cc +++ b/sus/iter/repeat_with_unittest.cc @@ -46,7 +46,8 @@ TEST(RepeatWith, NextBack) { EXPECT_EQ(o.next_back(), sus::some(3_u16)); } -// constexpr +// constexpr, and verifies that the return type can be converted to the Item +// type. static_assert(sus::iter::repeat_with([] { return 3; }).take(4u).sum() == 3 * 4); diff --git a/sus/iter/successors.h b/sus/iter/successors.h index 2872f6006..d9ff3455f 100644 --- a/sus/iter/successors.h +++ b/sus/iter/successors.h @@ -23,7 +23,7 @@ namespace sus::iter { using ::sus::option::Option; -template +template class Successors; /// Creates a new iterator where each successive item is computed based on the @@ -41,32 +41,42 @@ class Successors; /// sus::move(powers_of_10).collect>() == /// sus::Slice::from({1_u16, 10_u16, 100_u16, 1000_u16, 10000_u16})); /// ``` -template -inline Successors successors( - Option first, - ::sus::fn::FnMutBox(const Item&)> func) noexcept { - return Successors(::sus::move(first), ::sus::move(func)); +template (const Item&)> GenFn> +constexpr inline Successors successors(Option first, + GenFn func) noexcept { + return Successors(::sus::move(first), ::sus::move(func)); } /// An Iterator that generates each item from a function that takes the previous /// item. /// /// This type is created by `sus::iter::successors()`. -template +template class [[nodiscard]] Successors final - : public IteratorBase, ItemT> { + : public IteratorBase, ItemT> { public: using Item = ItemT; + // Type is Move and (can be) Clone. + Successors(Successors&&) = default; + Successors& operator=(Successors&&) = default; + + // sus::mem::Clone trait. + constexpr Successors clone() const noexcept + requires(::sus::mem::Clone) + { + return Successors(sus::clone(next_), sus::clone(func_)); + } + // sus::iter::Iterator trait. - Option next() noexcept { + constexpr Option next() noexcept { Option item = next_.take(); if (item.is_some()) next_ = ::sus::fn::call_mut(func_, item.as_value()); return item; } /// sus::iter::Iterator trait. - SizeHint size_hint() const noexcept { + constexpr SizeHint size_hint() const noexcept { if (next_.is_some()) return SizeHint(1u, ::sus::Option<::sus::num::usize>()); else @@ -74,16 +84,14 @@ class [[nodiscard]] Successors final } private: - friend Successors sus::iter::successors( - Option first, - ::sus::fn::FnMutBox(const Item&)> func) noexcept; + friend constexpr Successors sus::iter::successors( + Option first, GenFn func) noexcept; - Successors(Option first, - ::sus::fn::FnMutBox(const Item&)>&& func) + constexpr Successors(Option first, GenFn func) : next_(::sus::move(first)), func_(::sus::move(func)) {} Option next_; - ::sus::fn::FnMutBox(const Item&)> func_; + GenFn func_; sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn, decltype(next_), decltype(func_)); diff --git a/sus/iter/successors_unittest.cc b/sus/iter/successors_unittest.cc index ae741126f..43e7c2525 100644 --- a/sus/iter/successors_unittest.cc +++ b/sus/iter/successors_unittest.cc @@ -27,4 +27,39 @@ TEST(Successors, Example) { sus::Slice::from({1_u16, 10_u16, 100_u16, 1000_u16, 10000_u16})); } +TEST(Successors, Some) { + auto empty = sus::iter::successors( + sus::some(2), [](i32 n) -> sus::Option { return sus::some(n + 1); }); + EXPECT_EQ(empty.size_hint().lower, 1u); + EXPECT_EQ(empty.size_hint().upper, sus::none()); + EXPECT_EQ(empty.next().unwrap(), 2); + EXPECT_EQ(empty.next().unwrap(), 3); + EXPECT_EQ(empty.next().unwrap(), 4); +} + +TEST(Successors, None) { + auto empty = sus::iter::successors( + sus::none(), [](i32 n) -> sus::Option { return sus::some(n + 1); }); + EXPECT_EQ(empty.size_hint().lower, 0u); + EXPECT_EQ(empty.size_hint().upper, sus::some(0u)); + EXPECT_EQ(empty.next().is_none(), true); +} + +// constexpr, and verifies that the return type can be converted to the Item +// type. +static_assert(sus::iter::successors(sus::some(2), + [](i32 i) -> sus::Option { + return sus::some(i + 1); + }) + .take(4u) + .sum() == 2 + 3 + 4 + 5); + +// Longer iteration. +static_assert(sus::iter::successors(sus::some(2), + [](i32 i) { + return sus::Option(i); + }) + .take(100u) + .sum() == 2 * 100); + } // namespace From 54b831a10429e09297098c69f77ddfcd634ecac2 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 17:28:26 -0400 Subject: [PATCH 28/31] Remove use of FnBox in subdoc RunOptions Use Box instead. --- subdoc/lib/run_options.h | 7 +++++-- subdoc/tests/type_unittest.cc | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/subdoc/lib/run_options.h b/subdoc/lib/run_options.h index ee7f03388..9940eca22 100644 --- a/subdoc/lib/run_options.h +++ b/subdoc/lib/run_options.h @@ -17,6 +17,7 @@ #include #include "subdoc/llvm.h" +#include "sus/boxed/box.h" #include "sus/fn/fn.h" #include "sus/option/option.h" #include "sus/prelude.h" @@ -39,7 +40,8 @@ struct RunOptions { return sus::move(*this); } RunOptions set_on_tu_complete( - sus::fn::FnBox fn) && { + sus::Box> + fn) && { on_tu_complete = sus::some(sus::move(fn)); return sus::move(*this); } @@ -54,7 +56,8 @@ struct RunOptions { /// /// Used for tests to observe the AST and test subdoc methods that act on /// things from the AST. - sus::Option> + sus::Option< + sus::Box>> on_tu_complete; /// The overview markdown which will be applied as the doc comment to the /// global namespace/project overview page. This is the raw markdown text, not diff --git a/subdoc/tests/type_unittest.cc b/subdoc/tests/type_unittest.cc index 7c5a173cc..2fc418d89 100644 --- a/subdoc/tests/type_unittest.cc +++ b/subdoc/tests/type_unittest.cc @@ -83,7 +83,7 @@ struct SubDocTypeTest : public SubDocTest { clang::Preprocessor&)> auto body) { auto opts = subdoc::RunOptions() // .set_show_progress(false) // - .set_on_tu_complete(body); + .set_on_tu_complete(sus::move_into(body)); auto result = run_code_with_options(opts, code); ASSERT_TRUE(result.is_ok()); } From 72968c4394361c33439978b3b3496aed3cd6c998 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 20:14:35 -0400 Subject: [PATCH 29/31] Remove the FnRef and FnBox types These are superceded by the general concept-type-erasure mechanism of DynConcept and the DynFn/Mut/Once type-erasure support types. --- subdoc/lib/gen/generate_type.h | 2 +- sus/CMakeLists.txt | 11 +- sus/collections/slice.h | 1 - sus/collections/vec.h | 1 - sus/fn/__private/callable_types.h | 81 --- sus/fn/__private/fn_box_storage.h | 77 --- sus/fn/__private/fn_ref_invoker.h | 78 --- sus/fn/bind.h | 283 ---------- sus/fn/callable.h | 118 ---- sus/fn/fn.h | 4 - sus/fn/fn_box_defn.h | 694 ----------------------- sus/fn/fn_box_impl.h | 225 -------- sus/fn/fn_box_unittest.cc | 709 ------------------------ sus/fn/fn_concepts.h | 98 ++-- sus/fn/fn_ref.h | 511 ----------------- sus/fn/fn_ref_unittest.cc | 891 ------------------------------ sus/iter/__private/iter_compare.h | 1 + sus/iter/adaptors/filter.h | 2 +- sus/iter/adaptors/filter_map.h | 2 +- sus/iter/adaptors/flat_map.h | 2 +- sus/iter/adaptors/fuse.h | 2 +- sus/iter/adaptors/inspect.h | 2 +- sus/iter/adaptors/map.h | 2 +- sus/iter/adaptors/map_while.h | 2 +- sus/iter/adaptors/reverse.h | 2 +- sus/iter/adaptors/scan.h | 2 +- sus/iter/adaptors/skip_while.h | 2 +- sus/iter/once_with.h | 2 +- sus/iter/repeat_with.h | 2 +- sus/iter/successors.h | 2 +- sus/ops/ord.h | 1 - sus/result/result.h | 2 +- 32 files changed, 51 insertions(+), 3763 deletions(-) delete mode 100644 sus/fn/__private/callable_types.h delete mode 100644 sus/fn/__private/fn_box_storage.h delete mode 100644 sus/fn/__private/fn_ref_invoker.h delete mode 100644 sus/fn/bind.h delete mode 100644 sus/fn/callable.h delete mode 100644 sus/fn/fn_box_defn.h delete mode 100644 sus/fn/fn_box_impl.h delete mode 100644 sus/fn/fn_box_unittest.cc delete mode 100644 sus/fn/fn_ref.h delete mode 100644 sus/fn/fn_ref_unittest.cc diff --git a/subdoc/lib/gen/generate_type.h b/subdoc/lib/gen/generate_type.h index d57ddf63a..086d6c12a 100644 --- a/subdoc/lib/gen/generate_type.h +++ b/subdoc/lib/gen/generate_type.h @@ -17,7 +17,7 @@ #include "subdoc/lib/database.h" #include "subdoc/lib/gen/html_writer.h" #include "subdoc/lib/type.h" -#include "sus/fn/fn_ref.h" +#include "sus/fn/fn.h" #include "sus/prelude.h" namespace subdoc::gen { diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index 6d4d2c2ad..ac6e5b27d 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -76,16 +76,9 @@ target_sources(subspace PUBLIC "env/var.h" "error/compat_error.h" "error/error.h" - "fn/__private/callable_types.h" - "fn/__private/fn_box_storage.h" - "fn/__private/fn_ref_invoker.h" "fn/__private/signature.h" - "fn/callable.h" "fn/fn.h" - "fn/bind.h" - "fn/fn_box_defn.h" - "fn/fn_box_impl.h" - "fn/fn_ref.h" + "fn/fn_dyn.h" "iter/__private/into_iterator_archetype.h" "iter/__private/is_generator.h" "iter/__private/iter_compare.h" @@ -277,10 +270,8 @@ if(${SUBSPACE_BUILD_TESTS}) "construct/default_unittest.cc" "env/var_unittest.cc" "error/error_unittest.cc" - "fn/fn_box_unittest.cc" "fn/fn_concepts_unittest.cc" "fn/fn_dyn_unittest.cc" - "fn/fn_ref_unittest.cc" "iter/compat_ranges_unittest.cc" "iter/empty_unittest.cc" "iter/generator_unittest.cc" diff --git a/sus/collections/slice.h b/sus/collections/slice.h index 7da030971..2ef410dfa 100644 --- a/sus/collections/slice.h +++ b/sus/collections/slice.h @@ -28,7 +28,6 @@ #include "sus/collections/join.h" #include "sus/construct/default.h" #include "sus/fn/fn_concepts.h" -#include "sus/fn/fn_ref.h" #include "sus/iter/iterator_defn.h" #include "sus/iter/iterator_ref.h" #include "sus/lib/__private/forward_decl.h" diff --git a/sus/collections/vec.h b/sus/collections/vec.h index 5f720bc14..befab4a48 100644 --- a/sus/collections/vec.h +++ b/sus/collections/vec.h @@ -34,7 +34,6 @@ #include "sus/collections/iterators/vec_iter.h" #include "sus/collections/slice.h" #include "sus/fn/fn_concepts.h" -#include "sus/fn/fn_ref.h" #include "sus/iter/adaptors/by_ref.h" #include "sus/iter/adaptors/enumerate.h" #include "sus/iter/adaptors/take.h" diff --git a/sus/fn/__private/callable_types.h b/sus/fn/__private/callable_types.h deleted file mode 100644 index fe2cf9f39..000000000 --- a/sus/fn/__private/callable_types.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private -// IWYU pragma: friend "sus/.*" -#pragma once - -#include -#include -#include - -#include "sus/mem/forward.h" -#include "sus/mem/move.h" - -namespace sus::fn::__private { - -/// Whether a functor `F` is a function pointer that is callable with `Args...` -/// and will return a value that can be stored as `R`. -template -concept FunctionPointer = - std::is_pointer_v> && requires(F& f, Args... args) { - { std::invoke(*f, args...) } -> std::convertible_to; - }; - -/// Whether a functor `T` is a function pointer. -template -concept IsFunctionPointer = - std::is_pointer_v && std::is_function_v>; - -/// Whether a functor `T` can convert to a function pointer, typically this -/// means it's a captureless lambda. -template -concept ConvertsToFunctionPointer = requires(F& f) { - { +f } -> IsFunctionPointer; -}; - -/// Whether a functor `F` is a callable object (with an `operator()`) that is -/// once-callable as an rvalue with `Args...` and will return a value that can -/// be stored as `R`. -template -concept CallableOnceMut = - !FunctionPointer && requires(F&& f, Args... args) { - { - std::invoke(::sus::move(f), ::sus::forward(args)...) - } -> std::convertible_to; - }; - -/// Whether a functor `F` is a callable object (with an `operator()`) that is -/// mutable-callable as an lvalue with `Args...` and will return a value that -/// can be stored as `R`. -template -concept CallableMut = - !FunctionPointer && requires(F& f, Args... args) { - { - std::invoke(f, ::sus::forward(args)...) - } -> std::convertible_to; - }; - -/// Whether a functor `F` is a callable object (with an `operator()`) that is -/// const-callable with `Args...` and will return a value that can be stored as -/// `R`. -template -concept CallableConst = - !FunctionPointer && requires(const F& f, Args... args) { - { - std::invoke(f, ::sus::forward(args)...) - } -> std::convertible_to; - }; - -} // namespace sus::fn::__private diff --git a/sus/fn/__private/fn_box_storage.h b/sus/fn/__private/fn_box_storage.h deleted file mode 100644 index c73e1b00d..000000000 --- a/sus/fn/__private/fn_box_storage.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private -// IWYU pragma: friend "sus/.*" -#pragma once - -#include - -#include "sus/option/option.h" - -namespace sus::fn::__private { - -struct FnBoxStorageVtableBase {}; - -struct FnBoxStorageBase { - virtual ~FnBoxStorageBase() = default; - - // Should be to a static lifetime pointee. - Option vtable; -}; - -template -struct FnBoxStorageVtable final : public FnBoxStorageVtableBase { - R (*call_once)(__private::FnBoxStorageBase&&, CallArgs...); - R (*call_mut)(__private::FnBoxStorageBase&, CallArgs...); - R (*call)(const __private::FnBoxStorageBase&, CallArgs...); -}; - -template -class FnBoxStorage final : public FnBoxStorageBase { - public: - ~FnBoxStorage() override = default; - - constexpr FnBoxStorage(F&& callable) - requires(!(std::is_member_function_pointer_v> || - std::is_member_object_pointer_v>)) - : callable_(::sus::move(callable)) {} - constexpr FnBoxStorage(F ptr) - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - : callable_(ptr) {} - - template - static R call(const FnBoxStorageBase& self_base, CallArgs... callargs) { - const auto& self = static_cast(self_base); - return std::invoke(self.callable_, forward(callargs)...); - } - - template - static R call_mut(FnBoxStorageBase& self_base, CallArgs... callargs) { - auto& self = static_cast(self_base); - return std::invoke(self.callable_, forward(callargs)...); - } - - template - static R call_once(FnBoxStorageBase&& self_base, CallArgs... callargs) { - auto&& self = static_cast(self_base); - return std::invoke(::sus::move(self.callable_), - forward(callargs)...); - } - - F callable_; -}; - -} // namespace sus::fn::__private diff --git a/sus/fn/__private/fn_ref_invoker.h b/sus/fn/__private/fn_ref_invoker.h deleted file mode 100644 index 0a2ba540b..000000000 --- a/sus/fn/__private/fn_ref_invoker.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private -// IWYU pragma: friend "sus/.*" -#pragma once - -#include - -#include "sus/mem/forward.h" -#include "sus/mem/move.h" - -namespace sus::fn::__private { - -union Storage { - void (*fnptr)(); - void* object; -}; - -/// Functions to call a functor `F` that is stored in `Storage`. The choice of -/// function encodes which member of `Storage` holds the functor. -template -struct Invoker { - /// Calls the `F` in `Storage`, allowing mutable overlaods, when it is a - /// function pointer. - template - static R fnptr_call_mut(const union Storage& s, Args... args) { - F f = reinterpret_cast(s.fnptr); - return std::invoke(*f, ::sus::forward(args)...); - } - - /// Calls the `F` in `Storage`, as an lvalue, when it is a callable object. - template - static R object_call_mut(const union Storage& s, Args... args) { - F& f = *static_cast(s.object); - return std::invoke(f, ::sus::forward(args)...); - } - - /// Calls the `F` in `Storage`, as an rvalue, when it is a callable object. - template - static R object_call_once(const union Storage& s, Args... args) { - F& f = *static_cast(s.object); - return std::invoke(::sus::move(f), ::sus::forward(args)...); - } - - /// Calls the `F` in `Storage`, allowing only const overloads, when it is a - /// function pointer. - template - static R fnptr_call_const(const union Storage& s, Args... args) { - const F f = reinterpret_cast(s.fnptr); - return std::invoke(*f, ::sus::forward(args)...); - } - - /// Calls the `F` in `Storage`, as a const object, when it is a callable - /// object. - template - static R object_call_const(const union Storage& s, Args... args) { - const F& f = *static_cast(s.object); - return std::invoke(f, ::sus::forward(args)...); - } -}; - -/// A function pointer type that matches all the invoke functions the `Invoker`. -template -using InvokeFnPtr = R (*)(const union Storage& s, CallArgs... args); - -} // namespace sus::fn::__private diff --git a/sus/fn/bind.h b/sus/fn/bind.h deleted file mode 100644 index f1e01df70..000000000 --- a/sus/fn/bind.h +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private, include "sus/fn/fn.h" -// IWYU pragma: friend "sus/.*" -#pragma once - -#include - -#include "sus/fn/callable.h" -#include "sus/fn/fn_box_defn.h" -#include "sus/macros/__private/compiler_bugs.h" -#include "sus/macros/eval_macro.h" -#include "sus/macros/for_each.h" -#include "sus/macros/remove_parens.h" -#include "sus/marker/unsafe.h" -#include "sus/mem/forward.h" -#include "sus/mem/move.h" - -/// Bind a const lambda to storage for its bound arguments. The output can be -/// used to construct a FnOnceBox, FnMutBox, or FnBox. -/// -/// The first argument is a list of variables that will be bound into storage -/// for access from the lambda, wrapped in sus_store(). If there are no -/// variables to mention, sus_store() can be empty, or use the sus_bind0() macro -/// which omits this list. -/// -/// The second argument is a lambda, which can include captures. Any captures of -/// variables outside the lambda must be referenced in the sus_store() list. -/// -/// Use `sus_take(x)` in the sus_store() list to move `x` into storage instead -/// of copying it. -/// -/// Use `sus_unsafe_pointer(x)` to store a pointer named `x`. This is dangerous -/// and discouraged, and using smart pointers is strongly preferred. -/// -/// # Example -/// -/// This binds a lambda with 3 captures, the first two being variables from the -/// outside scope. The second variable is used as a reference to the storage, -/// rather that copying or moving it into the lambda. -/// ``` -/// sus_bind(sus_store(a, b), [a, &b, c = 1]() {})) -/// ``` -/// -/// # Implementation note -/// The lambda may arrive in multiple arguments, if there is a comma in the -/// definition of it. Thus we use variadic arguments to capture all of the -/// lambda. -#define sus_bind(names, lambda, ...) \ - [&]() { \ - [&]() consteval { \ - sus_for_each(_sus__check_storage, sus_for_each_sep_none, \ - _sus__unpack names) \ - }(); \ - using ::sus::fn::__private::SusBind; \ - return SusBind( \ - [sus_for_each(_sus__declare_storage, sus_for_each_sep_comma, \ - _sus__unpack names)](Args&&... args) { \ - const auto x = lambda __VA_OPT__(, ) __VA_ARGS__; \ - const bool is_const = \ - ::sus::fn::callable::CallableObjectConst; \ - if constexpr (!is_const) { \ - return ::sus::fn::__private::CheckCallableObjectConst< \ - is_const>::template error(); \ - } else { \ - return x(::sus::forward(args)...); \ - } \ - }); \ - }() - -/// A variant of `sus_bind()` which only takes a lambda, omitting the -/// `sus_store()` list. The output can be used to construct a FnOnceBox, -/// FnMutBox, or FnBox. -/// -/// Because there is no `sus_store()` list, the lambda can not capture variables -/// from the outside scope, however it can still declare captures contained -/// entirely inside the lambda. -/// -/// # Example -/// -/// This defines a lambda with a capture `a` of type `int`, and binds it so it -/// can be used to construct a FnOnceBox, FnMutBox, or FnBox. -/// ``` -/// sus_bind0([a = int(1)](char, int){}) -/// ``` -#define sus_bind0(lambda, ...) \ - sus_bind(sus_store(), lambda __VA_OPT__(, ) __VA_ARGS__) - -/// Bind a mutable lambda to storage for its bound arguments. The output can be -/// used to construct a FnOnceBox or FnMutBox. -/// -/// Because the storage is mutable, the lambda may capture references to the -/// storage and mutate it, and the lambda itself may be marked mutable. -/// -/// The first argument is a list of variables that will be bound into storage -/// for access from the lambda, wrapped in sus_store(). If there are no -/// variables to mention, sus_store() can be empty, or use the sus_bind0() macro -/// which omits this list. -/// -/// The second argument is a lambda, which can include captures. Any captures of -/// variables outside the lambda must be referenced in the sus_store() list. -/// -/// Use `sus_take(x)` in the sus_store() list to move `x` into storage instead -/// of copying it. -/// -/// Use `sus_unsafe_pointer(x)` to store a pointer named `x`. This is dangerous -/// and discouraged, and using smart pointers is strongly preferred. -/// -/// # Example -/// -/// This binds a lambda with 3 captures, the first two being variables from the -/// outside scope. The second variable is used as a reference to the storage, -/// rather that copying or moving it into the lambda. -/// ``` -/// sus_bind_mut(sus_store(a, b), [a, &b, c = 1]() {})) -/// ``` -/// -/// # Implementation note The lambda may arrive in multiple arguments, if there -/// is a comma in the definition of it. Thus we use variadic arguments to -/// capture all of the lambda. -#define sus_bind_mut(names, lambda, ...) \ - [&]() { \ - [&]() consteval { \ - sus_for_each(_sus__check_storage, sus_for_each_sep_none, \ - _sus__unpack names) \ - }(); \ - return ::sus::fn::__private::SusBind( \ - [sus_for_each(_sus__declare_storage_mut, sus_for_each_sep_comma, \ - _sus__unpack names)]( \ - Args&&... args) mutable { \ - auto x = lambda __VA_OPT__(, ) __VA_ARGS__; \ - return x(::sus::mem::forward(args)...); \ - }); \ - }() - -/// A variant of `sus_bind_mut()` which only takes a lambda, omitting the -/// `sus_store()` list. The output can be used to construct a FnOnceBox or -/// FnMutBox. -/// -/// Because there is no `sus_store()` list, the lambda can not capture variables -/// from the outside scope, however it can still declare captures contained -/// entirely inside the lambda. -/// -/// Can be used with a mutable lambda that can mutate its captures. -/// -/// # Example -/// -/// This defines a lambda with a capture `a` of type `int`, and binds it so it -/// can be used to construct a FnOnceBox, FnMutBox, or FnBox. -/// ``` -/// sus_bind0_mut([a = int(1)](char, int){}) -/// ``` -#define sus_bind0_mut(lambda, ...) \ - sus_bind_mut(sus_store(), lambda __VA_OPT__(, ) __VA_ARGS__) - -/// Declares the set of captures from the outside scope in `sus_bind()` or -/// `sus_bind_mut()`. -#define sus_store(...) (__VA_ARGS__) -/// Marks a capture in the `sus_store()` list to be moved from the outside scope -/// instead of copied. -#define sus_take(x) (x, _sus__bind_move) -/// Marks a capture in the `sus_store()` list as a pointer which is being -/// intentionally and unsafely captured. Otherwise, pointers are not allowed to -/// be captured. -#define sus_unsafe_pointer(x) (x, _sus__bind_pointer) - -namespace sus::fn::__private { - -/// Helper type returned by sus_bind() and used to construct a closure. -template -struct SusBind final { - constexpr inline SusBind(F&& lambda) : lambda(::sus::move(lambda)) {} - - /// The lambda generated by sus_bind() which holds the user-provided - /// lambda and any storage required for it. - F lambda; -}; - -// The type generated by sus_unsafe_pointer() for storage in sus_bind(). -template -struct UnsafePointer; - -template -struct UnsafePointer final { - constexpr inline UnsafePointer(::sus::marker::UnsafeFnMarker, T* pointer) - : pointer(pointer) {} - T* pointer; -}; - -template -UnsafePointer(::sus::marker::UnsafeFnMarker, T*) -> UnsafePointer; - -template -auto make_storage(T&& t) { - return std::decay_t(forward(t)); -} -template -auto make_storage(T*) { - static_assert(!std::is_pointer_v, - "Can not store a pointer in sus_bind() except through " - "sus_unsafe_pointer()."); -} -template -auto make_storage(UnsafePointer p) { - return static_cast(p.pointer); -} - -template -auto make_storage_mut(T&& t) { - return std::decay_t(forward(t)); -} -template -auto make_storage_mut(T* t) { - make_storage(t); -} -template -auto make_storage_mut(UnsafePointer p) { - return p.pointer; -} - -// Verifies the input is an lvalue (a name), so we can bind it to that same -// lvalue name in the resulting lambda. -template -std::true_type is_lvalue(T&); -std::false_type is_lvalue(...); - -/// Helper used when verifying if a lambda is const. The template parameter -/// represents the constness of the lambda. When false, the error() function -/// generates a compiler error. -template -struct CheckCallableObjectConst final { - template - static constexpr inline auto error() {} -}; - -template <> -struct CheckCallableObjectConst final { - template - static consteval inline auto error() { - throw "Use sus_bind_mut() to bind a mutable lambda"; - } -}; - -} // namespace sus::fn::__private - -// Private helper. -#define _sus__declare_storage(x) \ - sus_eval_macro(_sus__declare_storage_impl, sus_remove_parens(x), \ - _sus__bind_noop) -#define _sus__declare_storage_impl(x, modify, ...) \ - x = ::sus::fn::__private::make_storage(modify(x)) -#define _sus__declare_storage_mut(x) \ - sus_eval_macro(_sus__declare_storage_impl_mut, sus_remove_parens(x), \ - _sus__bind_noop) -#define _sus__declare_storage_impl_mut(x, modify, ...) \ - x = ::sus::fn::__private::make_storage_mut(modify(x)) -#define _sus__check_storage(x, ...) \ - sus_eval_macro(_sus__check_storage_impl, sus_remove_parens(x), \ - _sus__bind_noop) -#define _sus__check_storage_impl(x, modify, ...) \ - static_assert(decltype(::sus::fn::__private::is_lvalue(x))::value, \ - "sus_bind() can only bind to variable names (lvalues)."); - -#define _sus__bind_noop(x) x -#define _sus__bind_move(x) ::sus::move(x) -#define _sus__bind_pointer(x) \ - ::sus::fn::__private::UnsafePointer(::sus::marker::unsafe_fn, x) - -// Private helper. -#define _sus__unpack sus_bind_stored_argumnts_should_be_wrapped_in_sus_store -#define sus_bind_stored_argumnts_should_be_wrapped_in_sus_store(...) __VA_ARGS__ diff --git a/sus/fn/callable.h b/sus/fn/callable.h deleted file mode 100644 index b996bac90..000000000 --- a/sus/fn/callable.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private, include "sus/fn/fn.h" -// IWYU pragma: friend "sus/.*" -#pragma once - -#include -#include -#include - -#include "sus/mem/forward.h" - -namespace sus::fn::callable { - -template -concept FunctionPointer = requires(const T& t) { - { std::is_pointer_v }; -}; - -/// Verifies that T is a function pointer (or captureless lambda) that returns -/// a type convertible to `R` when called with `Args`. -/// -/// This concept allows conversion of `Args` to the function's actual receiving -/// types and conversion from the function's return type to `R`. -/// -// clang-format off -template -concept FunctionPointerReturns = ( - FunctionPointer && - std::convertible_to, R> && - // We verify that `T` can be stored in a function pointer. Types must match - // more strictly than just for invoking it. - requires (R(*p)(Args...), T& t) { - { p = t }; - } -); -// clang-format on - -/// Verifies that T is a function pointer (or captureless lambda) that receives -/// exactly `Args` as its parameters without conversion, and returns `R` without -/// conversion. -/// -/// This is concept is useful if you intend to store the pointer in a strongly -/// typed function pointer, as the types must match exactly. If you only intend -/// to call the function pointer, prefer `FunctionPointerReturns` which allows -/// appropriate conversions. -// -// clang-format off -template -concept FunctionPointerMatches = ( - FunctionPointer && - // We verify that `T` can be stored in a function pointer. Types must match - // more strictly than just for invoking it. - requires (R(*p)(Args...), T& t) { - { p = t }; - } -); -// clang-format on - -// clang-format off -template -concept FunctionPointerWith = ( - FunctionPointer && - requires (T& t, Args&&... args) { - std::invoke(t, ::sus::mem::forward(args)...); - } -); -// clang-format on - -namespace __private { - -template -inline constexpr bool callable_const(R (T::*)(Args...) const) { - return true; -}; - -} // namespace __private - -template -concept CallableObjectReturnsOnce = - !FunctionPointer && requires(T& t, Args&&... args) { - { - std::invoke(static_cast(t), ::sus::mem::forward(args)...) - } -> std::convertible_to; - }; - -template -concept CallableObjectReturnsConst = - !FunctionPointer && requires(const T& t, Args&&... args) { - { - std::invoke(t, ::sus::mem::forward(args)...) - } -> std::convertible_to; - }; - -template -concept CallableObjectReturnsMut = - !FunctionPointer && requires(T& t, Args&&... args) { - { - std::invoke(t, ::sus::mem::forward(args)...) - } -> std::convertible_to; - }; - -template -concept CallableObjectConst = __private::callable_const(&T::operator()); - -} // namespace sus::fn::callable diff --git a/sus/fn/fn.h b/sus/fn/fn.h index f2a41a109..36af7b383 100644 --- a/sus/fn/fn.h +++ b/sus/fn/fn.h @@ -15,10 +15,6 @@ #pragma once // IWYU pragma: begin_exports -#include "sus/fn/bind.h" -#include "sus/fn/fn_box_defn.h" -#include "sus/fn/fn_box_impl.h" #include "sus/fn/fn_concepts.h" -#include "sus/fn/fn_ref.h" #include "sus/fn/fn_dyn.h" // IWYU pragma: end_exports diff --git a/sus/fn/fn_box_defn.h b/sus/fn/fn_box_defn.h deleted file mode 100644 index d837d00ed..000000000 --- a/sus/fn/fn_box_defn.h +++ /dev/null @@ -1,694 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private, include "sus/fn/fn.h" -// IWYU pragma: friend "sus/.*" -#pragma once - -#include "sus/fn/callable.h" -#include "sus/lib/__private/forward_decl.h" -#include "sus/mem/forward.h" -#include "sus/mem/move.h" -#include "sus/mem/never_value.h" -#include "sus/mem/relocate.h" - -namespace sus::fn { - -namespace __private { - -/// The type-erased type (dropping the type of the internal lambda) of the -/// closure's heap-allocated storage. -struct FnBoxStorageBase; - -/// Helper type returned by sus_bind() and used to construct a closure. -template -struct SusBind; - -/// Helper to determine which functions need to be instantiated for the closure, -/// to be called from FnOnceBox, FnMutBox, and/or FnBox. -/// -/// This type indicates the closure can only be called from FnOnceBox. -enum StorageConstructionFnOnceBoxType { StorageConstructionFnOnceBox }; -/// Helper to determine which functions need to be instantiated for the closure, -/// to be called from FnOnceBox, FnMutBox, and/or FnBox. -/// -/// This type indicates the closure can be called from FnMutBox or FnOnceBox. -enum StorageConstructionFnMutBoxType { StorageConstructionFnMutBox }; -/// Helper to determine which functions need to be instantiated for the closure, -/// to be called from FnOnceBox, FnMutBox, and/or FnBox. -/// -/// This type indicates the closure can be called from FnBox, FnMutBox or -/// FnOnceBox. -enum StorageConstructionFnBoxType { StorageConstructionFnBox }; - -/// Used to indicate if the closure is holding a function pointer or -/// heap-allocated storage. -enum FnBoxType { - /// Holds a function pointer or captureless lambda. - FnBoxPointer = 1, - /// Holds the type-erased output of sus_bind() in a heap allocation. - Storage = 2, -}; - -} // namespace __private - -// TODO: Consider generic lambdas, it should be possible to bind them into -// FnOnceBox/FnMutBox/FnBox? -// Example: -// ``` -// auto even = [](const auto& i) { return i % 2 == 0; }; -// auto r0 = sus::Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -// 10); auto result = r0.iter().filter(even); -// ``` - -// TODO: There's no way to capture an rvalue right now. Need something like -// sus_take() but like sus_make(i, x.foo()) to bind `i = x.foo()`. - -// TODO: There's no way to capture a reference right now, sus_unsafe_ref()? - -/// A closure that erases the type of the internal callable object (lambda). A -/// FnOnceBox may only be called a single time. -/// -/// FnBox can be used as a FnMutBox, which can be used as a FnOnceBox. -/// -/// Lambdas without captures can be converted into a FnOnceBox, FnMutBox, or -/// FnBox directly. If the lambda has captured, it must be given to one of: -/// -/// - `sus_bind(sus_store(..captures..), lambda)` to bind a const lambda which -/// captures variables from local state. Variables to be captured in the lambda -/// must also be named in sus_store(). `sus_bind()` only allows those named -/// variables to be captured, and ensures they are stored by value instead of by -/// reference. -/// -/// - `sus_bind0(lambda)` to bind a const lambda which has bound variables that -/// don't capture state from outside the lambda, such as `[i = 2]() { return i; -/// }`. -/// -/// - `sus_bind_mut(sus_store(...captures...), lambda)` to bind a mutable lambda -/// which captures variables from local state. -/// -/// - `sus_bind0_mut(lambda)` to bind a mutable lambda which has bound variables -/// that don't capture state from outside the lambda, such as `[i = 2]() { -/// return ++i; }`. -/// -/// Within sus_store(), a variable name can be wrapped with a helper to capture -/// in different ways: -/// -/// - `sus_take(x)` will move `x` into the closure instead of copying it. -/// -/// - `sus_unsafe_pointer(x)` will allow capturing a pointer. Otherwise it be -/// disallowed, and it is strongly discouraged as it requires careful present -/// and future understanding of the pointee's lifetime. -/// -/// # Example -/// -/// Moves `a` into the closure's storage, and copies b. The lambda then refers -/// to the closure's stored values by reference. -/// -/// ``` -/// int a = 1; -/// int b = 2; -/// FnOnceBox f = sus_bind_mut( -/// sus_store(sus_take(a), b), [&a, &b]() mutable { a += b; } -/// ); -/// ``` -/// -/// Copies `a` into the closure's storage and defines a `b` from an rvalue. -/// Since `b` isn't referred to outside the FnBox it does not need to be bound. -/// -/// ``` -/// int a = 1; -/// FnOnceBox f = sus_bind_mut( -/// sus_store(a), [&a, b = 2]() mutable { a += b; } -/// ); -/// ``` -/// -/// TODO: There's no way to do this currently, since it won't know what to name -/// the `x.foo()` value. -/// -/// ``` -/// struct { int foo() { return 2; } } x; -/// FnOnceBox f = sus_bind_mut( -/// sus_store(x.foo()), [&a]() mutable { a += 1; } -/// ); -/// ``` -/// -/// # Why can a "const" FnBox convert to a mutable FnMutBox or FnOnceBox? -/// -/// A FnMutBox or FnOnceBox is _allowed_ to mutate its storage, but a "const" -/// FnBox closure would just choose not to do so. -/// -/// However, a `const FnBox` requires that the storage is not mutated, so it is -/// not useful if converted to a `const FnMutBox` or `const FnOnceBox` which are -/// only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnOnceBox from a null -/// pointer will panic. -template -class [[sus_trivial_abi]] FnOnceBox { - public: - /// Construction from a function pointer or captureless lambda. - /// - /// #[doc.overloads=ctor.fnpointer] - template <::sus::fn::callable::FunctionPointerMatches F> - FnOnceBox(F ptr) noexcept; - - /// Construction from a method or data member pointer. - /// - /// #[doc.overloads=ctor.methodpointer] - template <::sus::fn::callable::CallableObjectReturnsOnce F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - FnOnceBox(F ptr) noexcept - : FnOnceBox(__private::StorageConstructionFnOnceBox, ptr) {} - - /// Construction from a compatible FnOnceBox. - /// - /// #[doc.overloads=ctor.fnoncebox] - template - requires(::sus::fn::callable::CallableObjectReturnsOnce< - FnOnceBox &&, R, CallArgs...>) - FnOnceBox(FnOnceBox&& box) noexcept - : FnOnceBox(__private::StorageConstructionFnOnceBox, - ::sus::move(box)) {} - - /// Construction from a compatible FnMutBox. - /// - /// #[doc.overloads=ctor.fnmutbox] - template - requires(::sus::fn::callable::CallableObjectReturnsOnce< - FnMutBox, R, CallArgs...>) - FnOnceBox(FnMutBox&& box) noexcept - : FnOnceBox(__private::StorageConstructionFnOnceBox, - ::sus::move(box)) {} - - /// Construction from a compatible FnBox. - /// - /// #[doc.overloads=ctor.fnbox] - template - requires(::sus::fn::callable::CallableObjectReturnsOnce, - R, CallArgs...>) - FnOnceBox(FnBox&& box) noexcept - : FnOnceBox(__private::StorageConstructionFnOnceBox, - ::sus::move(box)) {} - - /// Construction from the output of `sus_bind()`. - /// - /// #[doc.overloads=ctor.bind] - template <::sus::fn::callable::CallableObjectReturnsOnce F> - FnOnceBox(__private::SusBind&& holder) noexcept - : FnOnceBox(__private::StorageConstructionFnOnceBox, - ::sus::move(holder.lambda)) {} - - ~FnOnceBox() noexcept; - - FnOnceBox(FnOnceBox&& o) noexcept; - FnOnceBox& operator=(FnOnceBox&& o) noexcept; - - FnOnceBox(const FnOnceBox&) noexcept = delete; - FnOnceBox& operator=(const FnOnceBox&) noexcept = delete; - - /// Runs and consumes the closure. - inline R operator()(CallArgs... args) && noexcept; - - /// `sus::construct::From` trait implementation. - // - // For function pointers or lambdas without captures. - template <::sus::fn::callable::FunctionPointerMatches F> - constexpr static auto from(F fn) noexcept { - return FnOnceBox(fn); - } - // For method or data member pointers. - template <::sus::fn::callable::CallableObjectReturnsOnce F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - constexpr static auto from(F fn) noexcept { - return FnOnceBox(fn); - } - // For compatible FnOnceBox. - template - requires(::sus::fn::callable::CallableObjectReturnsOnce< - FnOnceBox &&, R, CallArgs...>) - constexpr static auto from(FnOnceBox&& box) noexcept { - return FnOnceBox(::sus::move(box)); - } - // For compatible FnMutBox. - template - requires(::sus::fn::callable::CallableObjectReturnsOnce< - FnMutBox, R, CallArgs...>) - constexpr static auto from(FnMutBox&& box) noexcept { - return FnOnceBox(::sus::move(box)); - } - // For compatible FnBox. - template - requires(::sus::fn::callable::CallableObjectReturnsOnce, - R, CallArgs...>) - constexpr static auto from(FnBox&& box) noexcept { - return FnOnceBox(::sus::move(box)); - } - // For the output of `sus_bind()`. - template <::sus::fn::callable::CallableObjectReturnsOnce F> - constexpr static auto from(__private::SusBind&& holder) noexcept { - return FnOnceBox(::sus::move(holder)); - } - - protected: - template F> - requires(!(std::is_member_function_pointer_v> || - std::is_member_object_pointer_v>)) - FnOnceBox(ConstructionType, F&& lambda) noexcept; - - template F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - FnOnceBox(ConstructionType, F ptr) noexcept; - - union { - // Used when the closure is a function pointer (or a captureless lambda, - // which is converted to a function pointer). - R (*fn_ptr_)(CallArgs...); - - // Used when the closure is a lambda with storage, generated by - // `sus_bind()`. This is a type-erased pointer to the heap storage. - __private::FnBoxStorageBase* storage_; - }; - // TODO: Could we query the allocator to see if the pointer here is heap - // allocated or not, instead of storing a (pointer-sized, due to alignment) - // flag here? - __private::FnBoxType type_; - - private: - // Functions to construct and return a pointer to a static vtable object for - // the `__private::FnBoxStorage` being stored in `storage_`. - // - // A FnOnceBox needs to store only a single pointer, for call_once(). But a - // FnBox needs to store three, for call(), call_mut() and call_once() since it - // can be converted to a FnMutBox or FnOnceBox. For that reason we have 3 - // overloads where each one instantiates only the functions it requires - to - // avoid trying to compile functions that aren't accessible and thus don't - // need to be able to compile. - template - static void make_vtable(FnBoxStorage&, - __private::StorageConstructionFnOnceBoxType) noexcept; - template - static void make_vtable(FnBoxStorage&, - __private::StorageConstructionFnMutBoxType) noexcept; - template - static void make_vtable(FnBoxStorage&, - __private::StorageConstructionFnBoxType) noexcept; - - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, decltype(fn_ptr_), - decltype(storage_), decltype(type_)); - // Set the never value field to FnBoxPointer to perform a no-op destruction as - // there is nothing cleaned up when holding a function pointer. - sus_class_never_value_field(::sus::marker::unsafe_fn, FnOnceBox, type_, - static_cast<__private::FnBoxType>(0), - __private::FnBoxType::FnBoxPointer); - - protected: - // For the NeverValueField. - explicit FnOnceBox(::sus::mem::NeverValueConstructor) noexcept - : type_(static_cast<__private::FnBoxType>(0)) {} -}; - -/// A closure that erases the type of the internal callable object (lambda). A -/// FnMutBox may be called a multiple times, and may mutate its storage. -/// -/// FnBox can be used as a FnMutBox, which can be used as a FnOnceBox. -/// -/// Lambdas without captures can be converted into a FnOnceBox, FnMutBox, or -/// FnBox directly. If the lambda has captured, it must be given to one of: -/// -/// - `sus_bind(sus_store(..captures..), lambda)` to bind a const lambda which -/// captures variables from local state. Variables to be captured in the -/// lambda must also be named in sus_store(). `sus_bind()` only allows those -/// named variables to be captured, and ensures they are stored by value -/// instead of by reference. -/// -/// - `sus_bind0(lambda)` to bind a const lambda which has bound variables -/// that don't capture state from outside the lambda, such as `[i = 2]() { -/// return i; -/// }`. -/// -/// - `sus_bind_mut(sus_store(...captures...), lambda)` to bind a mutable -/// lambda which captures variables from local state. -/// -/// - `sus_bind0_mut(lambda)` to bind a mutable lambda which has bound -/// variables that don't capture state from outside the lambda, such as `[i = -/// 2]() { return ++i; }`. -/// -/// Within sus_store(), a variable name can be wrapped with a helper to -/// capture in different ways: -/// -/// - `sus_take(x)` will move `x` into the closure instead of copying it. -/// -/// - `sus_unsafe_pointer(x)` will allow capturing a pointer. Otherwise it be -/// disallowed, and it is strongly discouraged as it requires careful present -/// and future understanding of the pointee's lifetime. -/// -/// # Example -/// -/// Moves `a` into the closure's storage, and copies b. The lambda then refers -/// to the closure's stored values by reference. -/// -/// ``` -/// int a = 1; -/// int b = 2; -/// FnMutBox f = sus_bind_mut( -/// sus_store(sus_take(a), b), [&a, &b]() mutable { a += b; } -/// ); -/// ``` -/// -/// # Why can a "const" FnBox convert to a mutable FnMutBox or FnOnceBox? -/// -/// A FnMutBox or FnOnceBox is _allowed_ to mutate its storage, but a "const" -/// FnBox closure would just choose not to do so. -/// -/// However, a `const FnBox` requires that the storage is not mutated, so it -/// is not useful if converted to a `const FnMutBox` or `const FnOnceBox` -/// which are only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnMutBox from a -/// null pointer will panic. -template -class [[sus_trivial_abi]] FnMutBox - : public FnOnceBox { - public: - /// Construction from a function pointer or captureless lambda. - /// - /// #[doc.overloads=ctor.fnpointer] - template <::sus::fn::callable::FunctionPointerMatches F> - FnMutBox(F ptr) noexcept : FnOnceBox(::sus::move(ptr)) {} - - /// Construction from a method or data member pointer. - /// - /// #[doc.overloads=ctor.methodpointer] - template <::sus::fn::callable::CallableObjectReturnsMut F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - FnMutBox(F ptr) noexcept - : FnOnceBox(__private::StorageConstructionFnMutBox, ptr) { - } - - /// Construction from a compatible FnMutBox. - /// - /// #[doc.overloads=ctor.fnmutbox] - template - requires(::sus::fn::callable::CallableObjectReturnsMut< - FnMutBox, R, CallArgs...>) - FnMutBox(FnMutBox&& box) noexcept - : FnOnceBox(__private::StorageConstructionFnMutBox, - ::sus::move(box)) {} - - /// Construction from a compatible FnBox. - /// - /// #[doc.overloads=ctor.fnbox] - template - requires(::sus::fn::callable::CallableObjectReturnsMut, - R, CallArgs...>) - FnMutBox(FnBox&& box) noexcept - : FnOnceBox(__private::StorageConstructionFnMutBox, - ::sus::move(box)) {} - - /// Construction from the output of `sus_bind()`. - /// - /// #[doc.overloads=ctor.bind] - template <::sus::fn::callable::CallableObjectReturnsMut F> - FnMutBox(__private::SusBind&& holder) noexcept - : FnOnceBox(__private::StorageConstructionFnMutBox, - ::sus::move(holder.lambda)) {} - - ~FnMutBox() noexcept = default; - - FnMutBox(FnMutBox&&) noexcept = default; - FnMutBox& operator=(FnMutBox&&) noexcept = default; - - FnMutBox(const FnMutBox&) noexcept = delete; - FnMutBox& operator=(const FnMutBox&) noexcept = delete; - - /// Runs the closure. - inline R operator()(CallArgs... args) & noexcept; - inline R operator()(CallArgs... args) && noexcept { - return static_cast&&>(*this)( - forward(args)...); - } - - /// `sus::construct::From` trait implementation for function pointers or - /// lambdas without captures. - /// - /// #[doc.overloads=from.fnpointer] - template <::sus::fn::callable::FunctionPointerMatches F> - constexpr static auto from(F fn) noexcept { - return FnMutBox(fn); - } - - /// `sus::construct::From` trait implementation for method or data member - /// pointers. - /// - /// #[doc.overloads=from.method] - template <::sus::fn::callable::CallableObjectReturnsMut F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - constexpr static auto from(F fn) noexcept { - return FnMutBox(fn); - } - - /// `sus::construct::From` compatible FnMutBox. - /// - /// #[doc.overloads=from.fnmutbox] - template - requires(::sus::fn::callable::CallableObjectReturnsMut< - FnMutBox, R, CallArgs...>) - constexpr static auto from(FnMutBox&& box) noexcept { - return FnMutBox(::sus::move(box)); - } - - /// `sus::construct::From` compatible FnBox. - /// - /// #[doc.overloads=from.fnbox] - template - requires(::sus::fn::callable::CallableObjectReturnsMut, - R, CallArgs...>) - constexpr static auto from(FnBox&& box) noexcept { - return FnMutBox(::sus::move(box)); - } - - /// `sus::construct::From` trait implementation for the output of - /// `sus_bind()`. - /// - /// #[doc.overloads=from.callableobject] - template <::sus::fn::callable::CallableObjectReturnsMut F> - constexpr static auto from(__private::SusBind&& holder) noexcept { - return FnMutBox(::sus::move(holder)); - } - - protected: - // This class may only have trivially-destructible storage and must not - // do anything in its destructor, as `FnOnceBox` moves from itself, and it - // would slice that off. - - template F> - FnMutBox(ConstructionType c, F&& lambda) noexcept - : FnOnceBox(c, ::sus::move(lambda)) {} - - private: - sus_class_trivially_relocatable_unchecked(::sus::marker::unsafe_fn); - // Copied from FnOnceBox base class. - sus_class_never_value_field(::sus::marker::unsafe_fn, FnMutBox, - FnOnceBox::type_, - static_cast<__private::FnBoxType>(0), - __private::FnBoxType::FnBoxPointer); - - protected: - explicit FnMutBox(::sus::mem::NeverValueConstructor c) noexcept - : FnOnceBox(c) {} -}; - -/// A closure that erases the type of the internal callable object (lambda). A -/// FnBox may be called a multiple times, and will not mutate its storage. -/// -/// FnBox can be used as a FnMutBox, which can be used as a FnOnceBox. -/// -/// Lambdas without captures can be converted into a FnOnceBox, FnMutBox, or -/// FnBox directly. If the lambda has captured, it must be given to one of: -/// -/// - `sus_bind(sus_store(..captures..), lambda)` to bind a const lambda which -/// captures variables from local state. Variables to be captured in the -/// lambda must also be named in sus_store(). `sus_bind()` only allows those -/// named variables to be captured, and ensures they are stored by value -/// instead of by reference. -/// -/// - `sus_bind0(lambda)` to bind a const lambda which has bound variables -/// that don't capture state from outside the lambda, such as `[i = 2]() { -/// return i; -/// }`. -/// -/// - `sus_bind_mut(sus_store(...captures...), lambda)` to bind a mutable -/// lambda which captures variables from local state. -/// -/// - `sus_bind0_mut(lambda)` to bind a mutable lambda which has bound -/// variables that don't capture state from outside the lambda, such as `[i = -/// 2]() { return ++i; }`. -/// -/// Within sus_store(), a variable name can be wrapped with a helper to -/// capture in different ways: -/// -/// - `sus_take(x)` will move `x` into the closure instead of copying it. -/// -/// - `sus_unsafe_pointer(x)` will allow capturing a pointer. Otherwise it be -/// disallowed, and it is strongly discouraged as it requires careful present -/// and future understanding of the pointee's lifetime. -/// -/// # Example -/// -/// Moves `a` into the closure's storage, and copies b. The lambda then refers -/// to the closure's stored values by reference. -/// -/// ``` -/// int a = 1; -/// int b = 2; -/// FnBox f = sus_bind( -/// sus_store(sus_take(a), b), [&a, &b]() { return a + b; } -/// ); -/// ``` -/// -/// # Why can a "const" FnBox convert to a mutable FnMutBox or FnOnceBox? -/// -/// A FnMutBox or FnOnceBox is _allowed_ to mutate its storage, but a "const" -/// FnBox closure would just choose not to do so. -/// -/// However, a `const FnBox` requires that the storage is not mutated, so it -/// is not useful if converted to a `const FnMutBox` or `const FnOnceBox` -/// which are only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnBox from a null -/// pointer will panic. -template -class [[sus_trivial_abi]] FnBox final - : public FnMutBox { - public: - /// Construction from a function pointer or captureless lambda. - /// - /// #[doc.overloads=ctor.fnpointer] - template <::sus::fn::callable::FunctionPointerMatches F> - FnBox(F ptr) noexcept : FnMutBox(ptr) {} - - /// Construction from a method or data member pointer. - /// - /// #[doc.overloads=ctor.methodpointer] - template <::sus::fn::callable::CallableObjectReturnsConst F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - FnBox(F ptr) noexcept - : FnMutBox(__private::StorageConstructionFnBox, ptr) {} - - /// Construction from a compatible FnBox. - /// - /// #[doc.overloads=ctor.fnbox] - template - requires(::sus::fn::callable::CallableObjectReturnsConst, - R, CallArgs...>) - FnBox(FnBox&& box) noexcept - : FnMutBox(__private::StorageConstructionFnBox, - ::sus::move(box)) {} - - /// Construction from the output of `sus_bind()`. - /// - /// #[doc.overloads=ctor.bind] - template <::sus::fn::callable::CallableObjectReturnsConst F> - FnBox(__private::SusBind&& holder) noexcept - : FnMutBox(__private::StorageConstructionFnBox, - ::sus::move(holder.lambda)) {} - - ~FnBox() noexcept = default; - - FnBox(FnBox&&) noexcept = default; - FnBox& operator=(FnBox&&) noexcept = default; - - FnBox(const FnBox&) noexcept = delete; - FnBox& operator=(const FnBox&) noexcept = delete; - - /// Runs the closure. - inline R operator()(CallArgs... args) const& noexcept; - inline R operator()(CallArgs... args) && noexcept { - return static_cast&&>(*this)( - forward(args)...); - } - - /// `sus::construct::From` trait implementation for function pointers or - /// lambdas without captures. - /// - /// #[doc.overloads=from.fnpointer] - template <::sus::fn::callable::FunctionPointerMatches F> - constexpr static auto from(F fn) noexcept { - return FnBox(fn); - } - - /// `sus::construct::From` trait implementation for method or data member - /// pointers. - /// - /// #[doc.overloads=from.method] - template <::sus::fn::callable::CallableObjectReturnsConst F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) - constexpr static auto from(F fn) noexcept { - return FnBox(fn); - } - - /// `sus::construct::From` compatible FnBox. - /// - /// #[doc.overloads=from.fnbox] - template - requires(::sus::fn::callable::CallableObjectReturnsConst, - R, CallArgs...>) - constexpr static auto from(FnBox&& box) noexcept { - return FnMutBox(::sus::move(box)); - } - - /// `sus::construct::From` trait implementation for the output of - /// `sus_bind()`. - /// #[doc.overloads=1] - template <::sus::fn::callable::CallableObjectReturnsConst F> - constexpr static auto from(__private::SusBind&& holder) noexcept { - return FnBox(::sus::move(holder)); - } - - private: - // This class may only have trivially-destructible storage and must not - // do anything in its destructor, as `FnOnceBox` moves from itself, and it - // would slice that off. - - template <::sus::fn::callable::CallableObjectReturnsConst F> - FnBox(__private::StorageConstructionFnBoxType, F&& fn) noexcept; - - sus_class_trivially_relocatable_unchecked(::sus::marker::unsafe_fn); - // Copied from FnOnceBox base class. - sus_class_never_value_field(::sus::marker::unsafe_fn, FnBox, - FnOnceBox::type_, - static_cast<__private::FnBoxType>(0), - __private::FnBoxType::FnBoxPointer); - explicit FnBox(::sus::mem::NeverValueConstructor c) noexcept - : FnMutBox(c) {} -}; - -} // namespace sus::fn diff --git a/sus/fn/fn_box_impl.h b/sus/fn/fn_box_impl.h deleted file mode 100644 index 72453d650..000000000 --- a/sus/fn/fn_box_impl.h +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private, include "sus/fn/fn.h" -// IWYU pragma: friend "sus/.*" -#pragma once - -#include "sus/assertions/check.h" -#include "sus/assertions/unreachable.h" -#include "sus/fn/__private/fn_box_storage.h" -#include "sus/fn/fn_box_defn.h" -#include "sus/macros/__private/compiler_bugs.h" -#include "sus/mem/replace.h" - -namespace sus::fn { - -template -template <::sus::fn::callable::FunctionPointerMatches F> -FnOnceBox::FnOnceBox(F ptr) noexcept - : fn_ptr_(ptr), type_(__private::FnBoxPointer) { - ::sus::check(ptr != nullptr); -} - -template -template F> - requires(!(std::is_member_function_pointer_v> || - std::is_member_object_pointer_v>)) -FnOnceBox::FnOnceBox(ConstructionType construction, - F&& lambda) noexcept - : type_(__private::Storage) { - // TODO: Allow overriding the global allocator? Use the allocator in place of - // `new` and `delete` directly? - auto* s = new __private::FnBoxStorage(::sus::move(lambda)); - make_vtable(*s, construction); - storage_ = s; -} - -template -template F> - requires(std::is_member_function_pointer_v || - std::is_member_object_pointer_v) -FnOnceBox::FnOnceBox(ConstructionType construction, - F ptr) noexcept - : type_(__private::Storage) { - ::sus::check(ptr != nullptr); - // TODO: Allow overriding the global allocator? Use the allocator in place of - // `new` and `delete` directly? - auto* s = new __private::FnBoxStorage(ptr); - make_vtable(*s, construction); - storage_ = s; -} - -template -template -void FnOnceBox::make_vtable( - FnBoxStorage& storage, - __private::StorageConstructionFnOnceBoxType) noexcept { - static __private::FnBoxStorageVtable vtable = { - .call_once = &FnBoxStorage::template call_once, - .call_mut = nullptr, - .call = nullptr, - }; - storage.vtable.insert(vtable); -} - -template -template -void FnOnceBox::make_vtable( - FnBoxStorage& storage, - __private::StorageConstructionFnMutBoxType) noexcept { - static __private::FnBoxStorageVtable vtable = { - .call_once = &FnBoxStorage::template call_once, - .call_mut = &FnBoxStorage::template call_mut, - .call = nullptr, - }; - storage.vtable.insert(vtable); -} - -template -template -void FnOnceBox::make_vtable( - FnBoxStorage& storage, __private::StorageConstructionFnBoxType) noexcept { - static __private::FnBoxStorageVtable vtable = { - .call_once = &FnBoxStorage::template call_once, - .call_mut = &FnBoxStorage::template call_mut, - .call = &FnBoxStorage::template call, - }; - storage.vtable.insert(vtable); -} - -template -FnOnceBox::~FnOnceBox() noexcept { - switch (type_) { - case __private::FnBoxPointer: break; - case __private::Storage: { - if (auto* s = ::sus::mem::replace(storage_, nullptr); s) delete s; - break; - } - } - // The NeverValue in `type_` means we get here and do nothing. -} - -template -FnOnceBox::FnOnceBox(FnOnceBox&& o) noexcept : type_(o.type_) { - switch (type_) { - case __private::FnBoxPointer: - ::sus::check(o.fn_ptr_); // Catch use-after-move. - fn_ptr_ = ::sus::mem::replace(o.fn_ptr_, nullptr); - break; - case __private::Storage: - ::sus::check(o.storage_); // Catch use-after-move. - storage_ = ::sus::mem::replace(o.storage_, nullptr); - break; - } -} - -template -FnOnceBox& FnOnceBox::operator=( - FnOnceBox&& o) noexcept { - switch (type_) { - case __private::FnBoxPointer: break; - case __private::Storage: - if (auto* s = ::sus::mem::replace(storage_, nullptr); s) delete s; - } - switch (type_ = o.type_) { - case __private::FnBoxPointer: - ::sus::check(o.fn_ptr_); // Catch use-after-move. - fn_ptr_ = ::sus::mem::replace(o.fn_ptr_, nullptr); - break; - case __private::Storage: - ::sus::check(o.storage_); // Catch use-after-move. - storage_ = ::sus::mem::replace(o.storage_, nullptr); - break; - } - return *this; -} - -template -R FnOnceBox::operator()(CallArgs... args) && noexcept { - switch (type_) { - case __private::FnBoxPointer: { - ::sus::check(fn_ptr_); // Catch use-after-move. - auto* fn = ::sus::mem::replace(fn_ptr_, nullptr); - return std::invoke(fn, static_cast(args)...); - } - case __private::Storage: { - ::sus::check(storage_); // Catch use-after-move. - auto* storage = ::sus::mem::replace(storage_, nullptr); - auto& vtable = - static_cast&>( - storage->vtable.as_mut().unwrap()); - - // Delete storage, after the call_once() is complete. - // - // TODO: `storage` and `storage_` should be owning smart pointers. - struct DeleteStorage final { - sus_clang_bug_54040( - constexpr inline DeleteStorage(__private::FnBoxStorageBase* storage) - : storage(storage){}); - ~DeleteStorage() { delete storage; } - __private::FnBoxStorageBase* storage; - } deleter(storage); - - return vtable.call_once( - static_cast<__private::FnBoxStorageBase&&>(*storage), - forward(args)...); - } - } - ::sus::unreachable_unchecked(::sus::marker::unsafe_fn); -} - -template -R FnMutBox::operator()(CallArgs... args) & noexcept { - using Super = FnOnceBox; - switch (Super::type_) { - case __private::FnBoxPointer: - ::sus::check(Super::fn_ptr_); // Catch use-after-move. - return Super::fn_ptr_(static_cast(args)...); - case __private::Storage: { - ::sus::check(Super::storage_); // Catch use-after-move. - auto& vtable = - static_cast&>( - Super::storage_->vtable.as_mut().unwrap()); - return vtable.call_mut( - static_cast<__private::FnBoxStorageBase&>(*Super::storage_), - ::sus::forward(args)...); - } - } - ::sus::unreachable_unchecked(::sus::marker::unsafe_fn); -} - -template -R FnBox::operator()(CallArgs... args) const& noexcept { - using Super = FnOnceBox; - switch (Super::type_) { - case __private::FnBoxPointer: - ::sus::check(Super::fn_ptr_); // Catch use-after-move. - return Super::fn_ptr_(static_cast(args)...); - case __private::Storage: { - ::sus::check(Super::storage_); // Catch use-after-move. - auto& vtable = - static_cast&>( - Super::storage_->vtable.as_mut().unwrap()); - return vtable.call( - static_cast(*Super::storage_), - ::sus::forward(args)...); - } - } - ::sus::unreachable_unchecked(::sus::marker::unsafe_fn); -} - -} // namespace sus::fn diff --git a/sus/fn/fn_box_unittest.cc b/sus/fn/fn_box_unittest.cc deleted file mode 100644 index a07f75c94..000000000 --- a/sus/fn/fn_box_unittest.cc +++ /dev/null @@ -1,709 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "googletest/include/gtest/gtest.h" -#include "sus/construct/into.h" -#include "sus/fn/fn.h" -#include "sus/iter/iterator.h" -#include "sus/mem/forward.h" -#include "sus/mem/move.h" -#include "sus/mem/replace.h" -#include "sus/option/option.h" -#include "sus/prelude.h" - -namespace { - -using sus::construct::Into; -using sus::fn::FnBox; -using sus::fn::FnMutBox; -using sus::fn::FnOnceBox; - -struct Copyable { - Copyable(int i) : i(i) {} - Copyable(const Copyable& c) = default; - Copyable& operator=(const Copyable& c) = default; - ~Copyable() { i = -10000000; } - - int i = 0; -}; - -struct MoveOnly { - MoveOnly(int i) : i(i) {} - MoveOnly(const MoveOnly& c) = delete; - MoveOnly& operator=(const MoveOnly& c) = delete; - MoveOnly(MoveOnly&& c) : i(::sus::mem::replace(c.i, -1)) {} - MoveOnly& operator=(MoveOnly&& c) { - i = ::sus::mem::replace(c.i, -1); - return *this; - } - ~MoveOnly() { i = -10000000; } - - int i = 0; -}; - -// clang-format-off - -struct BaseClass {}; -struct SubClass : public BaseClass {}; - -static_assert(sizeof(FnOnceBox) > sizeof(void (*)())); -static_assert(sizeof(FnOnceBox) <= sizeof(void (*)()) * 2); - -void v_v_function() {} -int i_f_function(float) { return 0; } -void v_f_function(float) {} -BaseClass* b_b_function(BaseClass* b) { return b; } -SubClass* s_b_function(BaseClass* b) { return static_cast(b); } -SubClass* s_s_function(SubClass* b) { return b; } - -// These emulate binding with sus_bind(), but don't use it cuz capturing lambdas -// in a constant expression won't work. -auto b_b_lambda = sus::fn::__private::SusBind( - [a = 1](BaseClass* b) -> BaseClass* { return b; }); -auto s_b_lambda = sus::fn::__private::SusBind( - [a = 1](BaseClass* b) -> SubClass* { return static_cast(b); }); -auto s_s_lambda = sus::fn::__private::SusBind( - [a = 1](SubClass* b) -> SubClass* { return b; }); - -// FnBox types all have a never-value field. -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -// Which allows them to not require a flag in Option. -static_assert(sizeof(sus::Option>) == - sizeof(FnOnceBox)); - -// Closures can not be copied, as their storage is uniquely owned. -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -// Closures can be moved. -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); - -// Closures are trivially relocatable. -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); - -// A function pointer, or convertible lambda, can be bound to FnOnceBox, -// FnMutBox and FnBox. -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert( - std::is_constructible_v, decltype(v_v_function)>); -static_assert( - std::is_constructible_v, decltype(v_v_function)>); -static_assert(std::is_constructible_v, decltype(v_v_function)>); -// Non-void types for the same. -static_assert(std::is_constructible_v, - decltype([](float) { return 1; })>); -static_assert(std::is_constructible_v, - decltype([](float) { return 1; })>); -static_assert(std::is_constructible_v, - decltype([](float) { return 1; })>); -static_assert( - std::is_constructible_v, decltype(i_f_function)>); -static_assert( - std::is_constructible_v, decltype(i_f_function)>); -static_assert( - std::is_constructible_v, decltype(i_f_function)>); - -// Lambdas with bound args can't be passed without sus_bind(). -static_assert(!std::is_constructible_v, - decltype([i = int(1)]() { (void)i; })>); -static_assert(!std::is_constructible_v< - FnOnceBox, decltype([i = int(1)]() mutable { ++i; })>); - -// The return type of the FnBox must match that of the lambda. It will not allow -// converting to void. -static_assert(std::is_constructible_v, - decltype(s_b_function)>); -static_assert( - !std::is_constructible_v, decltype(b_b_function)>); -static_assert(!std::is_constructible_v, - decltype(b_b_function)>); -static_assert(std::is_constructible_v, - decltype(s_b_lambda)>); -static_assert( - !std::is_constructible_v, decltype(b_b_lambda)>); -static_assert(!std::is_constructible_v, - decltype(b_b_lambda)>); -// Similarly, argument types can't be converted to a different type. -static_assert(std::is_constructible_v, - decltype(s_s_function)>); -static_assert(!std::is_constructible_v, - decltype(s_s_function)>); -static_assert( - std::is_constructible_v, decltype(s_s_lambda)>); -static_assert(!std::is_constructible_v, - decltype(s_s_lambda)>); -// But FnBox type is compatible with convertible return and argument types in -// opposite directions. -// - If the return type Y of a lambda is convertible _to_ X, then FnBox can -// be -// used to store it. -// - If the argument type Y of a lambda is convertible _from_ X, then FnBox<(X)> -// can be used to store it. -// -// In both cases, the FnBox is more strict than the lambda, guaranteeing that -// the lambda's requirements are met. -static_assert(std::is_constructible_v, - decltype(s_b_lambda)>); -static_assert( - std::is_constructible_v, decltype(s_b_lambda)>); -// HOWEVER: When FnBox is passed a function pointer, it stores a function -// pointer. C++20 does not yet allow us to erase the type of that function -// pointer in a constexpr context. So the types in the pointer must match -// exactly to the FnBox's signature. -static_assert(!std::is_constructible_v, - decltype(s_b_function)>); -static_assert(!std::is_constructible_v, - decltype(s_b_function)>); - -// Lambdas with bound args can be passed with sus_bind(). Can use sus_bind0() -// when there's no captured variables. -static_assert(std::is_constructible_v, decltype([]() { - return sus_bind0([i = int(1)]() {}); - }())>); -// And use sus_bind0_mut for a mutable lambda. -static_assert(std::is_constructible_v, decltype([]() { - return sus_bind0_mut( - [i = int(1)]() mutable { ++i; }); - }())>); - -// FnBox that are compatible can be converted to. -static_assert(std::is_constructible_v, - FnBox>); -static_assert(!std::is_constructible_v, - FnBox>); -static_assert(std::is_constructible_v, - FnMutBox>); -static_assert(!std::is_constructible_v, - FnMutBox>); -static_assert(std::is_constructible_v, - FnOnceBox>); -static_assert(!std::is_constructible_v, - FnOnceBox>); - -static_assert(std::is_constructible_v, - FnMutBox>); -static_assert(std::is_constructible_v, - FnBox>); -static_assert(std::is_constructible_v, - FnBox>); - -// This incorrectly uses sus_bind0 with a mutable lambda, which produces a -// warning while also being non-constructible. But we build with errors enabled -// so it fails to compile instead of just failing the assert. -// -// TODO: It doesn't seem possible to disable this error selectively here, as the -// actual call happens in a template elsewhere. So that prevents us from -// checking the non-constructibility if the warning isn't compiled as an error. -// -// It would be nice to be able to produce a nice warning while also failing -// overload resolution nicely. But with -Werror the warning becomes an error, -// and we can't explain why overload resolution is failing with extra -// information in that error message. -// -// #pragma GCC diagnostic push -// #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -// static_assert(!std::is_constructible_v< -// FnOnceBox, -// decltype([]() { -// return sus_bind0([i = int(1)]() mutable {++i;}); -// }()) -// >); -// #pragma GCC diagnostic pop - -template -concept can_run = requires(Arg arg, FnOnceBox fnonce, - FnMutBox fnmut, FnBox fn) { - { static_cast&&>(fnonce)(sus::forward(arg)) }; - { static_cast&&>(fnmut)(sus::forward(arg)) }; - { static_cast&&>(fn)(sus::forward(arg)) }; -}; -// clang-format on - -// Int is copyable, so references are copied when passed. -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -// But for a move-only type, it can only be passed along as a reference or an -// rvalue. -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); - -// Receiving a mutable reference means it must be passed as a mutable reference. -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); - -// Receiving a const reference means it must be passed as a reference. -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); - -TEST(FnBox, Pointer) { - { - auto fn = FnOnceBox([](int a, int b) { return a * 2 + b; }); - EXPECT_EQ(sus::move(fn)(1, 2), 4); - } - { - auto fn = FnMutBox([](int a, int b) { return a * 2 + b; }); - EXPECT_EQ(fn(1, 2), 4); - } - { - auto fn = FnBox([](int a, int b) { return a * 2 + b; }); - EXPECT_EQ(sus::move(fn)(1, 2), 4); - } -} - -TEST(FnBox, InlineCapture) { - { - auto fn = - FnOnceBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = - FnMutBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = FnBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } -} - -TEST(FnBox, OutsideCapture) { - int a = 1; - { - auto fn = FnOnceBox( - sus_bind(sus_store(a), [a](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = FnMutBox( - sus_bind(sus_store(a), [a](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = FnBox( - sus_bind(sus_store(a), [a](int b) { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } -} - -TEST(FnBox, BothCapture) { - int a = 1; - { - auto fn = FnOnceBox( - sus_bind(sus_store(a), [a, b = 2]() { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(), 4); - } - { - auto fn = FnMutBox( - sus_bind(sus_store(a), [a, b = 2]() { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(), 4); - } - { - auto fn = FnBox( - sus_bind(sus_store(a), [a, b = 2]() { return a * 2 + b; })); - EXPECT_EQ(sus::move(fn)(), 4); - } -} - -TEST(FnBox, CopyFromCapture) { - auto c = Copyable(1); - { - auto fn = FnOnceBox( - sus_bind(sus_store(c), [c](int b) { return c.i * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = FnMutBox( - sus_bind(sus_store(c), [c](int b) { return c.i * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto fn = FnBox( - sus_bind(sus_store(c), [c](int b) { return c.i * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } -} - -TEST(FnBox, MoveFromCapture) { - { - auto m = MoveOnly(1); - auto fn = FnOnceBox(sus_bind_mut( - sus_store(sus_take(m)), - [m = static_cast(m)](int b) { return m.i * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto m = MoveOnly(1); - auto fn = FnMutBox(sus_bind_mut( - sus_store(sus_take(m)), - [m = static_cast(m)](int b) { return m.i * 2 + b; })); - EXPECT_EQ(fn(2), 4); - // The stored value was moved into the run lambda (so `m` holds `-1`). - EXPECT_EQ(m.i, -1); - EXPECT_EQ(fn(-2), -4); - EXPECT_EQ(sus::move(fn)(-2), -4); - } - // Can't hold sus_bind_mut() in FnBox. -} - -TEST(FnBox, MoveIntoCapture) { - { - auto m = MoveOnly(1); - auto fn = FnOnceBox( - sus_bind(sus_store(sus_take(m)), [&m](int b) { return m.i * 2 + b; })); - EXPECT_EQ(sus::move(fn)(2), 4); - } - { - auto m = MoveOnly(1); - auto fn = FnMutBox( - sus_bind(sus_store(sus_take(m)), [&m](int b) { return m.i * 2 + b; })); - EXPECT_EQ(fn(2), 4); - EXPECT_EQ(fn(2), 4); - } - // Can modify the captured m with sus_bind_mut(). - { - auto m = MoveOnly(1); - auto fn = FnMutBox(sus_bind_mut( - sus_store(sus_take(m)), [&m](int b) { return m.i++ * 2 + b; })); - EXPECT_EQ(fn(2), 4); - EXPECT_EQ(fn(2), 6); - } - { - auto m = MoveOnly(1); - auto fn = FnBox( - sus_bind(sus_store(sus_take(m)), [&m](int b) { return m.i * 2 + b; })); - EXPECT_EQ(fn(2), 4); - EXPECT_EQ(fn(2), 4); - } -} - -TEST(FnBox, MoveFnBox) { - { - auto fn = FnOnceBox([](int a, int b) { return a * 2 + b; }); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(1, 2), 4); - } - { - auto fn = - FnOnceBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(2), 4); - } - { - auto fn = FnMutBox([](int a, int b) { return a * 2 + b; }); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(1, 2), 4); - } - { - auto fn = - FnMutBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(2), 4); - } - { - auto fn = FnBox([](int a, int b) { return a * 2 + b; }); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(1, 2), 4); - } - { - auto fn = FnBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto fn2 = sus::move(fn); - EXPECT_EQ(sus::move(fn2)(2), 4); - } -} - -TEST(FnBox, FnBoxIsFnMutBox) { - { - auto fn = FnBox([](int a, int b) { return a * 2 + b; }); - auto mut = FnMutBox(sus::move(fn)); - EXPECT_EQ(mut(1, 2), 4); - } - { - auto fn = FnBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto mut = FnMutBox(sus::move(fn)); - EXPECT_EQ(mut(2), 4); - } -} - -TEST(FnBox, FnBoxIsFnOnceBox) { - { - auto fn = FnBox([](int a, int b) { return a * 2 + b; }); - auto once = FnOnceBox(sus::move(fn)); - EXPECT_EQ(sus::move(once)(1, 2), 4); - } - { - auto fn = FnBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto once = FnOnceBox(sus::move(fn)); - EXPECT_EQ(sus::move(once)(2), 4); - } -} - -TEST(FnBox, FnMutBoxIsFnOnceBox) { - { - auto fn = FnMutBox([](int a, int b) { return a * 2 + b; }); - auto once = FnOnceBox(sus::move(fn)); - EXPECT_EQ(sus::move(once)(1, 2), 4); - } - { - auto fn = - FnMutBox(sus_bind0([a = 1](int b) { return a * 2 + b; })); - auto once = FnOnceBox(sus::move(fn)); - EXPECT_EQ(sus::move(once)(2), 4); - } -} - -TEST(FnBox, BindUnsafePointer) { - int a = 1; - int* pa = &a; - int b = 2; - auto fn = - FnBox(sus_bind(sus_store(sus_unsafe_pointer(pa), b), [pa, b]() { - // sus_bind() will store pointers as const. - static_assert(std::is_const_v>); - return *pa * 2 + b; - })); - EXPECT_EQ(fn(), 4); - - auto fnmut = FnMutBox( - sus_bind_mut(sus_store(sus_unsafe_pointer(pa), b), [pa, b]() { - // sus_bind_mut() will store pointers as mutable. - static_assert(!std::is_const_v>); - return (*pa)++ * 2 + b; - })); - EXPECT_EQ(fnmut(), 4); -} - -TEST(FnBox, Into) { - auto into_fnonce = []> F>(F into_f) { - FnOnceBox f = sus::move_into(into_f); - return sus::move(f)(1); - }; - EXPECT_EQ(into_fnonce([](int i) { return i + 1; }), 2); - EXPECT_EQ(into_fnonce(sus_bind0([](int i) { return i + 1; })), 2); - - auto into_fnmut = []> F>(F into_f) { - return FnMutBox::from(::sus::move(into_f))(1); - }; - EXPECT_EQ(into_fnmut([](int i) { return i + 1; }), 2); - EXPECT_EQ(into_fnmut(sus_bind0([](int i) { return i + 1; })), 2); - - auto into_fn = []> F>(F into_f) { - FnBox f = sus::move_into(into_f); - return sus::move(f)(1); - }; - EXPECT_EQ(into_fn([](int i) { return i + 1; }), 2); - EXPECT_EQ(into_fn(sus_bind0([](int i) { return i + 1; })), 2); -} - -TEST(FnBoxDeathTest, NullPointer) { - void (*f)() = nullptr; -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(FnOnceBox::from(f), ""); -#endif -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(FnMutBox::from(f), ""); -#endif -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(FnBox::from(f), ""); -#endif -} - -TEST(FnBoxDeathTest, CallAfterMoveConstruct) { - { - auto x = FnOnceBox::from([]() {}); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from([]() {}); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnBox::from([]() {}); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnOnceBox::from(sus_bind0([]() {})); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from(sus_bind0([]() {})); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnBox::from(sus_bind0([]() {})); - [[maybe_unused]] auto y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } -} - -TEST(FnBoxDeathTest, CallAfterMoveAssign) { - { - auto x = FnOnceBox::from([]() {}); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from([]() {}); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnBox::from([]() {}); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnOnceBox::from(sus_bind0([]() {})); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from(sus_bind0([]() {})); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } - { - auto x = FnBox::from(sus_bind0([]() {})); - auto y = FnOnceBox::from([]() {}); - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(x(), ""); -#endif - } -} - -TEST(FnBoxDeathTest, CallAfterCall) { - { - auto x = FnOnceBox::from([]() {}); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from([]() {}); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnBox::from([]() {}); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnOnceBox::from(sus_bind0([]() {})); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnMutBox::from(sus_bind0([]() {})); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } - { - auto x = FnBox::from(sus_bind0([]() {})); - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - } -} - -struct Class { - Class(i32 value) : value_(value) {} - i32 value() const { return value_; } - - private: - i32 value_; -}; - -TEST(FnBox, Method) { - auto it = sus::Option(Class(42)).into_iter().map(&Class::value); - sus::check(it.next() == sus::some(42)); -} - -} // namespace diff --git a/sus/fn/fn_concepts.h b/sus/fn/fn_concepts.h index bb1903b0e..1520aa1cd 100644 --- a/sus/fn/fn_concepts.h +++ b/sus/fn/fn_concepts.h @@ -53,9 +53,7 @@ struct Anything { /// The version of a callable object that may be called only once. /// /// A `FnOnce` is typically the best fit for any callable that will -/// only be called at most once. However when a template (or constexpr) is not -/// required, receiving a `FnOnceRef` instead of `FnOnce auto&&` will typically -/// produce less code by using type erasure to avoid template instantiation. +/// only be called at most once. /// /// A type that satisfies `FnOnce` will return a type that can be converted to /// `R` when called with the arguments `Args...`. `FnOnce` is satisfied by @@ -77,11 +75,15 @@ struct Anything { /// an lvalue in the caller. /// /// A `FnOnce` should be called by moving it with `sus::move()` when passing it -/// to `sus::fn::call_once()` along with any arguments. It is moved-from after -/// calling, and it should only be called once. -/// -/// Calling a `FnOnce` multiple times may panic or cause Undefined Behaviour. -/// Not moving the `FnOnce` when calling it may fail to compile, panic, or cause +/// to `sus::fn::call_once()` along with any arguments. This ensures the +/// correct overload is called on the object and that method pointers are +/// called correctly. It is moved-from after calling, and it should only be +/// called once. +/// +/// Calling a `FnOnce` multiple times may [`panic`]($sus::assertions::panic) +/// or cause Undefined Behaviour. +/// Not moving the `FnOnce` when calling it may fail to compile, +/// [`panic`]($sus::assertions::panic), or cause /// Undefined Behaviour depending on the type that is being used to satisfy /// `FnOnce`. /// @@ -92,15 +94,6 @@ struct Anything { /// well, `FnOnce` is allowed to mutate internal state, but it does not have to, /// which is compatible with the const nature of `Fn`. /// -/// # Subspace types that satisfy `FnOnce` -/// The `FnOnceRef` and `FnOnceBox` types in the Subspace library satisfy -/// `FnOnce`. They must be moved from when called, and they panic if called more -/// than once, as that implies a use-after-move. -/// -/// Like the concepts, `FnRef` is convertible to `FnMutRef` is convertible to -/// `FnOnceRef`. And `FnBox` is convertible to `FnMutBox` is convertible to -/// `FnOnceBox`. -/// /// # Examples /// A function that receives a `FnOnce` matching type and calls it: /// ``` @@ -165,12 +158,8 @@ concept FnOnce = requires { /// The version of a callable object that is allowed to mutate internal state /// and may be called multiple times. /// -/// A `FnMut` is typically the best fit for -/// any callable that may be called one or more times. However when a template -/// (or constexpr) is not required, receiving a `FnMutRef` instead of `FnMut -/// auto` will typically produce less code by using type erasure to avoid -/// template instantiation. -/// +/// A `FnMut` is typically the best fit for any callable that may be called one +/// or more times. /// Because a `FnMut` is able to mutate internal state, it may return different /// values each time it is called with the same inputs. /// @@ -189,13 +178,13 @@ concept FnOnce = requires { /// ``` /// /// # Use of `FnMut` -/// `FnMut` should be received by value typically. If received as a rvalue -/// (universal) reference, it should be constrained by -/// [`IsMoveRef`]($sus::mem::IsMoveRef) to avoid moving out of -/// an lvalue in the caller. +/// `FnMut` should be received by value typically, though it can be received by +/// reference if mutations should be visible to the caller. /// /// A `FnMut` should be called by passing it to `sus::fn::call_mut()` along with -/// any arguments. A `FnMut` may be called any number of times, unlike `FnOnce`, +/// any arguments. This ensures the correct overload is called on the object and +/// that method pointers are called correctly. +/// A `FnMut` may be called any number of times, unlike `FnOnce`, /// and should not be moved when called. /// /// # Compatibility @@ -205,15 +194,6 @@ concept FnOnce = requires { /// well, `FnOnce` is allowed to mutate internal state, but it does not have to, /// which is compatible with the const nature of `Fn`. /// -/// # Subspace types that satisfy `FnMut` -/// The `FnMutRef` and `FnMutBox` types in the Subspace library satisfy `FnMut` -/// (and thus `FnOnce` as well). They may be called any number of times and are -/// able to mutate internal state. -/// -/// Like the concepts, `FnRef` is convertible to `FnMutRef` is convertible to -/// `FnOnceRef`. And `FnBox` is convertible to `FnMutBox` is convertible to -/// `FnOnceBox`. -/// /// # Examples /// A function that receives a `FnMut` matching type and calls it: /// ``` @@ -281,27 +261,25 @@ concept FnMut = requires { /// The version of a callable object that may be called multiple times without /// mutating internal state. /// -/// A `Fn` is useful for a callable that is received as -/// a const reference to indicate it and may be called one or more times and -/// does not change between call. However when a template (or constexpr) is not -/// required, receiving a `FnRef` instead of `const Fn auto&` will typically -/// produce less code by using type erasure to avoid template instantiation. +/// A `Fn` is useful for a callable that is expected to be called one or more +/// times and whose results do not change between calls. This is of course +/// possible to violate with `mutable` or global state, but it is discouraged +/// as it violates the `Fn` protocol expectations of the caller. +/// [`FnMut`]($sus::fn::FnMut) should be used when the function will +/// mutate anything and can return different values as a result. /// -/// Because a `FnMut` is able to mutate internal state, it may return different -/// values each time it is called with the same inputs. -/// -/// A type that satisfies `FnMut` will return a type that can be converted to -/// `R` when called with the arguments `Args...`. `FnMut` is satisfied by -/// being callable as an lvalue (which is done by providing an operator() that -/// is not `&&`-qualified). Mutable and const lambdas will satisfy -/// `FnMut`. +/// A type that satisfies `Fn` will return a type that can be converted to +/// `R` when called with the arguments `Args...`. `Fn` is satisfied by +/// being callable as a const lvalue (which is done by providing an operator() +/// that is `const`- or `const&`-qualified). Const lambdas will satisfy +/// `Fn` but mutable ones will not. /// /// The second argument of `Fn` is a function signature with the format /// `ReturnType(Args...)`, where `Args...` are the arguments that will be passed /// to the `Fn` and `ReturnType` is what is expected to be received back. It /// would appear as a matching concept as: /// ``` -/// void function(const Fn auto& f) { ... } +/// void function(Fn auto f) { ... } /// ``` /// /// # Use of `Fn` @@ -309,9 +287,10 @@ concept FnMut = requires { /// const reference. /// /// A `Fn` should be called by passing it to `std::fn::call()` along with any -/// arguments. This ensures the correct overload is called on the object. A `Fn` -/// may be called any number of times, unlike `FnOnce`, and should not be moved -/// when called. +/// arguments. This ensures the correct overload is called on the object and +/// that method pointers are called correctly. +/// A `Fn` may be called any number of times, unlike `FnOnce`, and should not +/// be moved when called. /// /// # Compatibility /// Any callable type that satisfies `Fn` will also satisfy `FnMut` and @@ -320,21 +299,12 @@ concept FnMut = requires { /// are able to mutate state when run, they are not required to and a constant /// `Fn` satisfies them. /// -/// # Subspace types that satisfy `FnMut` -/// The `FnRef` and `FnBox` types in the Subspace library satisfy `Fn` (and thus -/// `FnMut` and `FnOnce` as well). They may be called any number of times and -/// are callable as a const object. -/// -/// Like the concepts, `FnRef` is convertible to `FnMutRef` is convertible to -/// `FnOnceRef`. And `FnBox` is convertible to `FnMutBox` is convertible to -/// `FnOnceBox`. -/// /// # Examples /// A function that receives a `Fn` matching type and calls it: /// ``` /// // Accepts any type that can be called once with (Option) and returns /// // i32. -/// static i32 do_stuff(const sus::fn::Fn)> auto& f) { +/// static i32 do_stuff(sus::fn::Fn)> auto f) { /// return sus::fn::call(f, sus::some(400)) + /// sus::fn::call(f, sus::some(100)); /// } diff --git a/sus/fn/fn_ref.h b/sus/fn/fn_ref.h deleted file mode 100644 index ee4f220d6..000000000 --- a/sus/fn/fn_ref.h +++ /dev/null @@ -1,511 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// IWYU pragma: private, include "sus/fn/fn.h" -// IWYU pragma: friend "sus/.*" -#pragma once - -#include "sus/assertions/unreachable.h" -#include "sus/fn/__private/callable_types.h" -#include "sus/fn/__private/fn_ref_invoker.h" -#include "sus/lib/__private/forward_decl.h" -#include "sus/macros/lifetimebound.h" -#include "sus/mem/addressof.h" -#include "sus/mem/forward.h" -#include "sus/mem/never_value.h" -#include "sus/mem/relocate.h" -#include "sus/mem/replace.h" - -namespace sus::fn { - -/// A closure that erases the type of the internal callable object (lambda). A -/// FnMutRef may be called multiple times, and holds a const callable object, so -/// it will return the same value each call with the same inputs. -/// -/// FnRef can be used as a FnMutRef, which can be used as a FnOnceRef. Lambdas -/// can be converted into a FnOnceRef, FnMutRef, or FnRef directly. -/// -/// FnOnceRef, FnMutRef and FnRef are only safe to appear as lvalues when they -/// are a function parameter, and a clang-tidy check is provided to enforce -/// this. They only hold a reference to the underlying lambda so they must not -/// outlive the lambda. -/// -/// # Why can a "const" FnRef convert to a mutable FnMutRef or FnOnceRef? -/// -/// A FnMutRef or FnOnceRef is _allowed_ to mutate its storage, but a "const" -/// FnRef closure would just choose not to do so. -/// -/// However, a `const FnRef` requires that the storage is not mutated, so it is -/// not useful if converted to a `const FnMutRef` or `const FnOnceRef` which are -/// only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnOnceRef from a null -/// pointer will panic. -template -class [[sus_trivial_abi]] FnRef { - public: - /// Construction from a function pointer. - /// - /// #[doc.overloads=ctor.fnpointer] - template <__private::FunctionPointer F> - FnRef(F ptr) noexcept { - ::sus::check(ptr != nullptr); - storage_.fnptr = reinterpret_cast(ptr); - invoke_ = &__private::Invoker::template fnptr_call_const; - } - - /// Construction from a non-capturing lambda. - /// - /// #[doc.overloads=ctor.lambda] - template <__private::CallableConst F> - requires(__private::ConvertsToFunctionPointer) - constexpr FnRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_const; - } - - /// Construction from a capturing lambda or other callable object. - /// - /// #[doc.overloads=ctor.capturelambda] - template <__private::CallableConst F> - requires(!__private::ConvertsToFunctionPointer) - constexpr FnRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_const; - } - - ~FnRef() noexcept = default; - - constexpr FnRef(FnRef&& o) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - constexpr FnRef& operator=(FnRef&& o) noexcept { - storage_ = o.storage_; - invoke_ = ::sus::mem::replace(o.invoke_, nullptr); - ::sus::check(invoke_); // Catch use-after-move. - return *this; - } - - // Not copyable. - FnRef(const FnRef&) noexcept = delete; - FnRef& operator=(const FnRef&) noexcept = delete; - - /// sus::mem::Clone trait. - constexpr FnRef clone() const { - ::sus::check(invoke_); // Catch use-after-move. - return FnRef(storage_, invoke_); - } - - /// Runs the closure. - /// - /// #[doc.overloads=call.const] - inline R operator()(CallArgs... args) const& { - ::sus::check(invoke_); // Catch use-after-move. - return (*invoke_)(storage_, ::sus::forward(args)...); - } - - /// Runs and consumes the closure. - /// - /// #[doc.overloads=call.rvalue] - inline R operator()(CallArgs... args) && { - ::sus::check(invoke_); // Catch use-after-move. - return (*::sus::mem::replace(invoke_, nullptr))( - storage_, ::sus::forward(args)...); - } - - /// `sus::construct::From` trait implementation. - /// - /// FnRef satisfies `From` for the same types that it is constructible - /// from: function pointers that exactly match its own signature, and - /// const-callable objects (lambdas) that are compatible with its signature. - template - requires(std::constructible_from) - constexpr static auto from(F&& object sus_lifetimebound) noexcept { - return FnRef(::sus::forward(object)); - } - - private: - template - friend class FnOnceRef; - template - friend class FnMutRef; - - constexpr FnRef(union __private::Storage storage, - __private::InvokeFnPtr invoke) - : storage_(storage), invoke_(invoke) {} - - union __private::Storage storage_; - /// The `invoke_` pointer is set to null to indicate the FnRef is moved-from. - /// It uses another pointer value as its never-value. - __private::InvokeFnPtr invoke_; - - // A function pointer to use as a never-value for InvokeFnPointer. - static R invoke_never_value(const union __private::Storage&, CallArgs...) { - sus::unreachable(); - } - - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, - decltype(storage_.fnptr), - decltype(storage_.object), decltype(invoke_)); - sus_class_never_value_field(::sus::marker::unsafe_fn, FnRef, invoke_, - &invoke_never_value, &invoke_never_value); - // For the NeverValueField. - explicit constexpr FnRef(::sus::mem::NeverValueConstructor) noexcept - : invoke_(&invoke_never_value) {} -}; - -/// A closure that erases the type of the internal callable object (lambda) that -/// may mutate internal state. A FnMutRef may be called multiple times, and may -/// return a different value on each call with the same inputs. -/// -/// FnRef can be used as a FnMutRef, which can be used as a FnOnceRef. Lambdas -/// can be converted into a FnOnceRef, FnMutRef, or FnRef directly. -/// -/// FnOnceRef, FnMutRef and FnRef are only safe to appear as lvalues when they -/// are a function parameter, and a clang-tidy check is provided to enforce -/// this. They only hold a reference to the underlying lambda so they must not -/// outlive the lambda. -/// -/// # Why can a "const" FnRef convert to a mutable FnMutRef or FnOnceRef? -/// -/// A FnMutRef or FnOnceRef is _allowed_ to mutate its storage, but a "const" -/// FnRef closure would just choose not to do so. -/// -/// However, a `const FnRef` requires that the storage is not mutated, so it is -/// not useful if converted to a `const FnMutRef` or `const FnOnceRef` which are -/// only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnOnceRef from a null -/// pointer will panic. -template -class [[sus_trivial_abi]] FnMutRef { - public: - /// Construction from a function pointer or captureless lambda. - /// - /// #[doc.overloads=ctor.fnpointer] - template <__private::FunctionPointer F> - FnMutRef(F ptr) noexcept { - ::sus::check(ptr != nullptr); - storage_.fnptr = reinterpret_cast(ptr); - invoke_ = &__private::Invoker::template fnptr_call_mut; - } - - /// Construction from a non-capturing lambda. - /// - /// #[doc.overloads=ctor.lambda] - template <__private::CallableMut F> - requires(__private::ConvertsToFunctionPointer) - constexpr FnMutRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_mut; - } - - /// Construction from a capturing lambda or other callable object. - /// - /// #[doc.overloads=ctor.capturelambda] - template <__private::CallableMut F> - requires(!__private::ConvertsToFunctionPointer) - constexpr FnMutRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_mut; - } - - /// Construction from FnRef. - /// - /// Since FnRef is callable, FnMutRef is already constructible from it, but - /// this constructor avoids extra indirections being inserted when converting, - /// since otherwise an extra invoker call would be introduced. - /// - /// #[doc.overloads=ctor.fnref] - constexpr FnMutRef(FnRef&& o sus_lifetimebound) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - - ~FnMutRef() noexcept = default; - - constexpr FnMutRef(FnMutRef&& o) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - constexpr FnMutRef& operator=(FnMutRef&& o) noexcept { - storage_ = o.storage_; - invoke_ = ::sus::mem::replace(o.invoke_, nullptr); - ::sus::check(invoke_); // Catch use-after-move. - return *this; - } - - // Not copyable. - FnMutRef(const FnMutRef&) noexcept = delete; - FnMutRef& operator=(const FnMutRef&) noexcept = delete; - - /// sus::mem::Clone trait. - constexpr FnMutRef clone() const { - ::sus::check(invoke_); // Catch use-after-move. - return FnMutRef(storage_, invoke_); - } - - /// Runs the closure. - /// - /// #[doc.overloads=call.mut] - inline R operator()(CallArgs... args) & { - ::sus::check(invoke_); // Catch use-after-move. - return (*invoke_)(storage_, ::sus::forward(args)...); - } - - /// Runs and consumes the closure. - /// - /// #[doc.overloads=call.rvalue] - inline R operator()(CallArgs... args) && { - ::sus::check(invoke_); // Catch use-after-move. - return (*::sus::mem::replace(invoke_, nullptr))( - storage_, ::sus::forward(args)...); - } - - /// `sus::construct::From` trait implementation. - /// - /// FnMutRef satisfies `From` for the same types that it is constructible - /// from: function pointers that exactly match its own signature, and callable - /// objects (lambdas) that are compatible with its signature. - template - requires(std::constructible_from) - constexpr static auto from(F&& object sus_lifetimebound) noexcept { - return FnMutRef(::sus::forward(object)); - } - - private: - template - friend class FnOnceRef; - - constexpr FnMutRef(union __private::Storage storage, - __private::InvokeFnPtr invoke) - : storage_(storage), invoke_(invoke) {} - - union __private::Storage storage_; - /// The `invoke_` pointer is set to null to indicate the `FnMutRef` is - /// moved-from. It uses another pointer value as its never-value. - __private::InvokeFnPtr invoke_; - - // A function pointer to use as a never-value for InvokeFnPointer. - static R invoke_never_value(const union __private::Storage&, CallArgs...) { - sus::unreachable(); - } - - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, - decltype(storage_.fnptr), - decltype(storage_.object), decltype(invoke_)); - sus_class_never_value_field(::sus::marker::unsafe_fn, FnMutRef, invoke_, - &invoke_never_value, &invoke_never_value); - // For the NeverValueField. - explicit constexpr FnMutRef(::sus::mem::NeverValueConstructor) noexcept - : invoke_(&invoke_never_value) {} -}; - -/// A closure that erases the type of the internal callable object (lambda). A -/// FnOnceRef may only be called a single time. -/// -/// FnRef can be used as a FnMutRef, which can be used as a FnOnceRef. Lambdas -/// can be converted into a FnOnceRef, FnMutRef, or FnRef directly. -/// -/// FnOnceRef, FnMutRef and FnRef are only safe to appear as lvalues when they -/// are a function parameter, and a clang-tidy check is provided to enforce -/// this. They only hold a reference to the underlying lambda so they must not -/// outlive the lambda. -/// -/// # Why can a "const" FnRef convert to a mutable FnMutRef or FnOnceRef? -/// -/// A FnMutRef or FnOnceRef is _allowed_ to mutate its storage, but a "const" -/// FnRef closure would just choose not to do so. -/// -/// However, a `const FnRef` requires that the storage is not mutated, so it is -/// not useful if converted to a `const FnMutRef` or `const FnOnceRef` which are -/// only callable as mutable objects. -/// -/// # Null pointers -/// -/// A null function pointer is not allowed, constructing a FnOnceRef from a null -/// pointer will panic. -template -class [[sus_trivial_abi]] FnOnceRef { - public: - /// Construction from a function pointer or captureless lambda. - /// - /// #[doc.overloads=ctor.fnpointer] - template <__private::FunctionPointer F> - FnOnceRef(F ptr) noexcept { - ::sus::check(ptr != nullptr); - storage_.fnptr = reinterpret_cast(ptr); - invoke_ = &__private::Invoker::template fnptr_call_mut; - } - - /// Construction from a non-capturing lambda. - /// - /// #[doc.overloads=ctor.lambda] - template <__private::CallableOnceMut F> - requires(__private::ConvertsToFunctionPointer) - constexpr FnOnceRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_once; - } - - /// Construction from a capturing lambda or other callable object. - /// - /// #[doc.overloads=ctor.capturelambda] - template <__private::CallableOnceMut F> - requires(!__private::ConvertsToFunctionPointer) - constexpr FnOnceRef(F&& object sus_lifetimebound) noexcept { - storage_.object = ::sus::mem::addressof(object); - invoke_ = &__private::Invoker< - std::remove_reference_t>::template object_call_once; - } - - /// Construction from FnMutRef. - /// - /// Since FnMutRef is callable, FnOnceRef is already constructible from it, - /// but this constructor avoids extra indirections being inserted when - /// converting, since otherwise an extra invoker call would be introduced. - /// - /// #[doc.overloads=ctor.fnmutref] - constexpr FnOnceRef(FnMutRef&& o sus_lifetimebound) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - - /// Construction from FnRef. - /// - /// Since FnRef is callable, FnOnceRef is already constructible from it, but - /// this constructor avoids extra indirections being inserted when converting, - /// since otherwise an extra invoker call would be introduced. - /// - /// #[doc.overloads=ctor.fnref] - constexpr FnOnceRef(FnRef&& o sus_lifetimebound) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - - ~FnOnceRef() noexcept = default; - - constexpr FnOnceRef(FnOnceRef&& o) noexcept - : storage_(o.storage_), invoke_(::sus::mem::replace(o.invoke_, nullptr)) { - ::sus::check(invoke_); // Catch use-after-move. - } - constexpr FnOnceRef& operator=(FnOnceRef&& o) noexcept { - storage_ = o.storage_; - invoke_ = ::sus::mem::replace(o.invoke_, nullptr); - ::sus::check(invoke_); // Catch use-after-move. - return *this; - } - - // Not copyable. - FnOnceRef(const FnOnceRef&) noexcept = delete; - FnOnceRef& operator=(const FnOnceRef&) noexcept = delete; - - /// A split FnOnceRef object, which can be used to construct other FnOnceRef - /// objects, but enforces that only one of them is called. - /// - /// The Split object must not outlive the FnOnceRef it's constructed from or - /// Undefined Behaviour results. - class Split { - public: - Split(FnOnceRef& fn sus_lifetimebound) : fn_(fn) {} - - // Not Copy or Move, only used to construct FnOnceRef objects, which are - // constructible from this type. - Split(Split&&) = delete; - Split& operator=(Split&&) = delete; - - /// Runs the underlying `FnOnceRef`. The `FnOnceRef` may only be called a - /// single time and will panic on the second call. - R operator()(CallArgs... args) && { - return ::sus::move(fn_)(::sus::forward(args)...); - } - - private: - FnOnceRef& fn_; - }; - - /// A `FnOnceRef` can be split into any number of `FnOnceRef` objects, while - /// enforcing that the underlying function is only called a single time. - /// - /// This method returns a type that can convert into any number of `FnOnceRef` - /// objects. If two of them are called, the second call will panic. - /// - /// The returned object must not outlive the `FnOnceRef` object it is - /// constructed from, this is normally enforced by only using the `FnOnceRef` - /// type in function parameters, which ensures it lives for the entire - /// function body, and calling `split()` to construct temporary objects for - /// passing to other functions that receive a `FnOnceRef`. The result of - /// `split()` should never be stored as a member of an object. - /// - /// Only callable on an lvalue FnOnceRef (typically written as a function - /// parameter) as an rvalue can be simply passed along without splitting. - constexpr Split split() & noexcept sus_lifetimebound { return Split(*this); } - - /// Runs and consumes the closure. - inline R operator()(CallArgs... args) && { - ::sus::check(invoke_); // Catch use-after-move. - return (*::sus::mem::replace(invoke_, nullptr))( - storage_, ::sus::forward(args)...); - } - - /// `sus::construct::From` trait implementation. - /// - /// FnOnceRef satisfies `From` for the same types that it is constructible - /// from: function pointers that exactly match its own signature, and callable - /// objects (lambdas) that are compatible with its signature. - template - requires(std::constructible_from) - constexpr static auto from(F&& object sus_lifetimebound) noexcept { - return FnOnceRef(::sus::forward(object)); - } - - private: - friend FnMutRef; - friend FnRef; - - constexpr FnOnceRef(union __private::Storage storage, - __private::InvokeFnPtr invoke) - : storage_(storage), invoke_(invoke) {} - - union __private::Storage storage_; - /// The `invoke_` pointer is set to null to indicate the FnOnceRef is - /// moved-from. It uses another pointer value as its never-value. - __private::InvokeFnPtr invoke_; - - // A function pointer to use as a never-value for InvokeFnPointer. - static R invoke_never_value(const union __private::Storage&, CallArgs...) { - sus::unreachable(); - } - - sus_class_trivially_relocatable(::sus::marker::unsafe_fn, - decltype(storage_.fnptr), - decltype(storage_.object), decltype(invoke_)); - sus_class_never_value_field(::sus::marker::unsafe_fn, FnOnceRef, invoke_, - &invoke_never_value, &invoke_never_value); - // For the NeverValueField. - explicit constexpr FnOnceRef(::sus::mem::NeverValueConstructor) noexcept - : invoke_(&invoke_never_value) {} -}; - -} // namespace sus::fn diff --git a/sus/fn/fn_ref_unittest.cc b/sus/fn/fn_ref_unittest.cc deleted file mode 100644 index f25b2f38f..000000000 --- a/sus/fn/fn_ref_unittest.cc +++ /dev/null @@ -1,891 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "googletest/include/gtest/gtest.h" -#include "sus/construct/into.h" -#include "sus/fn/fn.h" -#include "sus/mem/forward.h" -#include "sus/mem/move.h" -#include "sus/mem/never_value.h" -#include "sus/mem/relocate.h" -#include "sus/mem/replace.h" -#include "sus/option/option.h" -#include "sus/prelude.h" - -namespace { - -using sus::construct::Into; -using sus::fn::FnMutRef; -using sus::fn::FnOnceRef; -using sus::fn::FnRef; - -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); - -struct Copyable { - Copyable(int i) : i(i) {} - Copyable(const Copyable& c) = default; - Copyable& operator=(const Copyable& c) = default; - ~Copyable() { i = -10000000; } - - int i = 0; -}; - -struct MoveOnly { - MoveOnly(int i) : i(i) {} - MoveOnly(const MoveOnly& c) = delete; - MoveOnly& operator=(const MoveOnly& c) = delete; - MoveOnly(MoveOnly&& c) : i(::sus::mem::replace(c.i, -1)) {} - MoveOnly& operator=(MoveOnly&& c) { - i = ::sus::mem::replace(c.i, -1); - return *this; - } - ~MoveOnly() { i = -10000000; } - - int i = 0; -}; - -// clang-format-off - -struct BaseClass {}; -struct SubClass : public BaseClass {}; - -static_assert(sizeof(FnOnceRef) == 2 * sizeof(void (*)())); - -void v_v_function() {} -int i_f_function(float) { return 0; } -void v_f_function(float) {} -BaseClass* b_b_function(BaseClass* b) { return b; } -SubClass* s_b_function(BaseClass* b) { return static_cast(b); } -SubClass* s_s_function(SubClass* b) { return b; } - -auto b_b_lambda = [a = 1](BaseClass* b) -> BaseClass* { return b; }; -auto s_b_lambda = [a = 1](BaseClass* b) -> SubClass* { - return static_cast(b); -}; -auto s_s_lambda = [a = 1](SubClass* b) -> SubClass* { return b; }; - -// FnRef types all have a never-value field. -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -static_assert(sus::mem::NeverValueField>); -// Which allows them to not require a flag in Option. -static_assert(sizeof(sus::Option>) == - sizeof(FnOnceRef)); - -// Closures are not copyable. -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -static_assert(!std::is_copy_constructible_v>); -static_assert(!std::is_copy_assignable_v>); -// Closures can be moved. -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); -static_assert(std::is_move_constructible_v>); -static_assert(std::is_move_assignable_v>); - -// FnMutRef and FnRef are Clone, but not FnOnceRef. None of them are Copy. -static_assert(sus::mem::Move>); -static_assert(sus::mem::Move>); -static_assert(sus::mem::Move>); -static_assert(!sus::mem::Copy>); -static_assert(!sus::mem::Copy>); -static_assert(!sus::mem::Copy>); -static_assert(!sus::mem::Clone>); -static_assert(sus::mem::Clone>); -static_assert(sus::mem::Clone>); - -// Closures are trivially relocatable. -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); -static_assert(sus::mem::relocate_by_memcpy>); - -// clang-format off - -// A function pointer, or convertible lambda, can be bound to FnOnceRef, FnMutRef and -// FnRef. -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert(std::is_constructible_v, decltype([]() {})>); -static_assert(std::is_constructible_v, decltype(v_v_function)>); -static_assert(std::is_constructible_v, decltype(v_v_function)>); -static_assert(std::is_constructible_v, decltype(v_v_function)>); -// Non-void types for the same. -static_assert(std::is_constructible_v< - FnOnceRef, decltype([](float) { return 1; })> -); -static_assert(std::is_constructible_v< - FnMutRef, decltype([](float) { return 1; })> -); -static_assert(std::is_constructible_v< - FnRef, decltype([](float) { return 1; })> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(i_f_function)> -); -static_assert(std::is_constructible_v< - FnMutRef, decltype(i_f_function)> -); -static_assert(std::is_constructible_v< - FnRef, decltype(i_f_function)> -); -// Lambdas with bound args can be bound to FnOnceRef, FnMutRef and FnRef. -static_assert(std::is_constructible_v< - FnOnceRef, decltype([i = int(1)]() { (void)i; })> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype([i = int(1)]() mutable { ++i; })> -); -static_assert(std::is_constructible_v< - FnMutRef, decltype([i = int(1)]() { (void)i; })> -); -static_assert(std::is_constructible_v< - FnMutRef, decltype([i = int(1)]() mutable { ++i; })> -); -static_assert(std::is_constructible_v< - FnRef, decltype([i = int(1)]() { (void)i; })> -); -// But FnRef, which is const, can't hold a mutable lambda. -static_assert(!std::is_constructible_v< - FnRef, decltype([i = int(1)]() mutable { ++i; })> -); - -// The return type of the FnOnceRef must match that of the lambda. It will not -// allow converting to void. -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_function)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(b_b_function)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(b_b_function)> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_lambda)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(b_b_lambda)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(b_b_lambda)> -); -// Similarly, argument types can't be converted to a different type. -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_s_function)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(s_s_function)> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_s_lambda)> -); -static_assert(!std::is_constructible_v< - FnOnceRef, decltype(s_s_lambda)> -); -// But FnOnceRef type is compatible with convertible return and argument types in -// opposite directions. -// - If the return type Y of a lambda is convertible _to_ X, then FnOnceRef -// can be -// used to store it. -// - If the argument type Y of a lambda is convertible _from_ X, then -// FnOnceRef<(X)> -// can be used to store it. -// -// In both cases, the FnOnceRef is more strict than the lambda, guaranteeing that -// the lambda's requirements are met. -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_lambda)> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_lambda)> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_function)> -); -static_assert(std::is_constructible_v< - FnOnceRef, decltype(s_b_function)> -); - -// clang-format on - -template -concept can_run = - requires(Arg&& arg, FnOnceRef fnonce, FnMutRef FnMutRef - //, - // FnRef FnRef - ) { - { sus::move(fnonce)(sus::forward(arg)) }; - { FnMutRef(sus::forward(arg)) }; - //{ sus::move(FnRef)(sus::forward(arg)) }; - }; -// clang-format on - -// Int is copyable, so references are copied when passed. -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -// But for a move-only type, it can only be passed along as a reference or an -// rvalue. -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); - -// Receiving a mutable reference means it must be passed as a mutable reference. -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); - -// Receiving a const reference means it must be passed as a reference. -static_assert(can_run); -static_assert(can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); -static_assert(!can_run); - -TEST(FnRef, Pointer) { - { - auto receive_fn = [](FnOnceRef f, i32 a, i32 b) { - return sus::move(f)(a, b); - }; - auto* ptr = +[](i32 a, i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(ptr, 1, 2), 4); - } - { - auto receive_fn = [](FnMutRef f, i32 a, i32 b) { - f(a, b); - return sus::move(f)(a, b); - }; - auto* ptr = +[](i32 a, i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(ptr, 1, 2), 4); - } - { - { - auto receive_fn = [](FnRef f, i32 a, i32 b) { - f(a, b); - return sus::move(f)(a, b); - }; - auto* ptr = +[](i32 a, i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(ptr, 1, 2), 4); - } - } -} - -TEST(FnRef, CapturelessLambda) { - { - auto receive_fn = [](FnOnceRef f, i32 a, i32 b) { - return sus::move(f)(a, b); - }; - auto lambda = [](i32 a, i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 1, 2), 4); - } - { - auto receive_fn = [](FnMutRef f, i32 a, i32 b) { - f(a, b); - return sus::move(f)(a, b); - }; - auto lambda = [i = 1_i32](i32 a, i32 b) mutable { - i += 1; - return a * 2 + b; - }; - EXPECT_EQ(receive_fn(lambda, 1, 2), 4); - } - { - { - auto receive_fn = [](FnRef f, i32 a, i32 b) { - f(a, b); - return sus::move(f)(a, b); - }; - auto lambda = [](i32 a, i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 1, 2), 4); - } - } -} - -TEST(FnRef, Lambda) { - { - auto receive_fn = [](FnOnceRef f, i32 b) { - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 2), 4); - } - { - auto receive_fn = [](FnMutRef f, i32 b) { - f(b); - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](i32 b) mutable { - a += 1; - return a * 2 + b; - }; - EXPECT_EQ(receive_fn(lambda, 2), 8); - } - { - auto receive_fn = [](FnRef f, i32 b) { - f(b); - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](i32 b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 2), 4); - } -} - -TEST(FnRef, TemplateLambda) { - { - auto receive_fn = [](FnOnceRef f, i32 b) { - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](auto b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 2), 4); - } - { - auto receive_fn = [](FnMutRef f, i32 b) { - f(b); - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](auto b) mutable { - a += 1; - return a * 2 + b; - }; - EXPECT_EQ(receive_fn(lambda, 2), 8); - } - { - auto receive_fn = [](FnRef f, i32 b) { - f(b); - return sus::move(f)(b); - }; - auto lambda = [a = 1_i32](auto b) { return a * 2 + b; }; - EXPECT_EQ(receive_fn(lambda, 2), 4); - } -} - -TEST(FnDeathTest, NullPointer) { - void (*f)() = nullptr; -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH((FnOnceRef(f)), ""); -#endif -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH((FnMutRef(f)), ""); -#endif -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH((FnRef(f)), ""); -#endif -} - -TEST(FnDeathTest, CallAfterMoveConstruct) { - { - [](FnOnceRef x) { - [](auto) {}(sus::move(x)); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } - { - [](FnMutRef x) { - [](auto) {}(sus::move(x)); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } - { - [](FnRef x) { - [](auto) {}(sus::move(x)); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } -} - -TEST(FnDeathTest, CallAfterMoveAssign) { - { - [](FnOnceRef x, FnOnceRef y) { - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}, []() {}); - } - { - [](FnMutRef x, FnMutRef y) { - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}, []() {}); - } - { - [](FnRef x, FnRef y) { - y = sus::move(x); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}, []() {}); - } -} - -TEST(FnDeathTest, CallAfterCall) { - { - [](FnOnceRef x) { - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } - { - [](FnMutRef x) { - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } - { - [](FnRef x) { - sus::move(x)(); -#if GTEST_HAS_DEATH_TEST - EXPECT_DEATH(sus::move(x)(), ""); -#endif - }([]() {}); - } -} - -TEST(FnMutRef, ConvertToFnOnce) { - auto receive_fnonce = [](FnOnceRef x) { return sus::move(x)(); }; - auto receive_fnmut = [&](FnMutRef x) { - return receive_fnonce(sus::move(x)); - }; - EXPECT_EQ(receive_fnmut([]() { return 2_i32; }), 2); -} - -TEST(FnRef, ConvertToFnOnce) { - auto receive_fnonce = [](FnOnceRef x) { return sus::move(x)(); }; - auto receive_fn = [&](FnRef x) { - return receive_fnonce(sus::move(x)); - }; - EXPECT_EQ(receive_fn([]() { return 2_i32; }), 2); -} - -TEST(FnRef, ConvertToFnMut) { - auto receive_fnmut = [](FnMutRef x) { return sus::move(x)(); }; - auto receive_fn = [&](FnRef x) { return receive_fnmut(sus::move(x)); }; - EXPECT_EQ(receive_fn([]() { return 2_i32; }), 2); -} - -TEST(FnRef, ConstructionFromConstMut) { - struct Captureless { - i32 operator()() const { return 2; } - }; - struct Capture { - i32 operator()() const { return i; } - i32 i = 2; - }; - - // Const callable can be put in all the FnRef types. - static_assert(sus::fn::__private::CallableMut); - EXPECT_EQ(2, - [](FnOnceRef m) { return sus::move(m)(); }(Captureless())); - EXPECT_EQ(2, [](FnMutRef m) { return sus::move(m)(); }(Captureless())); - EXPECT_EQ(2, [](FnRef m) { return sus::move(m)(); }(Captureless())); - static_assert(sus::fn::__private::CallableMut); - EXPECT_EQ(2, [](FnOnceRef m) { return sus::move(m)(); }(Capture())); - EXPECT_EQ(2, [](FnMutRef m) { return sus::move(m)(); }(Capture())); - EXPECT_EQ(2, [](FnRef m) { return sus::move(m)(); }(Capture())); - - struct CapturelessMut { - i32 operator()() { return 2; } - }; - struct CaptureMut { - i32 operator()() { return i; } - i32 i = 2; - }; - - // Mutable callable can only be put in the mutable FnRef types. - static_assert(sus::fn::__private::CallableMut); - static_assert(std::is_constructible_v, CapturelessMut&&>); - static_assert(std::is_constructible_v, CapturelessMut&&>); - static_assert(!std::is_constructible_v, CapturelessMut&&>); - EXPECT_EQ( - 2, [](FnOnceRef m) { return sus::move(m)(); }(CapturelessMut())); - EXPECT_EQ(2, - [](FnMutRef m) { return sus::move(m)(); }(CapturelessMut())); - - static_assert(sus::fn::__private::CallableMut); - static_assert(std::is_constructible_v, CaptureMut&&>); - static_assert(std::is_constructible_v, CaptureMut&&>); - static_assert(!std::is_constructible_v, CaptureMut&&>); - EXPECT_EQ(2, [](FnOnceRef m) { return sus::move(m)(); }(CaptureMut())); - EXPECT_EQ(2, [](FnMutRef m) { return sus::move(m)(); }(CaptureMut())); -} - -TEST(FnRef, IntoFromConstMut) { - i32 (*f)() = +[]() { return 2_i32; }; - - // Function pointer can be put in all the FnRef types. - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - EXPECT_EQ(2, FnOnceRef::from(f)()); - EXPECT_EQ(2, FnMutRef::from(f)()); - EXPECT_EQ(2, FnRef::from(f)()); - - struct Captureless { - i32 operator()() const { return 2; } - }; - struct Capture { - i32 operator()() const { return i; } - i32 i = 2; - }; - - // Const callable can be put in all the FnRef types. - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - EXPECT_EQ(2, FnOnceRef::from(Captureless())()); - EXPECT_EQ(2, FnMutRef::from(Captureless())()); - EXPECT_EQ(2, FnRef::from(Captureless())()); - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - EXPECT_EQ(2, FnOnceRef::from(Capture())()); - EXPECT_EQ(2, FnMutRef::from(Capture())()); - EXPECT_EQ(2, FnRef::from(Capture())()); - - struct CapturelessMut { - i32 operator()() { return 2; } - }; - struct CaptureMut { - i32 operator()() { return i; } - i32 i = 2; - }; - - // Mutable callable can only be put in the mutable FnRef types. - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - static_assert(!sus::construct::Into>); - EXPECT_EQ(2, FnOnceRef::from(CapturelessMut())()); - EXPECT_EQ(2, FnMutRef::from(CapturelessMut())()); - - static_assert(sus::construct::Into>); - static_assert(sus::construct::Into>); - static_assert(!sus::construct::Into>); - EXPECT_EQ(2, FnOnceRef::from(CaptureMut())()); - EXPECT_EQ(2, FnMutRef::from(CaptureMut())()); -} - -TEST(FnRef, CallsCorrectOverload) { - static i32 const_calls; - static i32 mut_calls; - struct S { - void operator()() const { const_calls += 1; } - void operator()() { mut_calls += 1; } - }; - - [](FnOnceRef m) { sus::move(m)(); }(S()); - EXPECT_EQ(const_calls, 0); - EXPECT_EQ(mut_calls, 1); - - [](FnMutRef m) { m(); }(S()); - EXPECT_EQ(const_calls, 0); - EXPECT_EQ(mut_calls, 2); - - [](FnRef m) { m(); }(S()); - EXPECT_EQ(const_calls, 1); - EXPECT_EQ(mut_calls, 2); - - // The FnRef is converted to FnMutRef but still calls the const overload. - [](FnRef m) { [](FnMutRef m) { m(); }(sus::move(m)); }(S()); - EXPECT_EQ(const_calls, 2); - EXPECT_EQ(mut_calls, 2); - - // The FnRef is converted to FnOnceRef but still calls the const overload. - [](FnRef m) { - [](FnOnceRef m) { sus::move(m)(); }(sus::move(m)); - }(S()); - EXPECT_EQ(const_calls, 3); - EXPECT_EQ(mut_calls, 2); -} - -TEST(FnRef, Clone) { - static_assert(sus::mem::Clone>); - auto clones_fn = [](FnRef f) { - return [](FnOnceRef f1) { return sus::move(f1)(); }(f.clone()) + - [](FnOnceRef f2) { return sus::move(f2)(); }(f.clone()); - }; - EXPECT_EQ(4, clones_fn([]() { return 2_i32; })); - - static_assert(sus::mem::Clone>); - auto clones_fnmut = [](FnMutRef f) { - return [](FnOnceRef f1) { return sus::move(f1)(); }(f.clone()) + - [](FnOnceRef f2) { return sus::move(f2)(); }(f.clone()); - }; - EXPECT_EQ(5, clones_fnmut([i = 1_i32]() mutable { - i += 1; - return i; - })); - - static_assert(!sus::mem::Clone>); -} - -TEST(FnOnceRef, Split) { - // The return type of split() is not Copy or Move - static_assert( - !::sus::mem::Copy&>().split())>); - static_assert( - !::sus::mem::Move&>().split())>); - // It's only used to build more FnOnceRef objects. - static_assert(::sus::construct::Into< - decltype(std::declval&>().split()), - FnOnceRef>); - // And not FnMutRef or FnRef, as that loses the intention to only call it - // once. This is implemented by making the operator() callable as an rvalue - // only. - static_assert(!::sus::construct::Into< - decltype(std::declval&>().split()), - FnMutRef>); - static_assert( - !::sus::construct::Into< - decltype(std::declval&>().split()), FnRef>); - - // split() as rvalues. First split is run. - auto rsplits_fnonce = [](FnOnceRef f) { - i32 a = [](FnOnceRef f) { return sus::move(f)(); }(f.split()); - i32 b = [](FnOnceRef) { - // Don't run the `FnOnceRef` as only one of the splits may run the - // FnOnceRef. - return 0_i32; - }(f.split()); - return a + b; - }; - EXPECT_EQ(2, rsplits_fnonce([i = 1_i32]() mutable { - i += 1; - return i; - })); - - // split() as rvalues. Second split is run. - auto rsplits_fnonce2 = [](FnOnceRef f) { - i32 a = [](FnOnceRef) { - // Don't run the `FnOnceRef` as only one of the splits may run the - // FnOnceRef. - return 0_i32; - }(f.split()); - i32 b = [](FnOnceRef f) { return sus::move(f)(); }(f.split()); - return a + b; - }; - EXPECT_EQ(2, rsplits_fnonce([i = 1_i32]() mutable { - i += 1; - return i; - })); - - // split() as lvalues. First split is run. - auto lsplits_fnonce = [](FnOnceRef f) { - auto split = f.split(); - i32 a = [](FnOnceRef f) { return sus::move(f)(); }(f); - i32 b = [](FnOnceRef) { - // Don't run the `FnOnceRef` as only one of the splits may run the - // FnOnceRef. - return 0_i32; - }(f); - return a + b; - }; - EXPECT_EQ(2, lsplits_fnonce([i = 1_i32]() mutable { - i += 1; - return i; - })); - - // split() as lvalues. Second split is run. - auto lsplits_fnonce2 = [](FnOnceRef f) { - auto split = f.split(); - i32 a = [](FnOnceRef) { - // Don't run the `FnOnceRef` as only one of the splits may run the - // FnOnceRef. - return 0_i32; - }(f); - i32 b = [](FnOnceRef f) { return sus::move(f)(); }(f); - return a + b; - }; - EXPECT_EQ(2, lsplits_fnonce([i = 1_i32]() mutable { - i += 1; - return i; - })); -} - -struct C { - i32 method(i32 p) const& { return p + 1; } - i32 method(i32 p) & { return p + 2; } - i32 method(i32 p) && { return p + 3; } - - i32 simple() const { return 99; } -}; - -TEST(FnOnceRef, Methods) { - { - auto test_simple = [](FnOnceRef y) { - const C c; - return call_once(sus::move(y), c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnOnceRef y) { - C c; - return call_once(sus::move(y), c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnOnceRef y) { - return call_once(sus::move(y), C()); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - - // Overloaded methods. - { - auto test_const = [](FnOnceRef y) { - return call_once(sus::move(y), C(), 10_i32); - }; - EXPECT_EQ( // - test_const(static_cast(&C::method)), 10 + 1); - - auto test_mut = [](FnOnceRef y) { - C c; - return call_once(sus::move(y), c, 10_i32); - }; - EXPECT_EQ( // - test_mut(static_cast(&C::method)), 10 + 2); - - auto test_rvalue = [](FnOnceRef y) { - C c; - return call_once(sus::move(y), sus::move(c), 10_i32); - }; - EXPECT_EQ( // - test_rvalue(static_cast(&C::method)), 10 + 3); - } -} - -TEST(FnMutRef, Methods) { - { - auto test_simple = [](FnMutRef y) { - const C c; - return call_mut(y, c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnMutRef y) { - C c; - return call_mut(y, c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnMutRef y) { return call_mut(y, C()); }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - - // Overloaded methods. - { - auto test_const = [](FnMutRef y) { - return call_mut(y, C(), 10_i32); - }; - EXPECT_EQ( // - test_const(static_cast(&C::method)), 10 + 1); - - auto test_mut = [](FnMutRef y) { - C c; - return call_mut(y, c, 10_i32); - }; - EXPECT_EQ( // - test_mut(static_cast(&C::method)), 10 + 2); - - auto test_rvalue = [](FnMutRef y) { - C c; - return call_mut(y, sus::move(c), 10_i32); - }; - EXPECT_EQ( // - test_rvalue(static_cast(&C::method)), 10 + 3); - } -} - -TEST(FnRef, Methods) { - { - auto test_simple = [](FnRef y) { - const C c; - return call(y, c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnRef y) { - C c; - return call(y, c); - }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - { - auto test_simple = [](FnRef y) { return call(y, C()); }; - EXPECT_EQ(test_simple(&C::simple), 99); - } - - // Overloaded methods. - { - auto test_const = [](FnRef y) { - return call(y, C(), 10_i32); - }; - EXPECT_EQ( // - test_const(static_cast(&C::method)), 10 + 1); - - auto test_mut = [](FnRef y) { - C c; - return call(y, c, 10_i32); - }; - EXPECT_EQ( // - test_mut(static_cast(&C::method)), 10 + 2); - - auto test_rvalue = [](FnRef y) { - C c; - return call(y, sus::move(c), 10_i32); - }; - EXPECT_EQ( // - test_rvalue(static_cast(&C::method)), 10 + 3); - } -} - -} // namespace diff --git a/sus/iter/__private/iter_compare.h b/sus/iter/__private/iter_compare.h index e466ff3c2..fd287d47c 100644 --- a/sus/iter/__private/iter_compare.h +++ b/sus/iter/__private/iter_compare.h @@ -16,6 +16,7 @@ #include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_concept.h" +#include "sus/option/option.h" namespace sus::iter::__private { diff --git a/sus/iter/adaptors/filter.h b/sus/iter/adaptors/filter.h index 0c56dccb1..9a310157f 100644 --- a/sus/iter/adaptors/filter.h +++ b/sus/iter/adaptors/filter.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/filter_map.h b/sus/iter/adaptors/filter_map.h index e60d4310e..f92b800b2 100644 --- a/sus/iter/adaptors/filter_map.h +++ b/sus/iter/adaptors/filter_map.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/flat_map.h b/sus/iter/adaptors/flat_map.h index 55b5eae12..db25351fd 100644 --- a/sus/iter/adaptors/flat_map.h +++ b/sus/iter/adaptors/flat_map.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/fuse.h b/sus/iter/adaptors/fuse.h index 66e2f8c40..c74b88c97 100644 --- a/sus/iter/adaptors/fuse.h +++ b/sus/iter/adaptors/fuse.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/inspect.h b/sus/iter/adaptors/inspect.h index c6a04a953..f4be59ab2 100644 --- a/sus/iter/adaptors/inspect.h +++ b/sus/iter/adaptors/inspect.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/map.h b/sus/iter/adaptors/map.h index aad8b8be8..e297e78b6 100644 --- a/sus/iter/adaptors/map.h +++ b/sus/iter/adaptors/map.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/map_while.h b/sus/iter/adaptors/map_while.h index d175a0787..e3088d3d4 100644 --- a/sus/iter/adaptors/map_while.h +++ b/sus/iter/adaptors/map_while.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/reverse.h b/sus/iter/adaptors/reverse.h index 30bc4256f..a21859c19 100644 --- a/sus/iter/adaptors/reverse.h +++ b/sus/iter/adaptors/reverse.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_concept.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" diff --git a/sus/iter/adaptors/scan.h b/sus/iter/adaptors/scan.h index 2bf791ea3..5e1f4c717 100644 --- a/sus/iter/adaptors/scan.h +++ b/sus/iter/adaptors/scan.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/adaptors/skip_while.h b/sus/iter/adaptors/skip_while.h index a8f660d6e..aea9dadf6 100644 --- a/sus/iter/adaptors/skip_while.h +++ b/sus/iter/adaptors/skip_while.h @@ -16,7 +16,7 @@ // IWYU pragma: friend "sus/.*" #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/once_with.h b/sus/iter/once_with.h index e4fe09eda..f92511e6c 100644 --- a/sus/iter/once_with.h +++ b/sus/iter/once_with.h @@ -14,7 +14,7 @@ #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/repeat_with.h b/sus/iter/repeat_with.h index 5b12b9e51..23f49c714 100644 --- a/sus/iter/repeat_with.h +++ b/sus/iter/repeat_with.h @@ -14,7 +14,7 @@ #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/iter/successors.h b/sus/iter/successors.h index d9ff3455f..9ca89e28b 100644 --- a/sus/iter/successors.h +++ b/sus/iter/successors.h @@ -14,7 +14,7 @@ #pragma once -#include "sus/fn/fn_box_defn.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/iterator_defn.h" #include "sus/mem/move.h" #include "sus/mem/relocate.h" diff --git a/sus/ops/ord.h b/sus/ops/ord.h index 0cc0bbba2..0fc95a285 100644 --- a/sus/ops/ord.h +++ b/sus/ops/ord.h @@ -21,7 +21,6 @@ #include "sus/assertions/check.h" #include "sus/fn/fn_concepts.h" -#include "sus/fn/fn_ref.h" #include "sus/macros/lifetimebound.h" #include "sus/mem/forward.h" diff --git a/sus/result/result.h b/sus/result/result.h index c795c805e..ef115deb9 100644 --- a/sus/result/result.h +++ b/sus/result/result.h @@ -23,7 +23,7 @@ #include "fmt/format.h" #include "sus/assertions/check.h" #include "sus/assertions/unreachable.h" -#include "sus/fn/fn_ref.h" +#include "sus/fn/fn_concepts.h" #include "sus/iter/into_iterator.h" #include "sus/iter/iterator_defn.h" #include "sus/iter/once.h" From 5c47bd0c46a9b7421626934261c140404c1b28ae Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 20:27:16 -0400 Subject: [PATCH 30/31] Provide a way to construct a Box for non-Move T Box::with_args() will pass the args to the ctor of T to construct it directly on the heap. --- sus/boxed/box.h | 22 ++++++++++++++++- sus/boxed/box_unittest.cc | 18 ++++++++++++++ sus/boxed/dyn.h | 50 +++++++++++++++++++++++++++++++++++++++ sus/boxed/dyn_unittest.cc | 26 ++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/sus/boxed/box.h b/sus/boxed/box.h index e27664570..16e6b001c 100644 --- a/sus/boxed/box.h +++ b/sus/boxed/box.h @@ -52,7 +52,11 @@ namespace sus::boxed { /// * Const correctness. A `const Box` treats the inner object as `const` and /// does not expose mutable access to it. /// * Never null. A `Box` always holds a value until it is moved-from. It is -/// constructed from a value, not a pointer. +/// constructed from a value, not a pointer. Alternatively it can be +/// constructed from the constructor arguments by +/// [`with_args`]($sus::boxed::Box::with_args), like [`std::make_unique`]( +/// https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) +/// but built into the type. /// A moved-from `Box` may not be used except to be assigned to or destroyed. /// Using a moved-from `Box` will [`panic`]($sus::assertions::panic) and /// terminate the program rather than operate on a null. This prevents @@ -132,6 +136,22 @@ class [[sus_trivial_abi]] Box final { if (t_) delete t_; } + /// Constructs a Box by calling the constructor of `T` with `args`. + /// + /// This allows construction of `T` directly on the heap, which is required + /// for types which do not satisfy [`Move`]($sus::mem::Move). This is a common + /// case for virtual clases which require themselves to be heap allocated. + /// + /// For type-erasure of concept objects using [`DynConcept`]( + /// $sus::boxed::DynConcept), such as [`DynError`]($sus::error::DynError), + /// the [`from`]($sus::boxed::Box::from!dync) constructor method + /// can be used to type erase the concept object and move it to the heap. + template + requires(std::constructible_from) + constexpr static Box with_args(Args&&... args) noexcept { + return Box(FROM_POINTER, new T(::sus::forward(args)...)); + } + /// Converts `U` into a [`Box`]($sus::boxed::Box). /// /// The conversion allocates on the heap and moves `u` into it. diff --git a/sus/boxed/box_unittest.cc b/sus/boxed/box_unittest.cc index 4c9715504..07953d21a 100644 --- a/sus/boxed/box_unittest.cc +++ b/sus/boxed/box_unittest.cc @@ -62,6 +62,24 @@ TEST(Box, Construct) { } } +TEST(Box, WithArgs) { + struct NoMove { + NoMove(i32 i) : i(i) {} + + NoMove(NoMove&&) = delete; + NoMove& operator=(NoMove&&) = delete; + + i32 i; + }; + static_assert(!sus::mem::Move); + + auto b = Box::with_args(3); + EXPECT_EQ(b->i, 3); + + auto b2 = sus::move(b); + EXPECT_EQ(b2->i, 3); +} + TEST(Box, FromT) { i32 i = 3; { diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h index a81472ded..f6f3dc973 100644 --- a/sus/boxed/dyn.h +++ b/sus/boxed/dyn.h @@ -93,6 +93,8 @@ namespace sus::boxed { /// /// # Examples /// +/// ## Implementing concept type-erasure +/// /// Providing the mechanism to type erase objects that satisfy a concept named /// `MyConcept` through a `DynMyConcept` class: /// ``` @@ -205,6 +207,54 @@ namespace sus::boxed { /// d(sus::dyn(MyConceptType())); /// } /// ``` +/// +/// ## Holding dyn() in a stack variable +/// +/// When a function receives a type-erased `DynC&` by reference, it allows the +/// caller to avoid heap allocations should they wish. In the easy case, the +/// caller will simply call `sus::dyn()` directly in the function arguments to +/// construct the `DynC&` reference, which ensures it outlives the function +/// call. +/// +/// In a more complicated scenario, the caller may wish to conditionally decide +/// to pass an Option with or without a reference, or to choose between +/// different references. It is not possible to return the result of +/// `sus::dyn()` without creating a dangling stack reference, which will be +/// caught by clang in most cases. This means in particular that lambdas such +/// as those passed to functions like [`Option::map`]($sus::option::Option::map) +/// can not be used to construct the `DynC&` reference. +/// +/// In order to ensure the target of the `DynC&` reference outlives the function +/// it can be constructed as a stack variable before calling the function. +/// ``` +/// std::srand(sus::mog(std::time(nullptr))); +/// +/// auto x = [](sus::Option&> fn) { +/// if (fn.is_some()) +/// return sus::move(fn).unwrap()(); +/// else +/// return std::string("tails"); +/// }; +/// +/// auto heads = [] { return std::string("heads"); }; +/// // Type-erased `Fn` that represents `heads`. Placed on the +/// // stack to outlive its use in the `Option` and the call to `x(cb)`. +/// auto dyn_heads = sus::dyn>(heads); +/// // Conditionally holds a type-erased reference to `heads`. This requires a +/// // type-erasure that outlives the `cb` variable. +/// auto cb = [&]() -> sus::Option&> { +/// if (std::rand() % 2) return sus::some(dyn_heads); +/// return sus::none(); +/// }(); +/// +/// std::string s = x(cb); +/// +/// fmt::println("{}", s); // Prints one of "heads" or "tails. +/// ``` +/// It can greatly simplify correctness of code to use owned type-erased +/// concept objects through [`Box`]($sus::boxed::Box), such as +/// `Box>` in the above example. Though references can be +/// useful, especially in simple or perf-critical code paths. template concept DynConcept = requires { // The types are not qualified or references. diff --git a/sus/boxed/dyn_unittest.cc b/sus/boxed/dyn_unittest.cc index a0f436243..b3b6773eb 100644 --- a/sus/boxed/dyn_unittest.cc +++ b/sus/boxed/dyn_unittest.cc @@ -338,4 +338,30 @@ TEST(Dyn, Example_Macro) { } // namespace example_macro +TEST(Dyn, Example_Stack) { + std::srand(sus::mog(std::time(nullptr))); + + auto x = [](sus::Option&> fn) { + if (fn.is_some()) + return sus::move(fn).unwrap()(); + else + return std::string("tails"); + }; + + auto heads = [] { return std::string("heads"); }; + // Type-erased `Fn` that represents `heads`. Placed on the + // stack to outlive its use in the `Option` and the call to `x(cb)`. + auto dyn_heads = sus::dyn>(heads); + // Conditionally holds a type-erased reference to `heads`. This requires a + // type-erasure that outlives the `cb` variable. + auto cb = [&]() -> sus::Option&> { + if (std::rand() % 2) return sus::some(dyn_heads); + return sus::none(); + }(); + + std::string s = x(cb); + + fmt::println("{}", s); // Prints one of "heads" or "tails. +} + } // namespace From 6cc13f1de0e8161a22f62eb442b737526e53e477 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 10 Sep 2023 21:59:09 -0400 Subject: [PATCH 31/31] Syntax-highlight colourize comments in pre blocks --- subdoc/gen_tests/markdown/Syntax.html | 82 ++++++++++++++++++++++++++ subdoc/gen_tests/markdown/index.html | 13 ++++ subdoc/gen_tests/markdown/test.cc | 8 +++ subdoc/gen_tests/subdoc-test-style.css | 3 + subdoc/lib/gen/markdown_to_html.cc | 34 +++++++++++ 5 files changed, 140 insertions(+) create mode 100644 subdoc/gen_tests/markdown/Syntax.html diff --git a/subdoc/gen_tests/markdown/Syntax.html b/subdoc/gen_tests/markdown/Syntax.html new file mode 100644 index 000000000..7aabe2982 --- /dev/null +++ b/subdoc/gen_tests/markdown/Syntax.html @@ -0,0 +1,82 @@ + + + + + + + + Syntax - PROJECT NAME + + + + + + + + + + + + +
    +
    +
    +
    + + Struct + + PROJECT NAME + :: + Syntax +
    +
    + + struct + + + Syntax + +
    + { ... }; +
    +
    +
    +

    A code block with syntax highlighting.

    +
    A comment;  // At the end of the line.
    +// At the start of the line.
    +  // And after some whitespace.
    +
    + +
    +
    +
    +
    + diff --git a/subdoc/gen_tests/markdown/index.html b/subdoc/gen_tests/markdown/index.html index cf65fd554..e7d7f0e57 100644 --- a/subdoc/gen_tests/markdown/index.html +++ b/subdoc/gen_tests/markdown/index.html @@ -52,6 +52,9 @@
  • S
  • +
  • + Syntax +
  • @@ -91,6 +94,16 @@

    The summary has html tags in it.

    +
  • +
    +
    + Syntax +
    +
    +
    +

    A code block with syntax highlighting.

    +
    +
  • diff --git a/subdoc/gen_tests/markdown/test.cc b/subdoc/gen_tests/markdown/test.cc index 549f3e47c..c16505663 100644 --- a/subdoc/gen_tests/markdown/test.cc +++ b/subdoc/gen_tests/markdown/test.cc @@ -8,3 +8,11 @@ struct S {}; /// newlines in /// it. struct N {}; + +/// A code block with syntax highlighting. +/// ``` +/// A comment; // At the end of the line. +/// // At the start of the line. +/// // And after some whitespace. +/// ``` +struct Syntax {}; diff --git a/subdoc/gen_tests/subdoc-test-style.css b/subdoc/gen_tests/subdoc-test-style.css index 34a3c2575..6687ced58 100644 --- a/subdoc/gen_tests/subdoc-test-style.css +++ b/subdoc/gen_tests/subdoc-test-style.css @@ -206,6 +206,9 @@ main { color:rgb(230, 230, 230); overflow-x: auto; } +pre > code span.comment { + color: #3ff9bd; +} .section-header { font-size: 125%; diff --git a/subdoc/lib/gen/markdown_to_html.cc b/subdoc/lib/gen/markdown_to_html.cc index 4f5c7ce8a..32ed410c7 100644 --- a/subdoc/lib/gen/markdown_to_html.cc +++ b/subdoc/lib/gen/markdown_to_html.cc @@ -256,6 +256,40 @@ sus::Result markdown_to_html( } } std::string str = sus::move(parsed).str(); + usize pos; + while (true) { + pos = str.find("
    ", pos);
    +    if (pos == std::string::npos) break;
    +    usize end_pos = str.find("
    ", pos + 5u); + if (end_pos == std::string::npos) break; + + while (true) { + usize comment_pos = str.find("// ", pos); + if (comment_pos == std::string::npos) { + pos = str.size(); + break; + } + if (comment_pos >= end_pos) { + // If we went past then back up and look for the next
    .
    +        pos = end_pos + 6u;
    +        break;
    +      }
    +
    +      const std::string_view open = "";
    +      const std::string_view close = "";
    +
    +      str.insert(comment_pos, open);
    +      comment_pos += open.size();
    +      end_pos += open.size();
    +
    +      usize eol_pos = str.find("\n", comment_pos);
    +      if (eol_pos == std::string::npos) eol_pos = str.size();
    +      str.insert(eol_pos, close);
    +      eol_pos += close.size();
    +      end_pos += close.size();
    +      pos = eol_pos;
    +    }
    +  }
       return sus::ok(MarkdownToHtml{
           .full_html = sus::clone(str),
           .summary_html = summarize_html(str),