Cpp11 pull requests to improve the integration of R and C++

[This article was first published on pacha.dev/blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

About

From cpp11 description: “Provides a header only, C++11 interface to R’s C interface. Compared to other approaches ‘cpp11’ strives to be safe against long jumps from the C API as well as C++ exceptions, conform to normal R function semantics and supports interaction with ‘ALTREP’ vectors.”

I have used cpp11 for two years right after I started learning C++ with no previous C/C++ knowledge. Now I have suggested the following changes to the codebase to improve the user experience and reduce the number of lines of code needed to perform common tasks. I excluded describing PRs that only relate to technical aspects or tests.

PRs

Convert logicals to integers and doubles

as_integers() and as_doubles() now understand logical inputs (#426).

Here is an example of something that returned an error before:

test_that("as_doubles(logicals)") {
  cpp11::writable::logicals y;

  for (int i = 0; i < 4; i++) {
    y.push_back(i % 2 == 0);
  }

  cpp11::doubles i(cpp11::as_doubles(y));

  expect_true(i[0] == 1.0);
  expect_true(i[1] == 0.0);
  expect_true(i[2] == 1.0);
  expect_true(i[3] == 0.0);
  expect_true(cpp11::detail::r_typeof(i) == REALSXP);
}

Improving string vector performance for push_back and subscript assignment

I added refactors and test that translate into a push_back that is closer to a 1:1 speed ratio than 1:4 compared to direct assignment (#430).

Previously, the push_back was 4 times slower than direct assignment because of protections applied in cases when there is immediate assignment with no translation.

# A tibble: 14 × 6
   expression                       len      min mem_alloc n_itr  n_gc
   <bch:expr>                     <int> <bch:tm> <bch:byt> <int> <dbl>
 1 assign_cpp11_(n = len, 123L) 1000000 590.79ms   21.63MB    12     8
 2 assign_rcpp_(n = len, 123L)  1000000 441.09ms    7.63MB    15     5

# A tibble: 3 × 6
  expression                          len      min mem_alloc n_itr  n_gc
  <bch:expr>                        <int> <bch:tm> <bch:byt> <int> <dbl>
1 grow_strings_cpp11_(len, 123L)  1000000    462ms   23.63MB     7    13
2 grow_strings_rcpp_(len, 123L)   1000000    453ms    7.63MB    16     4
3 grow_strings_manual_(len, 123L) 1000000    438ms   23.63MB     8    12

Convert ordered and unordered C++ maps to R lists

Ordered and unordered C++ maps are converted to R lists now (#437).

Here is an example of something that was not possible before:

[[cpp11::register]] SEXP ordered_map_to_list_(cpp11::doubles x) {
  std::map<double, int> counts;
  int n = x.size();
  for (int i = 0; i < n; i++) {
    counts[x[i]]++;
  }
  return cpp11::as_sexp(counts);
}

Correctly set names for matrices

Previously, cpp11 ignored the column or row names nor allowed to define those from C++ side for a doubles_matrix or integers_matrix, except if it was converted to a SEXP (#428).

Here is an example of the correction:

[[cpp11::register]] cpp11::doubles_matrix<> mat_mat_create_dimnames() {
  cpp11::writable::doubles_matrix<> out(2, 2);

  out(0, 0) = 1;
  out(0, 1) = 2;
  out(1, 0) = 3;
  out(1, 1) = 4;

  cpp11::writable::list dimnames(2);
  dimnames[0] = cpp11::strings({"a", "b"});
  dimnames[1] = cpp11::strings({"c", "d"});

  out.attr("dimnames") = dimnames;

  return out;
}

Copy complex numbers, vectors or matrices from R to C++ and viceversa

Previously, I was passing complex numbers from R to C++ and viceversa by converting them to a list with the real part and the imaginary part expressed as the first and second vectors of the list. Now it is possible to pass them directly (#427).

Here is an example of something that was not possible before:

test_that("vector objects can be created, filled, and copied") {
  cpp11::writable::complexes v(2);
  v[0] = std::complex<double>(1, 2);
  v[1] = std::complex<double>(3, 4);

  cpp11::complexes vc = v;

  expect_true(v.size() == vc.size());

  for (int i = 0; i < 2; ++i) {
   expect_true(v[i] == vc[i]);
  }
}

Document functions with Roxygen directly in C++ scripts

In order to reduce clutter in my workflow, I added some code to be able to roxygenise directly in the cpp files rather than document the functions by separate (#440).

Here is an example of something that was not possible before:

#include "cpp11/doubles.hpp"
using namespace cpp11;

/* roxygen start
@title Roxygenised x plus 1
@param x numeric value
@description Dummy function to test roxygen2. It adds 1.0 to a double.
@export
@examples roxcpp_(1.0)
roxygen end */
[[cpp11::register]] double roxcpp_(double x) {
  double y = x + 1.0;
  return y;
}
To leave a comment for the author, please follow the link and comment on their blog: pacha.dev/blog.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)