Skip to content

Commit e080766

Browse files
bkietzfsaintjacques
authored andcommitted
ARROW-6847: [C++] Add range_expression adapter to Iterator
Allows `Iterator<T>` to be used in a range-for loop Closes apache#5621 from bkietz/6847-Add-a-range-expression-in and squashes the following commits: 927ae0f <Benjamin Kietzman> document equality comparability of Iterator (necessary for Iterator of Iterators) 5ad05d2 <Benjamin Kietzman> add ToStringOstreamable mixin for automatically defining operator<< def95f1 <Benjamin Kietzman> added EqualityComparable helper for defining equality comparison consistently e020e34 <Benjamin Kietzman> refactor to iterate over Result<> a9537ef <Benjamin Kietzman> add test for failing iterator fda9326 <Benjamin Kietzman> ARROW-6847: Add range_expression adapter to Iterator Authored-by: Benjamin Kietzman <bengilgit@gmail.com> Signed-off-by: François Saint-Jacques <fsaintjacques@gmail.com>
1 parent bbf1de0 commit e080766

8 files changed

Lines changed: 221 additions & 16 deletions

File tree

cpp/src/arrow/result.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <utility>
2323

2424
#include "arrow/status.h"
25+
#include "arrow/util/compare.h"
2526
#include "arrow/util/macros.h"
2627
#include "arrow/util/variant.h"
2728

@@ -86,7 +87,7 @@ ARROW_EXPORT void DieWithMessage(const std::string& msg);
8687
/// arrow::Result<int> CalculateFoo();
8788
/// ```
8889
template <class T>
89-
class Result {
90+
class Result : public util::EqualityComparable<Result<T>> {
9091
template <typename U>
9192
friend class Result;
9293
using VariantType = arrow::util::variant<T, Status, const char*>;
@@ -215,6 +216,9 @@ class Result {
215216
return *this;
216217
}
217218

219+
/// Compare to another Result.
220+
bool Equals(const Result& other) const { return variant_ == other.variant_; }
221+
218222
/// Indicates whether the object contains a `T` value. Generally instead
219223
/// of accessing this directly you will want to use ASSIGN_OR_RAISE defined
220224
/// below.

cpp/src/arrow/result_test.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,5 +585,23 @@ TEST(ResultTest, TemplateMoveConstruct) {
585585
// NOLINTNEXTLINE use after move.
586586
}
587587

588+
TEST(ResultTest, Equality) {
589+
EXPECT_EQ(Result<int>(), Result<int>());
590+
EXPECT_EQ(Result<int>(3), Result<int>(3));
591+
EXPECT_EQ(Result<int>(Status::Invalid("error")), Result<int>(Status::Invalid("error")));
592+
593+
EXPECT_NE(Result<int>(), Result<int>(3));
594+
EXPECT_NE(Result<int>(Status::Invalid("error")), Result<int>(3));
595+
EXPECT_NE(Result<int>(3333), Result<int>(0));
596+
EXPECT_NE(Result<int>(Status::Invalid("error")),
597+
Result<int>(Status::Invalid("other error")));
598+
599+
Result<int> moved_from(3);
600+
auto moved_to = std::move(moved_from);
601+
// NOLINTNEXTLINE use after move.
602+
EXPECT_NE(Result<int>(3), moved_from);
603+
// NOLINTNEXTLINE use after move.
604+
}
605+
588606
} // namespace
589607
} // namespace arrow

cpp/src/arrow/status.h

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <string>
2222
#include <utility>
2323

24+
#include "arrow/util/compare.h"
2425
#include "arrow/util/macros.h"
2526
#include "arrow/util/string_builder.h"
2627
#include "arrow/util/visibility.h"
@@ -110,6 +111,10 @@ class ARROW_EXPORT StatusDetail {
110111
virtual const char* type_id() const = 0;
111112
/// \brief Produce a human-readable description of this status.
112113
virtual std::string ToString() const = 0;
114+
115+
bool operator==(const StatusDetail& other) const noexcept {
116+
return std::string(type_id()) == other.type_id() && ToString() == other.ToString();
117+
}
113118
};
114119

