Skip to content
Merged
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
21 changes: 15 additions & 6 deletions R/source.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
#' uses 'CXX11' if unset.
#' @param dir The directory to store the generated source files. `tempfile()` is
#' used by default. The directory will be removed if `clean` is `TRUE`.
#' @param local Passed to [dyn.load()]. If `TRUE` (the default) the shared
#' library is loaded with local symbols; if `FALSE` symbols are made global
#' (equivalent to `dyn.load(..., local = FALSE)`), which can be required when
#' other shared objects need to see RTTI/vtable symbols from this library.
#' @note See the unit test that demonstrates this usage at
#' \code{tests/testthat/test-source-local.R} (shows how `local = FALSE` exports
#' the necessary symbols so separate shared objects can link against them).
#' @return For [cpp_source()] and `[cpp_function()]` the results of
#' [dyn.load()] (invisibly). For `[cpp_eval()]` the results of the evaluated
#' expression.
Expand Down Expand Up @@ -65,7 +72,7 @@
#' }
#'
#' @export
cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11"), dir = tempfile()) {
cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11"), dir = tempfile(), local = TRUE) {
stop_unless_installed(c("brio", "callr", "cli", "decor", "desc", "glue", "tibble", "vctrs"))
if (!missing(file) && !file.exists(file)) {
stop("Can't find `file` at this path:\n", file, "\n", call. = FALSE)
Expand Down Expand Up @@ -145,7 +152,7 @@ cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, qu
brio::write_lines(r_functions, r_path)
source(r_path, local = env)

dyn.load(shared_lib, local = TRUE, now = TRUE)
dyn.load(shared_lib, local = local, now = TRUE)
}

the <- new.env(parent = emptyenv())
Expand Down Expand Up @@ -183,7 +190,7 @@ generate_makevars <- function(includes, cxx_std) {

#' @rdname cpp_source
#' @export
cpp_function <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11")) {
cpp_function <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11"), local = TRUE) {
cpp_source(code = paste(c('#include "cpp11.hpp"',
"using namespace ::cpp11;",
"namespace writable = ::cpp11::writable;",
Expand All @@ -193,15 +200,16 @@ cpp_function <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE,
env = env,
clean = clean,
quiet = quiet,
cxx_std = cxx_std
cxx_std = cxx_std,
local = local
)
}

utils::globalVariables("f")

#' @rdname cpp_source
#' @export
cpp_eval <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11")) {
cpp_eval <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11"), local = TRUE) {
cpp_source(code = paste(c('#include "cpp11.hpp"',
"using namespace ::cpp11;",
"namespace writable = ::cpp11::writable;",
Expand All @@ -214,7 +222,8 @@ cpp_eval <- function(code, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx
env = env,
clean = clean,
quiet = quiet,
cxx_std = cxx_std
cxx_std = cxx_std,
local = local
)
f()
}
Expand Down
69 changes: 0 additions & 69 deletions cpp11test/src/test-integers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,23 +138,6 @@ 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 @@ -295,56 +278,4 @@ 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);
}
}
60 changes: 26 additions & 34 deletions inst/include/cpp11/list_of.hpp
Original file line number Diff line number Diff line change
@@ -1,57 +1,51 @@
#pragma once

#include <string> // for string, basic_string
#include <string> // for string, basic_string

#include "cpp11/R.hpp" // for R_xlen_t, SEXP, SEXPREC, LONG_VECTOR_SUPPORT
#include "cpp11/list.hpp" // for list
#include "cpp11/R.hpp" // for R_xlen_t, SEXP, SEXPREC, LONG_VECTOR_SUPPORT
#include "cpp11/list.hpp" // for list

namespace cpp11 {

template <typename T> class list_of : public list {
public:
list_of(const list &data) : list(data) {}
template <typename T>
class list_of : public list {
public:
list_of(const list& data) : list(data) {}

#ifdef LONG_VECTOR_SUPPORT
T operator[](const int pos) const {
return operator[](static_cast<R_xlen_t>(pos));
}
T operator[](const int pos) const { return operator[](static_cast<R_xlen_t>(pos)); }
#endif

T operator[](const R_xlen_t pos) const { return list::operator[](pos); }

T operator[](const char *pos) const { return list::operator[](pos); }
T operator[](const char* pos) const { return list::operator[](pos); }

T operator[](const std::string &pos) const {
return list::operator[](pos.c_str());
}
T operator[](const std::string& pos) const { return list::operator[](pos.c_str()); }
};

namespace writable {
template <typename T> class list_of : public writable::list {
public:
list_of(const list &data) : writable::list(data) {}
template <typename T>
class list_of : public writable::list {
public:
list_of(const list& data) : writable::list(data) {}
list_of(R_xlen_t n) : writable::list(n) {}

class proxy {
private:
private:
writable::list::proxy data_;

public:
proxy(const writable::list::proxy &data) : data_(data) {}
public:
proxy(const writable::list::proxy& data) : data_(data) {}

operator T() const { return static_cast<SEXP>(*this); }
operator SEXP() const { return static_cast<SEXP>(data_); }
#ifdef LONG_VECTOR_SUPPORT
typename T::proxy operator[](int pos) { return static_cast<T>(data_)[pos]; }
#endif
typename T::proxy operator[](R_xlen_t pos) {
return static_cast<T>(data_)[pos];
}
proxy operator[](const char *pos) { static_cast<T>(data_)[pos]; }
proxy operator[](const std::string &pos) {
return static_cast<T>(data_)[pos];
}
proxy &operator=(const T &rhs) {
typename T::proxy operator[](R_xlen_t pos) { return static_cast<T>(data_)[pos]; }
proxy operator[](const char* pos) { static_cast<T>(data_)[pos]; }
proxy operator[](const std::string& pos) { return static_cast<T>(data_)[pos]; }
proxy& operator=(const T& rhs) {
data_ = rhs;

return *this;
Expand All @@ -60,20 +54,18 @@ template <typename T> class list_of : public writable::list {

#ifdef LONG_VECTOR_SUPPORT
proxy operator[](int pos) {
return { writable::list::operator[](static_cast<R_xlen_t>(pos)) };
return {writable::list::operator[](static_cast<R_xlen_t>(pos))};
}
#endif

proxy operator[](R_xlen_t pos) { return writable::list::operator[](pos); }

proxy operator[](const char *pos) {
return { writable::list::operator[](pos) };
}
proxy operator[](const char* pos) { return {writable::list::operator[](pos)}; }

proxy operator[](const std::string &pos) {
proxy operator[](const std::string& pos) {
return writable::list::operator[](pos.c_str());
}
};
} // namespace writable
} // namespace writable

} // namespace cpp11
} // namespace cpp11
Loading