From 2e057973ef0f5b4e0a2bdb41598a29892011731b Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 6 Aug 2023 21:19:31 -0400 Subject: [PATCH] Add Iterator::try_collect() Closes #295 try_collect can collect from an iterator of `T: Try` into a container C that supports FromIterator to create C. The actual Try types do not need to support FromIterator then, and this replaces the need for Option or Result to implement FromIterator. We needed to add another operation on Try types for this, which is to convert from T: Try in an error state to U: Try in an error state, where they have the same (or a convertible) error type. This new concept is sus::ops::TryErrorConvertibleTo and it's accessed through sus::ops::try_preserve_error(). The TryErrorConvertibleTo concept is implemented for Option, Result and std::optional. --- sus/CMakeLists.txt | 2 + sus/iter/from_iterator.h | 7 +- sus/iter/iterator.h | 1 + sus/iter/iterator_defn.h | 69 +++++++++++++++-- sus/iter/iterator_impl.h | 38 +++++++++ sus/iter/iterator_unittest.cc | 112 +++++++++++++++++++++++++++ sus/iter/try_from_iterator.h | 100 ++++++++++++++++++++++++ sus/macros/__private/compiler_bugs.h | 11 +++ sus/ops/try.h | 62 +++++++++++++-- sus/option/compat_option.h | 9 +++ sus/option/option.h | 25 ++++-- sus/result/result.h | 32 ++++++-- 12 files changed, 435 insertions(+), 33 deletions(-) create mode 100644 sus/iter/iterator_impl.h create mode 100644 sus/iter/try_from_iterator.h diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index a9617a516..ca1bd1f35 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -115,6 +115,7 @@ target_sources(subspace PUBLIC "iter/iterator.h" "iter/iterator_concept.h" "iter/iterator_defn.h" + "iter/iterator_impl.h" "iter/iterator_ref.h" "iter/once.h" "iter/product.h" @@ -123,6 +124,7 @@ target_sources(subspace PUBLIC "iter/size_hint.h" "iter/size_hint_impl.h" "iter/successors.h" + "iter/try_from_iterator.h" "iter/zip.h" "macros/__private/compiler_bugs.h" "macros/assume.h" diff --git a/sus/iter/from_iterator.h b/sus/iter/from_iterator.h index 7bf39c979..1712b8649 100644 --- a/sus/iter/from_iterator.h +++ b/sus/iter/from_iterator.h @@ -50,9 +50,10 @@ concept FromIterator = /// typically called through calling `collect()` on an iterator. However this /// function can be preferrable for some readers, especially in generic template /// code. -template - requires( - FromIterator::Item>) +template + requires(FromIterator::Item> && // + ::sus::mem::IsMoveRef) constexpr inline ToType from_iter(IntoIter&& into_iter) noexcept { return FromIteratorImpl::from_iter( ::sus::move(into_iter).into_iter()); diff --git a/sus/iter/iterator.h b/sus/iter/iterator.h index 0692fb50f..ffb5113d9 100644 --- a/sus/iter/iterator.h +++ b/sus/iter/iterator.h @@ -16,6 +16,7 @@ // IWYU pragma: begin_exports #include "sus/iter/iterator_defn.h" +#include "sus/iter/iterator_impl.h" // The usize formatting is in unsigned_integer_impl.h which has an include cycle // with usize->Array->Array iterators->SizeHint->usize. So the SizeHint diff --git a/sus/iter/iterator_defn.h b/sus/iter/iterator_defn.h index 622a2da58..8daefc906 100644 --- a/sus/iter/iterator_defn.h +++ b/sus/iter/iterator_defn.h @@ -927,6 +927,57 @@ class IteratorBase { ::sus::fn::FnMut&)> auto pred) && noexcept; + /// Fallibly transforms an iterator into a collection, short circuiting if a + /// failure is encountered. + /// + /// `try_collect()` is a variation of `collect()` that allows fallible + /// conversions during collection. Its main use case is simplifying + /// conversions from iterators yielding `Option` into + /// `Option>`, or similarly for other Try types (e.g. `Result` + /// or `std::optional`). + /// + /// Importantly, `try_collect()` doesn’t require that the outer `Try` type + /// also implements FromIterator; only the `Try` type's `Output` type must + /// implement it. Concretely, this means that collecting into + /// `TryThing, _>` can be valid because `Vec` implements + /// FromIterator, even if `TryThing` doesn’t. + /// + /// Also, if a failure is encountered during `try_collect()`, the iterator is + /// still valid and may continue to be used, in which case it will continue + /// iterating starting after the element that triggered the failure. See the + /// last example below for an example of how this works. + /// + /// # Examples + /// Successfully collecting an iterator of `Option` into + /// `Option>`: + /// ``` + /// auto u = Vec>::with(some(1), some(2), some(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == some(Vec::with(1, 2, 3 ))); + /// ``` + /// Failing to collect in the same way: + /// ``` + /// auto u = Vec>::with(some(1), some(2), none(), some(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == none()); + /// ``` + /// A similar example, but with [`Result`](sus::result::Result): + /// ``` + /// enum Error { ERROR }; + /// auto u = Vec>::with(ok(1), ok(2), ok(3)); + /// auto v = sus::move(u).into_iter().try_collect>(); + /// sus::check(v == ok(Vec::with(1, 2, 3))); + /// auto w = Vec>::with(ok(1), ok(2), err(ERROR), ok(3)); + /// auto x = sus::move(w).into_iter().try_collect>(); + /// sus::check(x == err(ERROR)); + /// ``` + template + requires(::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) + constexpr auto try_collect() noexcept; + /// This function acts like `fold()` but the closure returns a type that /// satisfies `sus::ops::Try` and which converts to the accumulator type on /// success through the Try concept. If the closure ever returns failure, the @@ -1059,7 +1110,7 @@ class IteratorBase { /// sus::move(iter).collect>() /// ``` template C> - constexpr FromIterator auto collect() && noexcept; + constexpr C collect() && noexcept; /// Transforms an iterator into a Vec. /// @@ -1433,7 +1484,8 @@ template template < ::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t&)> KeyFn, - int&..., class Key> + int&..., + class Key> requires(::sus::ops::Ord && // !std::is_reference_v) constexpr Option IteratorBase::max_by_key( @@ -1490,7 +1542,8 @@ template template < ::sus::fn::FnMut<::sus::fn::NonVoid(const std::remove_reference_t&)> KeyFn, - int&..., class Key> + int&..., + class Key> requires(::sus::ops::Ord && // !std::is_reference_v) constexpr Option IteratorBase::min_by_key( @@ -1699,7 +1752,8 @@ constexpr Option IteratorBase::rposition( template template F, - int&..., class R, class InnerR> + int&..., class R, + class InnerR> requires(::sus::option::__private::IsOptionType::value && // !std::is_reference_v) constexpr Iterator auto IteratorBase::scan( @@ -1859,14 +1913,13 @@ constexpr std::weak_ordering IteratorBase::weak_cmp_by( template template C> -constexpr FromIterator auto -IteratorBase::collect() && noexcept { +constexpr C IteratorBase::collect() && noexcept { return from_iter(static_cast(*this)); } template -::sus::containers::Vec constexpr IteratorBase< - Iter, Item>::collect_vec() && noexcept { +constexpr ::sus::containers::Vec +IteratorBase::collect_vec() && noexcept { return from_iter<::sus::containers::Vec>(static_cast(*this)); } diff --git a/sus/iter/iterator_impl.h b/sus/iter/iterator_impl.h new file mode 100644 index 000000000..211c48cd2 --- /dev/null +++ b/sus/iter/iterator_impl.h @@ -0,0 +1,38 @@ +// 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/iter/iterator.h" +// IWYU pragma: friend "sus/.*" +#pragma once + +#include + +#include "sus/iter/try_from_iterator.h" + +namespace sus::iter { + +// Implementation of IteratorBase::try_collect has to live here to avoid +// cyclical includes, as the impl of try_from_iter() needs to see IteratorBase +// in order to make something that can be consumed by FromIterator. +template +template + requires(::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) +constexpr auto IteratorBase::try_collect() noexcept { + return try_from_iter(static_cast(*this)); +} + +} // namespace sus::iter diff --git a/sus/iter/iterator_unittest.cc b/sus/iter/iterator_unittest.cc index f0e5fb7c8..a87641d0d 100644 --- a/sus/iter/iterator_unittest.cc +++ b/sus/iter/iterator_unittest.cc @@ -33,10 +33,13 @@ #include "sus/num/overflow_integer.h" #include "sus/ops/eq.h" #include "sus/prelude.h" +#include "sus/test/no_copy_move.h" using sus::containers::Array; using sus::iter::IteratorBase; using sus::option::Option; +using sus::result::Result; +using sus::test::NoCopyMove; namespace sus::test::iter { @@ -45,6 +48,10 @@ struct CollectSum { T sum; }; +struct CollectRefs { + sus::Vec vec; +}; + } // namespace sus::test::iter template @@ -57,6 +64,16 @@ struct sus::iter::FromIteratorImpl> { } }; +template <> +struct sus::iter::FromIteratorImpl { + static sus::test::iter::CollectRefs from_iter( + sus::iter::IntoIterator auto iter) noexcept { + auto v = sus::Vec(); + for (const NoCopyMove& t : sus::move(iter).into_iter()) v.push(&t); + return sus::test::iter::CollectRefs(sus::move(v)); + } +}; + namespace { using namespace sus::test::iter; @@ -460,6 +477,10 @@ TEST(Iterator, Collect) { ArrayIterator::with_array(nums).collect>(); EXPECT_EQ(collected.sum, 1 + 2 + 3 + 4 + 5); + auto from = sus::iter::from_iter>( + ArrayIterator::with_array(nums)); + EXPECT_EQ(from.sum, 1 + 2 + 3 + 4 + 5); + static_assert(sus::Array::with(1, 2, 3, 4, 5) .into_iter() .collect>() @@ -481,6 +502,97 @@ TEST(Iterator, CollectVec) { sus::Vec::with(1, 2, 3, 4, 5)); } +TEST(Iterator, TryCollect) { + // Option. + { + auto collected = sus::Array, 3>::with( + ::sus::some(1), ::sus::some(2), ::sus::some(3)) + .into_iter() + .try_collect>(); + static_assert(std::same_as>>); + EXPECT_EQ(collected.is_some(), true); + EXPECT_EQ(collected.as_value(), sus::Vec::with(1, 2, 3)); + + auto it = sus::Array, 3>::with(::sus::some(1), ::sus::none(), + ::sus::some(3)) + .into_iter(); + auto up_to_none = it.try_collect>(); + EXPECT_EQ(up_to_none, sus::none()); + auto after_none = it.try_collect>(); + EXPECT_EQ(after_none.as_value(), sus::Vec::with(3)); + + NoCopyMove n[3]; + auto refs = sus::Array, 3>::with( + ::sus::some(n[0]), ::sus::some(n[1]), ::sus::some(n[2])) + .into_iter() + .try_collect(); + EXPECT_EQ(refs.is_some(), true); + EXPECT_EQ(refs.as_value().vec[0u], &n[0]); + EXPECT_EQ(refs.as_value().vec[1u], &n[1]); + EXPECT_EQ(refs.as_value().vec[2u], &n[2]); + } + // Result. + enum Error { ERROR }; + { + auto collected = sus::Array, 3>::with( + ::sus::ok(1), ::sus::ok(2), ::sus::ok(3)) + .into_iter() + .try_collect>(); + static_assert(std::same_as, Error>>); + EXPECT_EQ(collected.as_ok(), sus::Vec::with(1, 2, 3)); + + auto it = sus::Array, 3>::with( + ::sus::ok(1), ::sus::err(ERROR), ::sus::ok(3)) + .into_iter(); + auto up_to_err = it.try_collect>(); + EXPECT_EQ(up_to_err, sus::err(ERROR)); + auto after_err = it.try_collect>(); + EXPECT_EQ(after_err.as_ok(), sus::Vec::with(3)); + } + + auto from = + sus::iter::try_from_iter>(sus::Array, 3>::with( + ::sus::some(1), ::sus::some(2), ::sus::some(3))); + EXPECT_EQ(from.as_value(), sus::Vec::with(1, 2, 3)); + + static_assert( + sus::Array, 3>::with(sus::some(1), sus::some(2), sus::some(3)) + .into_iter() + .try_collect>() + .unwrap() + .into_iter() + .sum() == 1 + 2 + 3); +} + +TEST(Iterator, TryCollect_Example) { + using sus::none; + using sus::ok; + using sus::err; + using sus::Option; + using sus::result::Result; + using sus::some; + using sus::Vec; + { + auto u = Vec>::with(some(1), some(2), some(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == some(Vec::with(1, 2, 3))); + } + { + auto u = Vec>::with(some(1), some(2), none(), some(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == none()); + } + { + enum Error { ERROR }; + auto u = Vec>::with(ok(1), ok(2), ok(3)); + auto v = sus::move(u).into_iter().try_collect>(); + sus::check(v == ok(Vec::with(1, 2, 3))); + auto w = Vec>::with(ok(1), ok(2), err(ERROR), ok(3)); + auto x = sus::move(w).into_iter().try_collect>(); + sus::check(x == err(ERROR)); + } +} + TEST(Iterator, Rev) { i32 nums[5] = {1, 2, 3, 4, 5}; diff --git a/sus/iter/try_from_iterator.h b/sus/iter/try_from_iterator.h new file mode 100644 index 000000000..fbddb9398 --- /dev/null +++ b/sus/iter/try_from_iterator.h @@ -0,0 +1,100 @@ +// 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. + +#pragma once + +#include + +#include "sus/iter/from_iterator.h" +#include "sus/iter/into_iterator.h" +#include "sus/iter/iterator_defn.h" +#include "sus/mem/move.h" +#include "sus/ops/try.h" +#include "sus/option/option.h" + +namespace sus::iter { + +namespace __private { + +template +struct TryFromIteratorUnwrapper final + : public IteratorBase< + TryFromIteratorUnwrapper, + ::sus::ops::TryOutputType> { + using FromItem = typename SourceIter::Item; + using Item = ::sus::ops::TryOutputType; + + constexpr TryFromIteratorUnwrapper(SourceIter& iter, + ::sus::Option& failure) + : iter(iter), failure(failure) {} + + constexpr ::sus::Option next() noexcept { + ::sus::option::Option input = iter.next(); + if (input.is_some()) { + if (::sus::ops::try_is_success(input.as_value())) { + return ::sus::move(input).map(&::sus::ops::try_into_output); + } else { + failure = ::sus::move(input); + } + } + return ::sus::Option(); + } + + constexpr SizeHint size_hint() const noexcept { + return SizeHint(0u, iter.size_hint().upper); + } + + SourceIter& iter; + ::sus::Option& failure; +}; + +} // namespace __private + +/// Constructs `ToType` from a type that can be turned into an `Iterator` over +/// elements of type `ItemType`. +/// +/// If a failure value is seen in the iterator, then the failure value will be +/// returned. Otherwise, the `ToType` success type (`TryOutputType`) is +/// constructed from the success values in the Iterator, and a +/// success-representing `ToType` is returned containing that success type. +/// +/// This is the other end of +/// [`Iterator::try_collect()`](::sus::iter::IteratorBase::try_collect), and is +/// typically called through calling `try_collect()` on an iterator. However +/// this function can be preferrable for some readers, especially in generic +/// template code. +template ::Item, + class ToType = ::sus::ops::TryRemapOutputType> + requires(::sus::mem::IsMoveRef && // + ::sus::ops::Try && // + FromIterator> && + // Void can not be collected from. + !std::is_void_v<::sus::ops::TryOutputType>) +constexpr inline ToType try_from_iter(IntoIter&& into_iter) noexcept { + // This is supposed to be guaranteed by TryRemapOutputType. + static_assert(::sus::ops::TryErrorConvertibleTo); + + auto&& iter = ::sus::move(into_iter).into_iter(); + using SourceIter = std::remove_reference_t; + + ::sus::Option failure; + auto out = ::sus::ops::try_from_output(from_iter( + __private::TryFromIteratorUnwrapper(iter, failure))); + if (failure.is_some()) + out = ::sus::ops::try_preserve_error(::sus::move(failure).unwrap()); + return out; +} + +} // namespace sus::iter diff --git a/sus/macros/__private/compiler_bugs.h b/sus/macros/__private/compiler_bugs.h index c82503a5a..16a190520 100644 --- a/sus/macros/__private/compiler_bugs.h +++ b/sus/macros/__private/compiler_bugs.h @@ -136,3 +136,14 @@ #define sus_gcc_bug_110905(...) #define sus_gcc_bug_110905_else(...) __VA_ARGS__ #endif + +// TODO: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110927 +// GCC does not parse dependent types in a partial specialization. +#if defined(__GNUC__) && __GNUC__ > 0 // TODO: Update when the bug is fixed. +#define sus_gcc_bug_110927(...) __VA_ARGS__ +#define sus_gcc_bug_110927_else(...) +#define sus_gcc_bug_110927_exists +#else +#define sus_gcc_bug_110927(...) +#define sus_gcc_bug_110927_else(...) __VA_ARGS__ +#endif diff --git a/sus/ops/try.h b/sus/ops/try.h index ece14754a..22bf3fc3d 100644 --- a/sus/ops/try.h +++ b/sus/ops/try.h @@ -14,6 +14,8 @@ #pragma once +#include "sus/macros/__private/compiler_bugs.h" +#include "sus/mem/forward.h" #include "sus/mem/move.h" namespace sus::ops { @@ -43,10 +45,13 @@ concept Try = requires { // The Try type is not a reference, conversions require concrete types. requires !std::is_reference_v; - // The Output type can be converted to/from the Try type. + // The Output success type can be converted to/from the Try type. typename TryImpl::Output; - // The Output type is also not a reference. - requires !std::is_reference_v::Output>; +#if !defined(sus_gcc_bug_110927_exists) + // The Try type can be used to produce another Try type with a different + // Output type but the same error type. + typename TryImpl::template RemapOutput::Output>; +#endif } // && // requires(const T& t, T&& tt) { @@ -64,9 +69,12 @@ concept Try = // construct a `T` with a void Output type, require `TryDefault` instead and // use `from_default()`. (std::is_void_v::Output> || // - requires(typename TryImpl::Output output) { + requires(typename TryImpl::Output&& output) { // from_output() converts a success type to the Try type. - { TryImpl::from_output(::sus::move(output)) } -> std::same_as; + { + TryImpl::from_output( + ::sus::forward::Output>(output)) + } -> std::same_as; }); /// Identifies Try types which can be constructed with a default success value. @@ -82,6 +90,32 @@ concept TryDefault = Try && requires { { TryImpl::from_default() } -> std::same_as; }; +/// Can be used to further constrain the relationship between two `Try` types +/// such that an error in one can be used to construct the other type. +/// +/// This allows `Try` to be returned from a function working with +/// `Try` in the case of an error, as `sus::ops::try_preserve_error()` +/// can be used to construct the error return type. +template +concept TryErrorConvertibleTo = + Try && // + Try && // + requires(From&& f) { + // preserve_error() constructs a Try type from another related type while + // passing the error state along. + { + TryImpl::template preserve_error(::sus::move(f)) + } -> std::same_as; + }; + +/// A helper to get the `Output` type for a type `T` that satisfies `Try`. +template +using TryOutputType = typename TryImpl::Output; + +/// A helper to get the `RemapOutput` type for a type `T` that satisfies `Try`. +template +using TryRemapOutputType = typename TryImpl::template RemapOutput; + /// Determines if a type `T` that satisfies `Try` represents success in its /// current state. template @@ -123,7 +157,8 @@ constexpr inline TryImpl::Output try_into_output(T&& t) noexcept { template requires(!std::is_void_v::Output>) constexpr inline T try_from_output(typename TryImpl::Output&& t) noexcept { - return TryImpl::from_output(::sus::move(t)); + return TryImpl::from_output( + ::sus::forward::Output>(t)); } /// Constructs an object of type `T` that satisfies `TryDefault` (and `Try`) @@ -140,4 +175,19 @@ constexpr inline T try_from_default() noexcept { return TryImpl::from_default(); } +/// Converts from a `Try` type `T` to another `Try` type `U` with a compatible +/// error state. The input must be in an error state, and the output will be as +/// well. +/// +/// # Panics +/// +/// If the input is not in an error state (`is_success()` would return false) +/// the function will panic. +template T> + requires(::sus::mem::IsMoveRef) +constexpr inline U try_preserve_error(T&& t) noexcept { + ::sus::check(!TryImpl::is_success(t)); + return TryImpl::preserve_error(::sus::move(t)); +} + } // namespace sus::ops diff --git a/sus/option/compat_option.h b/sus/option/compat_option.h index 503134aad..f6ca191b9 100644 --- a/sus/option/compat_option.h +++ b/sus/option/compat_option.h @@ -138,6 +138,8 @@ constexpr Option::operator std::optional*>() template struct sus::ops::TryImpl> { using Output = T; + template + using RemapOutput = std::optional; constexpr static bool is_success(const std::optional& t) { return t.has_value(); } @@ -150,6 +152,13 @@ struct sus::ops::TryImpl> { constexpr static std::optional from_output(Output t) { return std::optional(std::in_place, ::sus::move(t)); } + template + constexpr static std::optional preserve_error(std::optional) noexcept { + // The incoming optional is known to be empty (the error state) and this is + // checked by try_preserve_error() before coming here. So we can just return + // another empty optional. + return std::optional(); + } // Implements sus::ops::TryDefault for `std::optional` if `T` satisfies // `Default`. diff --git a/sus/option/option.h b/sus/option/option.h index 1fc0136ba..3294faf96 100644 --- a/sus/option/option.h +++ b/sus/option/option.h @@ -1489,6 +1489,8 @@ sus_pure_const inline constexpr auto none() noexcept { template struct sus::ops::TryImpl<::sus::option::Option> { using Output = T; + template + using RemapOutput = ::sus::option::Option; constexpr static bool is_success(const ::sus::option::Option& t) noexcept { return t.is_some(); } @@ -1500,6 +1502,14 @@ struct sus::ops::TryImpl<::sus::option::Option> { constexpr static ::sus::option::Option from_output(Output t) noexcept { return ::sus::option::Option::with(::sus::move(t)); } + template + constexpr static ::sus::option::Option preserve_error( + ::sus::option::Option) noexcept { + // The incoming Option is known to be empty (the error state) and this is + // checked by try_preserve_error() before coming here. So we can just return + // another empty Option. + return ::sus::option::Option(); + } // Implements sus::ops::TryDefault for `Option` if `T` satisfies `Default`. constexpr static ::sus::option::Option from_default() noexcept @@ -1677,16 +1687,16 @@ struct sus::iter::FromIteratorImpl<::sus::option::Option> { // until it reaches a `None` or the end. struct UntilNoneIter final : public ::sus::iter::IteratorBase { - UntilNoneIter(Iter&& iter, bool& found_none) + constexpr UntilNoneIter(Iter&& iter, bool& found_none) : iter(iter), found_none(found_none) {} - ::sus::option::Option next() noexcept { + constexpr ::sus::option::Option next() noexcept { ::sus::option::Option<::sus::option::Option> item = iter.next(); if (found_none || item.is_none()) return ::sus::option::Option(); found_none = item->is_none(); return ::sus::move(item).flatten(); } - ::sus::iter::SizeHint size_hint() const noexcept { + constexpr ::sus::iter::SizeHint size_hint() const noexcept { return ::sus::iter::SizeHint(0u, iter.size_hint().upper); } @@ -1696,10 +1706,9 @@ struct sus::iter::FromIteratorImpl<::sus::option::Option> { bool found_none = false; auto iter = UntilNoneIter(::sus::move(option_iter).into_iter(), found_none); - auto collected = sus::iter::from_iter(::sus::move(iter)); - if (found_none) - return ::sus::option::Option(); - else - return ::sus::option::Option::with(::sus::move(collected)); + auto collected = + Option::with(sus::iter::from_iter(::sus::move(iter))); + if (found_none) collected = ::sus::option::Option(); + return collected; } }; diff --git a/sus/result/result.h b/sus/result/result.h index 2f548db56..477b195bb 100644 --- a/sus/result/result.h +++ b/sus/result/result.h @@ -1140,17 +1140,27 @@ template requires(!std::is_void_v) struct sus::ops::TryImpl<::sus::result::Result> { using Output = T; + template + using RemapOutput = ::sus::result::Result; constexpr static bool is_success(const ::sus::result::Result& t) { return t.is_ok(); } constexpr static Output into_output(::sus::result::Result t) { // SAFETY: The Result is verified to be holding Ok(T) by - // `::sus::ops::try_into_output()` before it calls here. + // `sus::ops::try_into_output()` before it calls here. return ::sus::move(t).unwrap_unchecked(::sus::marker::unsafe_fn); } constexpr static ::sus::result::Result from_output(Output t) { return ::sus::result::Result::with(::sus::move(t)); } + template + constexpr static ::sus::result::Result preserve_error( + ::sus::result::Result t) noexcept { + // SAFETY: The Result is verified to be holding Err(T) by + // `sus::ops::try_preserve_error()` before it calls here. + return ::sus::result::Result::with_err( + ::sus::move(t).unwrap_err_unchecked(::sus::marker::unsafe_fn)); + } // Implements sus::ops::TryDefault for `Result` if `T` satisfies // `Default`. constexpr static ::sus::result::Result from_default() noexcept @@ -1164,12 +1174,18 @@ struct sus::ops::TryImpl<::sus::result::Result> { template struct sus::ops::TryImpl<::sus::result::Result> { using Output = void; + template + using RemapOutput = ::sus::result::Result; constexpr static bool is_success( const ::sus::result::Result& t) noexcept { return t.is_ok(); } constexpr static void into_output(::sus::result::Result t) noexcept { } + constexpr static ::sus::result::Result preserve_error( + ::sus::result::Result t) noexcept { + ::sus::move(t); // Preserve the Err by returning it. + } // Implements sus::ops::TryDefault for `Result`. constexpr static ::sus::result::Result from_default() noexcept { return ::sus::result::Result::with(); @@ -1200,10 +1216,10 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { ::sus::iter::FromIterator) { struct Unwrapper final : public ::sus::iter::IteratorBase { - Unwrapper(Iter&& iter, Option& err) : iter(iter), err(err) {} + constexpr Unwrapper(Iter&& iter, Option& err) : iter(iter), err(err) {} // sus::iter::Iterator trait. - Option next() noexcept { + constexpr Option next() noexcept { Option<::sus::result::Result> try_item = iter.next(); if (try_item.is_none()) return Option(); ::sus::result::Result result = @@ -1215,7 +1231,7 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { ::sus::move(result).unwrap_err_unchecked(::sus::marker::unsafe_fn)); return Option(); } - ::sus::iter::SizeHint size_hint() const noexcept { + constexpr ::sus::iter::SizeHint size_hint() const noexcept { return ::sus::iter::SizeHint(0u, iter.size_hint().upper); } @@ -1225,11 +1241,11 @@ struct sus::iter::FromIteratorImpl<::sus::result::Result> { auto err = Option(); auto iter = Unwrapper(::sus::move(result_iter).into_iter(), err); - auto success_out = ::sus::result::Result::with( + auto out = ::sus::result::Result::with( ::sus::iter::from_iter(::sus::move(iter))); - return ::sus::move(err).map_or_else( - [&]() { return ::sus::move(success_out); }, - [](E e) { return ::sus::result::Result::with_err(e); }); + if (err.is_some()) + out = ::sus::result::Result::with_err(::sus::move(err).unwrap()); + return out; } };