Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ Please note that the cpp11 project is released with a [Contributor Code of Condu

cpp11 would not exist without Rcpp.
Thanks to the Rcpp authors, Dirk Eddelbuettel, Romain Francois, JJ Allaire, Kevin Ushey, Qiang Kou, Nathan Russell, Douglas Bates and John Chambers for their work writing and maintaining Rcpp.

## Clang format

To match GHA, use clang-format-12 to format C++ code. With systems that provide clang-format-14 or newer, you can use Docker:

```bash
docker run --rm -v "$PWD":/work -w /work ubuntu:22.04 bash -lc "\
apt-get update && apt-get install -y clang-format-12 && \
find . -name '*.cpp' -o -name '*.hpp' -o -name '*.h' | xargs -r clang-format-12 -i"
```
69 changes: 69 additions & 0 deletions cpp11test/src/test-integers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ context("integers-C++") {
expect_true(x[1] == 3);
expect_true(x[2] == 4);
}
test_that("integers.value()") {
cpp11::writable::integers x;
x.push_back(1);
x.push_back(2);
x.push_back(3);

// Test that value() returns the same as operator[] but as T directly
expect_true(x.value(0) == 1);
expect_true(x.value(1) == 2);
expect_true(x.value(2) == 3);

// Test that value() works with C-style formatting (this was the original issue in
// #453)
expect_true(x.value(0) == x[0]);
expect_true(x.value(1) == x[1]);
expect_true(x.value(2) == x[2]);
}

test_that("writable::integers(SEXP)") {
SEXP x = PROTECT(Rf_allocVector(INTSXP, 5));
Expand Down Expand Up @@ -278,4 +295,56 @@ context("integers-C++") {
int y = NA_INTEGER;
expect_true(cpp11::is_na(y));
}

test_that("proxy issue demonstration") {
// This test demonstrates the proxy issue and shows that all solutions work
cpp11::writable::integers x;
for (int i = 0; i < 3; i++) {
x.push_back(i * 10);
}

// Test that value() method works correctly
expect_true(x.value(0) == 0);
expect_true(x.value(1) == 10);
expect_true(x.value(2) == 20);

// Test that explicit cast works
expect_true((int)x[0] == 0);
expect_true((int)x[1] == 10);
expect_true((int)x[2] == 20);

// Test that auto assignment works (triggers implicit conversion)
int val0 = x[0];
int val1 = x[1];
int val2 = x[2];
expect_true(val0 == 0);
expect_true(val1 == 10);
expect_true(val2 == 20);

// Test that value() and operator[] return equivalent results
expect_true(x.value(0) == (int)x[0]);
expect_true(x.value(1) == (int)x[1]);
expect_true(x.value(2) == (int)x[2]);
}
}

// [[cpp11::register]]
// Demo function to show the three ways to handle the proxy issue
// To use this function:
// 1. Run cpp11::cpp_register() to regenerate R bindings
// 2. Rebuild and reinstall the package
// 3. Call test_proxy_issue_demo() from R
void test_proxy_issue_demo() {
cpp11::writable::integers x;
for (int i = 0; i < 5; i++) {
x.push_back(i);

// These all work correctly:
Rprintf("Method 1 - cast: x[%d] = %d\n", i, (int)x[i]);
Rprintf("Method 2 - value(): x[%d] = %d\n", i, x.value(i));

// This also works (auto triggers implicit conversion):
int val = x[i];
Rprintf("Method 3 - auto: x[%d] = %d\n", i, val);
}
}
12 changes: 4 additions & 8 deletions inst/include/cpp11/as.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,14 @@ template <typename Container, typename AsCstring>
SEXP as_sexp_strings(const Container& from, AsCstring&& c_str) {
R_xlen_t size = from.size();

SEXP data;
try {
data = PROTECT(safe[Rf_allocVector](STRSXP, size));
SEXP data = PROTECT(safe[Rf_allocVector](STRSXP, size));

unwind_protect([&] {
auto it = from.begin();
for (R_xlen_t i = 0; i < size; ++i, ++it) {
SET_STRING_ELT(data, i, safe[Rf_mkCharCE](c_str(*it), CE_UTF8));
SET_STRING_ELT(data, i, Rf_mkCharCE(c_str(*it), CE_UTF8));
}
} catch (const unwind_exception& e) {
UNPROTECT(1);
throw e;
}
});

UNPROTECT(1);
return data;
Expand Down
23 changes: 23 additions & 0 deletions inst/include/cpp11/integers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,26 @@ inline integers as_integers(SEXP x) {
}

} // namespace cpp11

// Note: Proxy Behavior in writable::integers
//
// When using writable::integers, operator[] returns a proxy object that allows
// both reading and writing. For cases where you need the actual int value
// (e.g., when using with C-style variadic functions like Rprintf), use one of
// these three approaches:
//
// 1. Direct value access: vec.value(i) [Recommended]
// 2. Explicit cast: (int)vec[i]
// 3. Auto with explicit type: int val = vec[i];
//
// Example demonstrating the issue and solutions:
// writable::integers vec;
// vec.push_back(42);
//
// // This may print garbage due to proxy object:
// // Rprintf("Value: %d\n", vec[0]); // DON'T DO THIS
//
// // These all work correctly:
// Rprintf("Value: %d\n", vec.value(0)); // Recommended
// Rprintf("Value: %d\n", (int)vec[0]); // Also works
// int val = vec[0]; Rprintf("Value: %d\n", val); // Also works
27 changes: 27 additions & 0 deletions inst/include/cpp11/r_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ class r_vector : public cpp11::r_vector<T> {

iterator find(const r_string& name) const;

/// Get the value at position without returning a proxy
/// This is useful when you need the actual value (e.g., for C-style printf functions)
/// that don't trigger implicit conversions from proxy types
#ifdef LONG_VECTOR_SUPPORT
T value(const int pos) const;
#endif
T value(const R_xlen_t pos) const;
T value(const size_type pos) const;

attribute_proxy<r_vector<T>> attr(const char* name) const;
attribute_proxy<r_vector<T>> attr(const std::string& name) const;
attribute_proxy<r_vector<T>> attr(SEXP name) const;
Expand Down Expand Up @@ -1156,6 +1165,24 @@ inline typename r_vector<T>::iterator r_vector<T>::find(const r_string& name) co
return end();
}

#ifdef LONG_VECTOR_SUPPORT
template <typename T>
inline T r_vector<T>::value(const int pos) const {
return value(static_cast<R_xlen_t>(pos));
}
#endif

template <typename T>
inline T r_vector<T>::value(const R_xlen_t pos) const {
// Use the parent read-only class's operator[] which returns T directly
return cpp11::r_vector<T>::operator[](pos);
}

template <typename T>
inline T r_vector<T>::value(const size_type pos) const {
return value(static_cast<R_xlen_t>(pos));
}

template <typename T>
inline attribute_proxy<r_vector<T>> r_vector<T>::attr(const char* name) const {
return attribute_proxy<r_vector<T>>(*this, name);
Expand Down
Loading
Loading