In many cases there is no need to convert a package from Rcpp. If the code is already written and you don’t have a very compelling need to use cpp11 I would recommend you continue to use Rcpp. However if you do feel like your project will benefit from using cpp11 this vignette will provide some guidance and doing the conversion.
It is also a place to highlight some of the largest differences between Rcpp and cpp11.
Rcpp | cpp11 (read-only) | cpp11 (writable) | cpp11 header |
---|---|---|---|
NumericVector | doubles | writable::doubles | <cpp11/doubles.hpp> |
IntegerVector | integers | writable::integers | <cpp11/integers.hpp> |
CharacterVector | strings | writable::strings | <cpp11/strings.hpp> |
RawVector | raws | writable::raws | <cpp11/raws.hpp> |
List | list | writable::list | <cpp11/list.hpp> |
RObject | sexp | <cpp11/sexp.hpp> | |
XPtr | external_pointer | <cpp11/external_pointer.hpp> | |
Environment | environment | <cpp11/environment.hpp> | |
Function | function | <cpp11/function.hpp> | |
Environment (namespace) | package | <cpp11/function.hpp> | |
wrap | as_sexp | <cpp11/as.hpp> | |
as | as_cpp | <cpp11/as.hpp> | |
stop | stop | <cpp11/protect.hpp> |
The largest difference between cpp11 and Rcpp classes is that Rcpp classes modify their data in place, whereas cpp11 classes require copying the data to a writable class for modification.
The default classes, e.g. cpp11::doubles
are read-only classes that do not permit modification. If you want to modify the data you need to use the classes in the cpp11::writable
namespace, e.g. cpp11::writable::doubles
.
In addition use the writable
variants if you need to create a new R vector entirely in C++.
Calling R functions from C++ is similar to using Rcpp.
One major difference in Rcpp and cpp11 is how vectors are grown. Rcpp vectors have a push_back()
method, but unlike std::vector()
no additional space is reserved when pushing. This makes calling push_back()
repeatably very expensive, as the entire vector has to be copied each call.
In contrast cpp11
vectors grow efficiently, reserving extra space. Because of this you can do ~10,000,000 vector appends with cpp11 in approximately the same amount of time that Rcpp does 10,000, as this benchmark demonstrates.
grid <- expand.grid(len = 10 ^ (0:7), pkg = "cpp11", stringsAsFactors = FALSE)
grid <- rbind(
grid,
expand.grid(len = 10 ^ (0:4), pkg = "rcpp", stringsAsFactors = FALSE)
)
b_grow <- bench::press(.grid = grid,
{
fun = match.fun(sprintf("%sgrow_", ifelse(pkg == "cpp11", "", paste0(pkg, "_"))))
bench::mark(
fun(len)
)
}
)[c("len", "pkg", "min", "mem_alloc", "n_itr", "n_gc")]
saveRDS(b_grow, "growth.Rds", version = 2)
len | pkg | min | mem_alloc | n_itr | n_gc |
---|---|---|---|---|---|
1e+00 | cpp11 | 3.25µs | 0B | 9999 | 1 |
1e+01 | cpp11 | 5.76µs | 0B | 9999 | 1 |
1e+02 | cpp11 | 8.45µs | 1.89KB | 9999 | 1 |
1e+03 | cpp11 | 13.62µs | 16.03KB | 9999 | 1 |
1e+04 | cpp11 | 63.47µs | 256.22KB | 2797 | 2 |
1e+05 | cpp11 | 432.75µs | 2MB | 451 | 4 |
1e+06 | cpp11 | 3.45ms | 16MB | 56 | 3 |
1e+07 | cpp11 | 111.09ms | 256MB | 1 | 5 |
1e+00 | rcpp | 2.36µs | 0B | 10000 | 0 |
1e+01 | rcpp | 2.87µs | 0B | 9999 | 1 |
1e+02 | rcpp | 14.84µs | 42.33KB | 9997 | 3 |
1e+03 | rcpp | 529.55µs | 3.86MB | 345 | 3 |
1e+04 | rcpp | 71.19ms | 381.96MB | 1 | 3 |
Rcpp unconditionally includes calls to GetRNGstate()
and PutRNGstate()
before each wrapped function. This ensures that if any C++ code calls the R API functions unif_rand()
, norm_rand()
, exp_rand()
or R_unif_index()
the random seed state is set accordingly. cpp11 does not do this, so you must include the calls to GetRNGstate()
and PutRNGstate()
yourself if you use any of those functions in your C++ code. See R-exts 6.3 - Random number generation for details on these functions.
LinkingTo
SystemRequirements
// [[Rcpp::export]]
to [[cpp11::register]]
pkgbuild::clean_dll()
pkgload::load_all()
devtools::test()
SEXP
if needed.Due to cpp11 redefining the Rboolean enum any cpp11 header needs to come before any Rcpp or Rinternals.h include. Errors like
error: redefinition of enumerator ‘FALSE’
Indicate this is a problem and can be resolved by ensuring the cpp11 headers are included first.
Rcpp.h includes a number of STL headers automatically, notably <string>
and <vector>
, however the cpp11 headers generally do not. If you have errors like
error: no type named ‘string’ in namespace ‘std’
You will need to include the appropriate STL header, in this case <string>
.
cpp11 defines a compatible but different version of the Rboolean enum. This means that you must ensure that at least one cpp11 header is included before any R headers. One easy way to ensure this is to replace #include <Rinternals.h>
with #include <cpp11/R.hpp>
.