115120
/// \brief Status outcome object (success or error)
@@ -120,7 +125,8 @@ class ARROW_EXPORT StatusDetail {
120125
///
121126
/// Additionally, if an error occurred, a specific error message is generally
122127
/// attached.
123-
class ARROW_EXPORT Status {
128+
class ARROW_EXPORT Status : public util::EqualityComparable<Status>,
129+
public util::ToStringOstreamable<Status> {
124130
public:
125131
// Create a success status.
126132
Status() noexcept : state_(NULLPTR) {}
@@ -144,6 +150,8 @@ class ARROW_EXPORT Status {
144150
inline Status(Status&& s) noexcept;
145151
inline Status& operator=(Status&& s) noexcept;
146152

153+
inline bool Equals(const Status& s) const;
154+
147155
// AND the statuses.
148156
inline Status operator&(const Status& s) const noexcept;
149157
inline Status operator&(Status&& s) const noexcept;
@@ -153,12 +161,6 @@ class ARROW_EXPORT Status {
153161
/// Return a success status
154162
static Status OK() { return Status(); }
155163

156-
/// Return a success status with a specific message
157-
template <typename... Args>
158-
static Status OK(Args&&... args) {
159-
return Status(StatusCode::OK, util::StringBuilder(std::forward<Args>(args)...));
160-
}
161-
162164
/// Return an error status for out-of-memory conditions
163165
template <typename... Args>
164166
static Status OutOfMemory(Args&&... args) {
@@ -347,11 +349,6 @@ class ARROW_EXPORT Status {
347349
inline void MoveFrom(Status& s);
348350
};
349351

350-
static inline std::ostream& operator<<(std::ostream& os, const Status& x) {
351-
os << x.ToString();
352-
return os;
353-
}
354-
355352
void Status::MoveFrom(Status& s) {
356353
delete state_;
357354
state_ = s.state_;
@@ -377,6 +374,22 @@ Status& Status::operator=(Status&& s) noexcept {
377374
return *this;
378375
}
379376

377+
bool Status::Equals(const Status& s) const {
378+
if (state_ == s.state_) {
379+
return true;
380+
}
381+
382+
if (ok() || s.ok()) {
383+
return false;
384+
}
385+
386+
if (detail() != s.detail() && !(*detail() == *s.detail())) {
387+
return false;
388+
}
389+
390+
return code() == s.code() && message() == s.message();
391+
}
392+
380393
/// \cond FALSE
381394
// (note: emits warnings on Doxygen < 1.8.15,
382395
// see https://github.com/doxygen/doxygen/issues/6295)

cpp/src/arrow/status_test.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,12 @@ TEST(StatusTest, AndStatus) {
106106
ASSERT_TRUE(res.IsInvalid());
107107
}
108108

109+
TEST(StatusTest, TestEquality) {
110+
ASSERT_EQ(Status(), Status::OK());
111+
ASSERT_EQ(Status::Invalid("error"), Status::Invalid("error"));
112+
113+
ASSERT_NE(Status::Invalid("error"), Status::OK());
114+
ASSERT_NE(Status::Invalid("error"), Status::Invalid("other error"));
115+
}
116+
109117
} // namespace arrow

cpp/src/arrow/util/compare.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include <memory>
21+
#include <type_traits>
22+
#include <utility>
23+
24+
#include "arrow/util/macros.h"
25+
26+
namespace arrow {
27+
namespace util {
28+
29+
/// CRTP helper for declaring equality comparison. Defines operator== and operator!=
30+
template <typename T>
31+
class EqualityComparable {
32+
public:
33+
~EqualityComparable() {
34+
static_assert(
35+
std::is_same<decltype(std::declval<const T>().Equals(std::declval<const T>())),
36+
bool>::value,
37+
"EqualityComparable depends on the method T::Equals(const T&) const");
38+
}
39+
40+
template <typename... Extra>
41+
bool Equals(const std::shared_ptr<T>& other, Extra&&... extra) const {
42+
if (other == NULLPTR) {
43+
return false;
44+
}
45+
return cast().Equals(*other, std::forward<Extra>(extra)...);
46+
}
47+
48+
bool operator==(const T& other) const { return cast().Equals(other); }
49+
50+
bool operator!=(const T& other) const { return !(cast() == other); }
51+
52+
private:
53+
const T& cast() const { return static_cast<const T&>(*this); }
54+
};
55+
56+
} // namespace util
57+
} // namespace arrow

cpp/src/arrow/util/iterator.h

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
#include <utility>
2525
#include <vector>
2626

27+
#include "arrow/result.h"
2728
#include "arrow/status.h"
29+
#include "arrow/util/compare.h"
2830
#include "arrow/util/functional.h"
2931
#include "arrow/util/macros.h"
3032
#include "arrow/util/visibility.h"
@@ -44,7 +46,7 @@ struct IterationTraits {
4446

4547
/// \brief A generic Iterator that can return errors
4648
template <typename T>
47-
class Iterator {
49+
class Iterator : public util::EqualityComparable<Iterator<T>> {
4850
public:
4951
/// \brief Iterator may be constructed from any type which has a member function
5052
/// with signature Status Next(T*);
@@ -87,11 +89,54 @@ class Iterator {
8789
return status;
8890
}
8991

90-
/// Iterators will only compare equal if they are both null
91-
bool operator==(const Iterator& other) const { return ptr_ == other.ptr_; }
92+
/// Iterators will only compare equal if they are both null.
93+
/// Equality comparability is required to make an Iterator of Iterators
94+
/// (to check for the end condition).
95+
bool Equals(const Iterator& other) const { return ptr_ == other.ptr_; }
9296

9397
explicit operator bool() const { return ptr_ != NULLPTR; }
9498

99+
class RangeIterator {
100+
public:
101+
RangeIterator() = default;
102+
103+
explicit RangeIterator(Iterator i) : iterator_(std::move(i)) { Next(); }
104+
105+
bool operator!=(const RangeIterator& other) const {
106+
return !(status_ == other.status_ && value_ == other.value_);
107+
}
108+
109+
RangeIterator& operator++() {
110+
Next();
111+
return *this;
112+
}
113+
114+
Result<T> operator*() {
115+
if (status_.ok()) {
116+
return std::move(value_);
117+
}
118+
return status_;
119+
}
120+
121+
private:
122+
void Next() {
123+
if (!status_.ok()) {
124+
status_ = Status::OK();
125+
value_ = IterationTraits<T>::End();
126+
return;
127+
}
128+
status_ = iterator_.Next(&value_);
129+
}
130+
131+
T value_ = IterationTraits<T>::End();
132+
Iterator iterator_;
133+
Status status_;
134+
};
135+
136+
RangeIterator begin() { return RangeIterator(std::move(*this)); }
137+
138+
RangeIterator end() { return RangeIterator(); }
139+
95140
private:
96141
/// Implementation of deleter for ptr_: Casts from void* to the wrapped type and
97142
/// deletes that.

cpp/src/arrow/util/iterator_test.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,48 @@ TEST(TestVectorIterator, Basic) {
177177
AssertIteratorNoMatch({1, 2, 3, 1}, VectorIt({1, 2, 3}));
178178
}
179179

180+
TEST(TestVectorIterator, RangeForLoop) {
181+
std::vector<TestInt> ints = {1, 2, 3, 4};
182+
183+
auto ints_it = ints.begin();
184+
for (auto v_result : VectorIt(ints)) {
185+
ASSERT_OK_AND_ASSIGN(TestInt v, v_result);
186+
ASSERT_EQ(v, *ints_it++);
187+
}
188+
ASSERT_EQ(ints_it, ints.end());
189+
190+
std::vector<std::unique_ptr<TestInt>> vec;
191+
for (TestInt i : ints) {
192+
vec.emplace_back(new TestInt(i));
193+
}
194+
195+
// also works with move only types
196+
ints_it = ints.begin();
197+
for (auto v_result : MakeVectorIterator(std::move(vec))) {
198+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<TestInt> v, std::move(v_result));
199+
ASSERT_EQ(*v, *ints_it++);
200+
}
201+
ASSERT_EQ(ints_it, ints.end());
202+
}
203+
204+
TEST(TestFunctionIterator, RangeForLoop) {
205+
int i = 0;
206+
auto fails_at_3 = MakeFunctionIterator([&](TestInt* out) {
207+
if (i >= 3) {
208+
return Status::IndexError("fails at 3");
209+
}
210+
out->value = i++;
211+
return Status::OK();
212+
});
213+
214+
std::vector<Result<TestInt>> actual,
215+
expected{0, 1, 2, Status::IndexError("fails at 3")};
216+
for (auto r : fails_at_3) {
217+
actual.push_back(std::move(r));
218+
}
219+
ASSERT_EQ(actual, expected);
220+
}
221+
180222
TEST(FilterIterator, Basic) {
181223
AssertIteratorMatch({1, 2, 3, 4}, FilterIt(VectorIt({1, 2, 3, 4}),
182224
[](TestInt i, TestInt* o, bool* accept) {

cpp/src/arrow/util/string_builder.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ std::string StringBuilder(Args&&... args) {
6363
return ss.str();
6464
}
6565

66+
/// CRTP helper for declaring string representation. Defines operator<<
67+
template <typename T>
68+
class ToStringOstreamable {
69+
public:
70+
~ToStringOstreamable() {
71+
static_assert(
72+
std::is_same<decltype(std::declval<const T>().ToString()), std::string>::value,
73+
"ToStringOstreamable depends on the method T::ToString() const");
74+
}
75+
76+
private:
77+
const T& cast() const { return static_cast<const T&>(*this); }
78+
79+
friend inline std::ostream& operator<<(std::ostream& os, const ToStringOstreamable& t) {
80+
return os << t.cast().ToString();
81+
}
82+
};
83+
6684
} // namespace util
6785
} // namespace arrow
6886

0 commit comments

Comments
 (0)