Skip to content

Commit 6a2bc9b

Browse files
authored
Enable copy assignment operator for iterator class (#389)
* Add capabilities tests * Use `data_` pointer from `const_iterator` This re-enables the copy assignment operator that was implicitly deleted for the `iterator` class, and also cleans some things up since we don't need the duplicate `data_` reference * Include `<algorithm>` * NEWS bullets * Add iterator comments * Conditonally run capabilities tests It seems that the toolchain on 3.6 Windows did not have these implemented yet
1 parent 47f284c commit 6a2bc9b

File tree

3 files changed

+123
-13
lines changed

3 files changed

+123
-13
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# cpp11 (development version)
22

3+
* `cpp11::writable::r_vector<T>::iterator` no longer implicitly deletes its
4+
copy assignment operator (#360).
5+
6+
* `std::max_element()` can now be used with writable vectors (#334).
7+
38
* The `environment` class no longer uses the non-API function
49
`Rf_findVarInFrame3()` (#367).
510

cpp11test/src/test-r_vector.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,84 @@
44

55
#include <testthat.h>
66

7+
#include <algorithm> // for max_element
8+
9+
#ifdef _WIN32
10+
#include "Rversion.h"
11+
#define CPP11_HAS_IS_UTILITIES R_VERSION >= R_Version(4, 0, 0)
12+
#else
13+
#define CPP11_HAS_IS_UTILITIES 1
14+
#endif
15+
16+
#if CPP11_HAS_IS_UTILITIES
17+
context("r_vector-capabilities-C++") {
18+
test_that("read only vector capabilities") {
19+
using cpp11::integers;
20+
21+
expect_true(std::is_destructible<integers>::value);
22+
expect_true(std::is_default_constructible<integers>::value);
23+
expect_true(std::is_nothrow_default_constructible<integers>::value);
24+
expect_true(std::is_copy_constructible<integers>::value);
25+
expect_true(std::is_move_constructible<integers>::value);
26+
expect_true(std::is_copy_assignable<integers>::value);
27+
expect_true(std::is_move_assignable<integers>::value);
28+
}
29+
30+
test_that("writable vector capabilities") {
31+
using cpp11::writable::integers;
32+
33+
expect_true(std::is_destructible<integers>::value);
34+
expect_true(std::is_default_constructible<integers>::value);
35+
expect_true(std::is_nothrow_default_constructible<integers>::value);
36+
expect_true(std::is_copy_constructible<integers>::value);
37+
expect_true(std::is_move_constructible<integers>::value);
38+
expect_true(std::is_copy_assignable<integers>::value);
39+
expect_true(std::is_move_assignable<integers>::value);
40+
}
41+
42+
test_that("read only const_iterator capabilities") {
43+
using cpp11::integers;
44+
45+
expect_true(std::is_destructible<integers::const_iterator>::value);
46+
expect_true(std::is_trivially_destructible<integers::const_iterator>::value);
47+
expect_true(std::is_copy_constructible<integers::const_iterator>::value);
48+
expect_true(std::is_move_constructible<integers::const_iterator>::value);
49+
expect_true(std::is_copy_assignable<integers::const_iterator>::value);
50+
expect_true(std::is_trivially_copy_assignable<integers::const_iterator>::value);
51+
expect_true(std::is_move_assignable<integers::const_iterator>::value);
52+
expect_true(std::is_trivially_move_assignable<integers::const_iterator>::value);
53+
}
54+
55+
test_that("writable iterator capabilities") {
56+
using cpp11::writable::integers;
57+
58+
expect_true(std::is_destructible<integers::iterator>::value);
59+
expect_true(std::is_trivially_destructible<integers::iterator>::value);
60+
expect_true(std::is_copy_constructible<integers::iterator>::value);
61+
expect_true(std::is_move_constructible<integers::iterator>::value);
62+
expect_true(std::is_copy_assignable<integers::iterator>::value);
63+
expect_true(std::is_trivially_copy_assignable<integers::iterator>::value);
64+
expect_true(std::is_move_assignable<integers::iterator>::value);
65+
expect_true(std::is_trivially_move_assignable<integers::iterator>::value);
66+
}
67+
68+
test_that("writable proxy capabilities") {
69+
using cpp11::writable::integers;
70+
71+
expect_true(std::is_destructible<integers::proxy>::value);
72+
expect_true(std::is_trivially_destructible<integers::proxy>::value);
73+
expect_true(std::is_copy_constructible<integers::proxy>::value);
74+
expect_true(std::is_move_constructible<integers::proxy>::value);
75+
76+
// Should these be true? Does it affect anything in practice?
77+
expect_false(std::is_copy_assignable<integers::proxy>::value);
78+
expect_false(std::is_trivially_copy_assignable<integers::proxy>::value);
79+
expect_false(std::is_move_assignable<integers::proxy>::value);
80+
expect_false(std::is_trivially_move_assignable<integers::proxy>::value);
81+
}
82+
}
83+
#endif
84+
785
context("r_vector-C++") {
886
test_that("writable vector temporary isn't leaked (integer) (#338)") {
987
R_xlen_t before = cpp11::detail::store::count();
@@ -396,4 +474,26 @@ context("r_vector-C++") {
396474

397475
UNPROTECT(2);
398476
}
477+
478+
test_that("std::max_element works on read only vectors") {
479+
SEXP foo_sexp = PROTECT(Rf_allocVector(INTSXP, 5));
480+
SET_INTEGER_ELT(foo_sexp, 0, 1);
481+
SET_INTEGER_ELT(foo_sexp, 1, 2);
482+
SET_INTEGER_ELT(foo_sexp, 2, 5);
483+
SET_INTEGER_ELT(foo_sexp, 3, 4);
484+
SET_INTEGER_ELT(foo_sexp, 4, 3);
485+
cpp11::integers foo(foo_sexp);
486+
487+
auto element = std::max_element(foo.begin(), foo.end());
488+
489+
expect_true(*element == 5);
490+
491+
UNPROTECT(1);
492+
}
493+
494+
test_that("std::max_element works on writable vectors (#334)") {
495+
cpp11::writable::integers foo = {1, 2, 5, 4, 3};
496+
auto element = std::max_element(foo.begin(), foo.end());
497+
expect_true(*element == 5);
498+
}
399499
}

inst/include/cpp11/r_vector.hpp

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ class r_vector {
102102
const_iterator find(const r_string& name) const;
103103

104104
class const_iterator {
105+
// Iterator references:
106+
// https://cplusplus.com/reference/iterator/
107+
// https://stackoverflow.com/questions/8054273/how-to-implement-an-stl-style-iterator-and-avoid-common-pitfalls
108+
// It seems like our iterator doesn't fully implement everything for
109+
// `random_access_iterator_tag` (like an `[]` operator, for example). If we discover
110+
// issues with it, we probably need to add more methods.
105111
private:
106112
const r_vector* data_;
107113
R_xlen_t pos_;
@@ -282,8 +288,7 @@ class r_vector : public cpp11::r_vector<T> {
282288

283289
class iterator : public cpp11::r_vector<T>::const_iterator {
284290
private:
285-
const r_vector& data_;
286-
291+
using cpp11::r_vector<T>::const_iterator::data_;
287292
using cpp11::r_vector<T>::const_iterator::block_start_;
288293
using cpp11::r_vector<T>::const_iterator::pos_;
289294
using cpp11::r_vector<T>::const_iterator::buf_;
@@ -298,7 +303,7 @@ class r_vector : public cpp11::r_vector<T> {
298303
using reference = proxy&;
299304
using iterator_category = std::forward_iterator_tag;
300305

301-
iterator(const r_vector& data, R_xlen_t pos);
306+
iterator(const r_vector* data, R_xlen_t pos);
302307

303308
iterator& operator++();
304309

@@ -1120,12 +1125,12 @@ inline void r_vector<T>::clear() {
11201125

11211126
template <typename T>
11221127
inline typename r_vector<T>::iterator r_vector<T>::begin() const {
1123-
return iterator(*this, 0);
1128+
return iterator(this, 0);
11241129
}
11251130

11261131
template <typename T>
11271132
inline typename r_vector<T>::iterator r_vector<T>::end() const {
1128-
return iterator(*this, length_);
1133+
return iterator(this, length_);
11291134
}
11301135

11311136
template <typename T>
@@ -1238,35 +1243,35 @@ inline r_vector<T>::proxy::operator T() const {
12381243
}
12391244

12401245
template <typename T>
1241-
r_vector<T>::iterator::iterator(const r_vector& data, R_xlen_t pos)
1242-
: r_vector::const_iterator(&data, pos), data_(data) {}
1246+
r_vector<T>::iterator::iterator(const r_vector* data, R_xlen_t pos)
1247+
: r_vector::const_iterator(data, pos) {}
12431248

12441249
template <typename T>
12451250
inline typename r_vector<T>::iterator& r_vector<T>::iterator::operator++() {
12461251
++pos_;
1247-
if (use_buf(data_.is_altrep()) && pos_ >= block_start_ + length_) {
1252+
if (use_buf(data_->is_altrep()) && pos_ >= block_start_ + length_) {
12481253
fill_buf(pos_);
12491254
}
12501255
return *this;
12511256
}
12521257

12531258
template <typename T>
12541259
inline typename r_vector<T>::proxy r_vector<T>::iterator::operator*() const {
1255-
if (use_buf(data_.is_altrep())) {
1260+
if (use_buf(data_->is_altrep())) {
12561261
return proxy(
1257-
data_.data(), pos_,
1262+
data_->data(), pos_,
12581263
const_cast<typename r_vector::underlying_type*>(&buf_[pos_ - block_start_]),
12591264
true);
12601265
} else {
1261-
return proxy(data_.data(), pos_,
1262-
data_.data_p_ != nullptr ? &data_.data_p_[pos_] : nullptr, false);
1266+
return proxy(data_->data(), pos_,
1267+
data_->data_p_ != nullptr ? &data_->data_p_[pos_] : nullptr, false);
12631268
}
12641269
}
12651270

12661271
template <typename T>
12671272
inline typename r_vector<T>::iterator& r_vector<T>::iterator::operator+=(R_xlen_t rhs) {
12681273
pos_ += rhs;
1269-
if (use_buf(data_.is_altrep()) && pos_ >= block_start_ + length_) {
1274+
if (use_buf(data_->is_altrep()) && pos_ >= block_start_ + length_) {
12701275
fill_buf(pos_);
12711276
}
12721277
return *this;

0 commit comments

Comments
 (0)