Skip to content

Commit 215a8fa

Browse files
committed
Add Iterator::try_collect()
Closes chromium#295 try_collect can collect from an iterator of `T: Try` into a container C that supports FromIterator<T: Try> to create C<U>. 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.
1 parent e5ad0b6 commit 215a8fa

11 files changed

+351
-29
lines changed

sus/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ target_sources(subspace PUBLIC
115115
"iter/iterator.h"
116116
"iter/iterator_concept.h"
117117
"iter/iterator_defn.h"
118+
"iter/iterator_impl.h"
118119
"iter/iterator_ref.h"
119120
"iter/once.h"
120121
"iter/product.h"
@@ -123,6 +124,7 @@ target_sources(subspace PUBLIC
123124
"iter/size_hint.h"
124125
"iter/size_hint_impl.h"
125126
"iter/successors.h"
127+
"iter/try_from_iterator.h"
126128
"iter/zip.h"
127129
"macros/__private/compiler_bugs.h"
128130
"macros/assume.h"

sus/iter/from_iterator.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ concept FromIterator =
5050
/// typically called through calling `collect()` on an iterator. However this
5151
/// function can be preferrable for some readers, especially in generic template
5252
/// code.
53-
template <class ToType, ::sus::iter::IntoIteratorAny IntoIter>
54-
requires(
55-
FromIterator<ToType, typename IntoIteratorOutputType<IntoIter>::Item>)
53+
template <class ToType, IntoIteratorAny IntoIter>
54+
requires(FromIterator<ToType,
55+
typename IntoIteratorOutputType<IntoIter>::Item> && //
56+
::sus::mem::IsMoveRef<IntoIter &&>)
5657
constexpr inline ToType from_iter(IntoIter&& into_iter) noexcept {
5758
return FromIteratorImpl<ToType>::from_iter(
5859
::sus::move(into_iter).into_iter());

sus/iter/iterator.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
// IWYU pragma: begin_exports
1818
#include "sus/iter/iterator_defn.h"
19+
#include "sus/iter/iterator_impl.h"
1920

2021
// The usize formatting is in unsigned_integer_impl.h which has an include cycle
2122
// with usize->Array->Array iterators->SizeHint->usize. So the SizeHint

sus/iter/iterator_defn.h

+20-5
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,22 @@ class IteratorBase {
927927
::sus::fn::FnMut<bool(const std::remove_reference_t<Item>&)> auto
928928
pred) && noexcept;
929929

930+
template <class C>
931+
requires(::sus::ops::Try<C> && //
932+
::sus::ops::Try<ItemT> &&
933+
// On failure the iterated failure is returned so it needs to be
934+
// possible.
935+
::sus::ops::TryErrorConvertibleTo<ItemT, C> &&
936+
FromIterator<::sus::ops::TryOutputType<C>,
937+
::sus::ops::TryOutputType<ItemT>> && //
938+
// Void can not be collected from or into.
939+
!std::is_void_v<::sus::ops::TryOutputType<C>> && //
940+
!std::is_void_v<::sus::ops::TryOutputType<ItemT>> &&
941+
// We construct the output types of C, so it must not be holding
942+
// a reference type.
943+
!std::is_reference_v<::sus::ops::TryOutputType<C>>)
944+
constexpr C try_collect() noexcept;
945+
930946
/// This function acts like `fold()` but the closure returns a type that
931947
/// satisfies `sus::ops::Try` and which converts to the accumulator type on
932948
/// success through the Try concept. If the closure ever returns failure, the
@@ -1059,7 +1075,7 @@ class IteratorBase {
10591075
/// sus::move(iter).collect<MyContainer<i32>>()
10601076
/// ```
10611077
template <FromIterator<ItemT> C>
1062-
constexpr FromIterator<ItemT> auto collect() && noexcept;
1078+
constexpr C collect() && noexcept;
10631079

10641080
/// Transforms an iterator into a Vec.
10651081
///
@@ -1859,14 +1875,13 @@ constexpr std::weak_ordering IteratorBase<Iter, Item>::weak_cmp_by(
18591875

18601876
template <class Iter, class Item>
18611877
template <FromIterator<Item> C>
1862-
constexpr FromIterator<Item> auto
1863-
IteratorBase<Iter, Item>::collect() && noexcept {
1878+
constexpr C IteratorBase<Iter, Item>::collect() && noexcept {
18641879
return from_iter<C>(static_cast<Iter&&>(*this));
18651880
}
18661881

18671882
template <class Iter, class Item>
1868-
::sus::containers::Vec<Item> constexpr IteratorBase<
1869-
Iter, Item>::collect_vec() && noexcept {
1883+
constexpr ::sus::containers::Vec<Item>
1884+
IteratorBase<Iter, Item>::collect_vec() && noexcept {
18701885
return from_iter<::sus::containers::Vec<Item>>(static_cast<Iter&&>(*this));
18711886
}
18721887

sus/iter/iterator_impl.h

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// IWYU pragma: private, include "sus/iter/iterator.h"
16+
// IWYU pragma: friend "sus/.*"
17+
#pragma once
18+
19+
#include <type_traits>
20+
21+
#include "sus/iter/try_from_iterator.h"
22+
23+
namespace sus::iter {
24+
25+
// Implementation of IteratorBase::try_collect has to live here to avoid
26+
// cyclical includes, as the impl of try_from_iter() needs to see IteratorBase
27+
// in order to make something that can be consumed by FromIterator.
28+
template <class Iter, class Item>
29+
template <class C>
30+
requires(
31+
::sus::ops::Try<C> && //
32+
::sus::ops::Try<Item> &&
33+
// On failure the iterated failure is returned so it needs to be possible.
34+
::sus::ops::TryErrorConvertibleTo<Item, C> &&
35+
FromIterator<::sus::ops::TryOutputType<C>,
36+
::sus::ops::TryOutputType<Item>> && //
37+
// Void can not be collected from or into.
38+
!std::is_void_v<::sus::ops::TryOutputType<C>> && //
39+
!std::is_void_v<::sus::ops::TryOutputType<Item>> &&
40+
// We construct the output types of C, so it must not be holding
41+
// a reference type.
42+
!std::is_reference_v<::sus::ops::TryOutputType<C>>)
43+
constexpr C IteratorBase<Iter, Item>::try_collect() noexcept {
44+
return try_from_iter<C>(static_cast<Iter&&>(*this));
45+
}
46+
47+
} // namespace sus::iter

sus/iter/iterator_unittest.cc

+81
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@
3333
#include "sus/num/overflow_integer.h"
3434
#include "sus/ops/eq.h"
3535
#include "sus/prelude.h"
36+
#include "sus/test/no_copy_move.h"
3637

3738
using sus::containers::Array;
3839
using sus::iter::IteratorBase;
3940
using sus::option::Option;
41+
using sus::result::Result;
42+
using sus::test::NoCopyMove;
4043

4144
namespace sus::test::iter {
4245

@@ -45,6 +48,10 @@ struct CollectSum {
4548
T sum;
4649
};
4750

51+
struct CollectRefs {
52+
sus::Vec<const NoCopyMove*> vec;
53+
};
54+
4855
} // namespace sus::test::iter
4956

5057
template <class T>
@@ -57,6 +64,16 @@ struct sus::iter::FromIteratorImpl<sus::test::iter::CollectSum<T>> {
5764
}
5865
};
5966

67+
template <>
68+
struct sus::iter::FromIteratorImpl<sus::test::iter::CollectRefs> {
69+
static sus::test::iter::CollectRefs from_iter(
70+
sus::iter::IntoIterator<const NoCopyMove&> auto iter) noexcept {
71+
auto v = sus::Vec<const NoCopyMove*>();
72+
for (const NoCopyMove& t : sus::move(iter).into_iter()) v.push(&t);
73+
return sus::test::iter::CollectRefs(sus::move(v));
74+
}
75+
};
76+
6077
namespace {
6178
using namespace sus::test::iter;
6279

@@ -460,6 +477,10 @@ TEST(Iterator, Collect) {
460477
ArrayIterator<i32, 5>::with_array(nums).collect<CollectSum<i32>>();
461478
EXPECT_EQ(collected.sum, 1 + 2 + 3 + 4 + 5);
462479

480+
auto from = sus::iter::from_iter<CollectSum<i32>>(
481+
ArrayIterator<i32, 5>::with_array(nums));
482+
EXPECT_EQ(from.sum, 1 + 2 + 3 + 4 + 5);
483+
463484
static_assert(sus::Array<i32, 5>::with(1, 2, 3, 4, 5)
464485
.into_iter()
465486
.collect<CollectSum<i32>>()
@@ -481,6 +502,66 @@ TEST(Iterator, CollectVec) {
481502
sus::Vec<i32>::with(1, 2, 3, 4, 5));
482503
}
483504

505+
TEST(Iterator, TryCollect) {
506+
// Option.
507+
{
508+
auto collected = sus::Array<Option<i32>, 3>::with(
509+
::sus::some(1), ::sus::some(2), ::sus::some(3))
510+
.into_iter()
511+
.try_collect<Option<Vec<i32>>>();
512+
EXPECT_EQ(collected.is_some(), true);
513+
EXPECT_EQ(collected.as_value(), sus::Vec<i32>::with(1, 2, 3));
514+
515+
auto it = sus::Array<Option<i32>, 3>::with(::sus::some(1), ::sus::none(),
516+
::sus::some(3))
517+
.into_iter();
518+
auto up_to_none = it.try_collect<Option<Vec<i32>>>();
519+
EXPECT_EQ(up_to_none, sus::none());
520+
auto after_none = it.try_collect<Option<Vec<i32>>>();
521+
EXPECT_EQ(after_none.as_value(), sus::Vec<i32>::with(3));
522+
523+
NoCopyMove n[3];
524+
auto refs = sus::Array<Option<const NoCopyMove&>, 3>::with(
525+
::sus::some(n[0]), ::sus::some(n[1]), ::sus::some(n[2]))
526+
.into_iter()
527+
.try_collect<Option<CollectRefs>>();
528+
EXPECT_EQ(refs.is_some(), true);
529+
EXPECT_EQ(refs.as_value().vec[0u], &n[0]);
530+
EXPECT_EQ(refs.as_value().vec[1u], &n[1]);
531+
EXPECT_EQ(refs.as_value().vec[2u], &n[2]);
532+
}
533+
// Result.
534+
enum Error { ERROR };
535+
{
536+
auto collected = sus::Array<Result<i32, Error>, 3>::with(
537+
::sus::ok(1), ::sus::ok(2), ::sus::ok(3))
538+
.into_iter()
539+
.try_collect<Result<Vec<i32>, Error>>();
540+
EXPECT_EQ(collected.as_ok(), sus::Vec<i32>::with(1, 2, 3));
541+
542+
auto it = sus::Array<Result<i32, Error>, 3>::with(
543+
::sus::ok(1), ::sus::err(ERROR), ::sus::ok(3))
544+
.into_iter();
545+
auto up_to_err = it.try_collect<Result<Vec<i32>, Error>>();
546+
EXPECT_EQ(up_to_err, sus::err(ERROR));
547+
auto after_err = it.try_collect<Result<Vec<i32>, Error>>();
548+
EXPECT_EQ(after_err.as_ok(), sus::Vec<i32>::with(3));
549+
}
550+
551+
auto from = sus::iter::try_from_iter<Option<Vec<i32>>>(
552+
sus::Array<Option<i32>, 3>::with(::sus::some(1), ::sus::some(2),
553+
::sus::some(3)));
554+
EXPECT_EQ(from.as_value(), sus::Vec<i32>::with(1, 2, 3));
555+
556+
static_assert(
557+
sus::Array<Option<i32>, 3>::with(sus::some(1), sus::some(2), sus::some(3))
558+
.into_iter()
559+
.try_collect<Option<Vec<i32>>>()
560+
.unwrap()
561+
.into_iter()
562+
.sum() == 1 + 2 + 3);
563+
}
564+
484565
TEST(Iterator, Rev) {
485566
i32 nums[5] = {1, 2, 3, 4, 5};
486567

sus/iter/try_from_iterator.h

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <concepts>
18+
19+
#include "sus/iter/from_iterator.h"
20+
#include "sus/iter/into_iterator.h"
21+
#include "sus/iter/iterator_defn.h"
22+
#include "sus/mem/move.h"
23+
#include "sus/ops/try.h"
24+
#include "sus/option/option.h"
25+
26+
namespace sus::iter {
27+
28+
namespace __private {
29+
30+
template <class SourceIter>
31+
struct TryFromIteratorUnwrapper final
32+
: public IteratorBase<
33+
TryFromIteratorUnwrapper<SourceIter>,
34+
::sus::ops::TryOutputType<typename SourceIter::Item>> {
35+
using FromItem = typename SourceIter::Item;
36+
using Item = ::sus::ops::TryOutputType<FromItem>;
37+
38+
constexpr TryFromIteratorUnwrapper(SourceIter& iter,
39+
::sus::Option<FromItem>& failure)
40+
: iter(iter), failure(failure) {}
41+
42+
constexpr ::sus::Option<Item> next() noexcept {
43+
::sus::option::Option<FromItem> input = iter.next();
44+
if (input.is_some()) {
45+
if (::sus::ops::try_is_success(input.as_value())) {
46+
return ::sus::move(input).map(&::sus::ops::try_into_output<FromItem>);
47+
} else {
48+
failure = ::sus::move(input);
49+
}
50+
}
51+
return ::sus::Option<Item>();
52+
}
53+
54+
constexpr SizeHint size_hint() const noexcept {
55+
return SizeHint(0u, iter.size_hint().upper);
56+
}
57+
58+
SourceIter& iter;
59+
::sus::Option<FromItem>& failure;
60+
};
61+
62+
} // namespace __private
63+
64+
/// Constructs `ToType` from a type that can be turned into an `Iterator` over
65+
/// elements of type `ItemType`.
66+
///
67+
/// If a failure value is seen in the iterator, then the failure value will be
68+
/// returned. Otherwise, the `ToType` success type (`TryOutputType<ToType>`) is
69+
/// constructed from the success values in the Iterator, and a
70+
/// success-representing `ToType` is returned containing that success type.
71+
///
72+
/// This is the other end of
73+
/// [`Iterator::try_collect()`](::sus::iter::IteratorBase::try_collect), and is
74+
/// typically called through calling `try_collect()` on an iterator. However
75+
/// this function can be preferrable for some readers, especially in generic
76+
/// template code.
77+
template <class ToType, IntoIteratorAny IntoIter, int&...,
78+
class FromType = typename IntoIteratorOutputType<IntoIter>::Item>
79+
requires(
80+
::sus::mem::IsMoveRef<IntoIter &&> && //
81+
::sus::ops::Try<ToType> && //
82+
::sus::ops::Try<FromType> &&
83+
// On failure the iterated failure is returned so it needs to be possible.
84+
::sus::ops::TryErrorConvertibleTo<FromType, ToType> &&
85+
// The success type from the iterator can be collected into the
86+
// success type of the output.
87+
FromIterator<::sus::ops::TryOutputType<ToType>,
88+
::sus::ops::TryOutputType<FromType>> &&
89+
// Void can not be collected from or into.
90+
!std::is_void_v<::sus::ops::TryOutputType<ToType>> && //
91+
!std::is_void_v<::sus::ops::TryOutputType<FromType>> && //
92+
// We construct the output types of ToType, so it must not be holding a
93+
// reference type.
94+
!std::is_reference_v<::sus::ops::TryOutputType<ToType>>)
95+
constexpr inline ToType try_from_iter(IntoIter&& into_iter) noexcept {
96+
auto&& iter = ::sus::move(into_iter).into_iter();
97+
using SourceIter = std::remove_reference_t<decltype(iter)>;
98+
99+
::sus::Option<typename SourceIter::Item> failure;
100+
auto out = ::sus::ops::try_from_output<ToType>( //
101+
from_iter<::sus::ops::TryOutputType<ToType>>(
102+
__private::TryFromIteratorUnwrapper<SourceIter>(iter, failure)));
103+
if (failure.is_some())
104+
out = ::sus::ops::try_preserve_error<ToType>(::sus::move(failure).unwrap());
105+
return out;
106+
}
107+
108+
} // namespace sus::iter

0 commit comments

Comments
 (0)