Skip to content

Commit 3c87798

Browse files
Fix missing value propagation in as_integers/doubles() (#265)
* Correctly propagate missing values in `as_integers()` Also: - Switch to `R_xlen_t` over `size_t` since this is what `size()` returns and is what our constructors prefer - Initialize return value with `ret(len)` since that seems cleaner - Forward declare required bits from `doubles.hpp`. Use of `is_na(<dbl>)` is new, so it makes sense that we need to forward declare it. Even before this PR, we used the `[]` double operator, so you might be wondering why we need the forward declaration. Well, it only previously worked by luck because we had a hidden dependency on `cpp11/doubles.hpp` in `test-integers.cpp` by including `cpp11/doubles.hpp` before `cpp11/integers.hpp`. I swapped them around in the test file and it indeed failed without this forward declaration. * Correctly propagate missing values in `as_doubles()` Also: - Switch to `R_xlen_t` over `size_t` since this is what `size()` returns and is what our constructors prefer - Initialize sized return value, rather than using `push_back()`, which should be faster - Forward declare required bits from `integers.hpp`. There is no `is_na(<int>)` specialization to forward declare, but there is a `na<int>()` forward declaration that is required, because that is used by the default `is_na()` implementation - Move double specializations of `na()` and `is_na()` before the implementation of `as_doubles()`. Since `as_doubles()` now calls the `na<double>()` specialization, it has to be implemented (or forward declared) ahead of time. * NEWS bullet * Guess at how to make the clang formatter happy * Separate `cpp11/integers.hpp` into its own include "block" * Typo, `convertable` -> `convertible` --------- Co-authored-by: Romain François <romain@rstudio.com>
1 parent 971164d commit 3c87798

File tree

6 files changed

+60
-27
lines changed

6 files changed

+60
-27
lines changed

NEWS.md

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

3+
* `as_doubles()` and `as_integers()` now propagate missing values correctly
4+
(#265).
5+
36
* Fixed a performance issue related to nested `unwind_protect()` calls (#298).
47

58
* Minor performance improvements to the cpp11 protect code. (@kevinushey)

cpp11test/src/test-doubles.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,11 @@ context("doubles-C++") {
393393
e.push_back("a");
394394
e.push_back("b");
395395
expect_error(cpp11::as_doubles(e));
396+
397+
cpp11::writable::integers na;
398+
na.push_back(cpp11::na<int>());
399+
cpp11::doubles na2(cpp11::as_doubles(na));
400+
expect_true(cpp11::is_na(na2[0]));
396401
}
397402

398403
test_that("doubles operator[] and at") {

cpp11test/src/test-integers.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include "Rversion.h"
2-
#include "cpp11/doubles.hpp"
2+
33
#include "cpp11/function.hpp"
44
#include "cpp11/integers.hpp"
5+
6+
#include "cpp11/doubles.hpp"
57
#include "cpp11/strings.hpp"
68

79
#include <testthat.h>
@@ -36,6 +38,11 @@ context("integers-C++") {
3638
expect_true(t[2] == 100000);
3739
expect_true(t[3] == 100000);
3840
expect_true(TYPEOF(t) == INTSXP);
41+
42+
cpp11::writable::doubles na;
43+
na.push_back(cpp11::na<double>());
44+
cpp11::integers na2(cpp11::as_integers(na));
45+
expect_true(cpp11::is_na(na2[0]));
3946
}
4047

4148
test_that("integers.push_back()") {

inst/include/cpp11/as.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ using enable_if_c_string = enable_if_t<std::is_same<T, const char*>::value, R>;
7373

7474
// https://stackoverflow.com/a/1521682/2055486
7575
//
76-
inline bool is_convertable_without_loss_to_integer(double value) {
76+
inline bool is_convertible_without_loss_to_integer(double value) {
7777
double int_part;
7878
return std::modf(value, &int_part) == 0.0;
7979
}
@@ -100,7 +100,7 @@ enable_if_integral<T, T> as_cpp(SEXP from) {
100100
return NA_INTEGER;
101101
}
102102
double value = REAL_ELT(from, 0);
103-
if (is_convertable_without_loss_to_integer(value)) {
103+
if (is_convertible_without_loss_to_integer(value)) {
104104
return value;
105105
}
106106
}

inst/include/cpp11/doubles.hpp

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,33 +135,44 @@ typedef r_vector<double> doubles;
135135

136136
} // namespace writable
137137

138+
template <>
139+
inline double na() {
140+
return NA_REAL;
141+
}
142+
143+
template <>
144+
inline bool is_na(const double& x) {
145+
return ISNA(x);
146+
}
147+
148+
// forward declarations
138149
typedef r_vector<int> integers;
139150

151+
template <>
152+
int na();
153+
154+
template <>
155+
int r_vector<int>::operator[](const R_xlen_t pos) const;
156+
140157
inline doubles as_doubles(sexp x) {
141158
if (TYPEOF(x) == REALSXP) {
142159
return as_cpp<doubles>(x);
143-
}
144-
145-
else if (TYPEOF(x) == INTSXP) {
160+
} else if (TYPEOF(x) == INTSXP) {
146161
integers xn = as_cpp<integers>(x);
147-
size_t len = xn.size();
148-
writable::doubles ret;
149-
for (size_t i = 0; i < len; ++i) {
150-
ret.push_back(static_cast<double>(xn[i]));
162+
R_xlen_t len = xn.size();
163+
writable::doubles ret(len);
164+
for (R_xlen_t i = 0; i < len; ++i) {
165+
int el = xn[i];
166+
if (is_na(el)) {
167+
ret[i] = na<double>();
168+
} else {
169+
ret[i] = static_cast<double>(el);
170+
}
151171
}
152172
return ret;
153173
}
154174

155175
throw type_error(REALSXP, TYPEOF(x));
156176
}
157177

158-
template <>
159-
inline double na() {
160-
return NA_REAL;
161-
}
162-
163-
template <>
164-
inline bool is_na(const double& x) {
165-
return ISNA(x);
166-
}
167178
} // namespace cpp11

inst/include/cpp11/integers.hpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,25 +145,32 @@ inline int na() {
145145
return NA_INTEGER;
146146
}
147147

148-
// forward declaration
149-
148+
// forward declarations
150149
typedef r_vector<double> doubles;
151150

151+
template <>
152+
bool is_na(const double& x);
153+
154+
template <>
155+
double r_vector<double>::operator[](const R_xlen_t pos) const;
156+
152157
inline integers as_integers(sexp x) {
153158
if (TYPEOF(x) == INTSXP) {
154159
return as_cpp<integers>(x);
155160
} else if (TYPEOF(x) == REALSXP) {
156161
doubles xn = as_cpp<doubles>(x);
157-
size_t len = (xn.size());
158-
writable::integers ret = writable::integers(len);
159-
for (size_t i = 0; i < len; ++i) {
162+
R_xlen_t len = xn.size();
163+
writable::integers ret(len);
164+
for (R_xlen_t i = 0; i < len; ++i) {
160165
double el = xn[i];
161-
if (!is_convertable_without_loss_to_integer(el)) {
166+
if (is_na(el)) {
167+
ret[i] = na<int>();
168+
} else if (is_convertible_without_loss_to_integer(el)) {
169+
ret[i] = static_cast<int>(el);
170+
} else {
162171
throw std::runtime_error("All elements must be integer-like");
163172
}
164-
ret[i] = (static_cast<int>(el));
165173
}
166-
167174
return ret;
168175
}
169176

0 commit comments

Comments
 (0)