1--- 2title: "Converting from Rcpp" 3output: rmarkdown::html_vignette 4vignette: > 5 %\VignetteIndexEntry{Converting from Rcpp} 6 %\VignetteEngine{knitr::rmarkdown} 7 %\VignetteEncoding{UTF-8} 8--- 9 10```{r, include = FALSE} 11knitr::opts_chunk$set( 12 collapse = TRUE, 13 comment = "#>" 14) 15 16should_run_benchmarks <- function(x) { 17 get("requireNamespace")("cpp11test", quietly = TRUE) && asNamespace("cpp11test")$should_run_benchmarks() 18} 19``` 20 21In many cases there is no need to convert a package from Rcpp. 22If 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. 23However if you _do_ feel like your project will benefit from using cpp11 this vignette will provide some guidance and doing the conversion. 24 25It is also a place to highlight some of the largest differences between Rcpp and cpp11. 26 27## Class comparison table 28 29| Rcpp | cpp11 (read-only) | cpp11 (writable) | cpp11 header | 30| --- | --- | --- | --- | 31| NumericVector | doubles | writable::doubles | <cpp11/doubles.hpp> | 32| IntegerVector | integers | writable::integers | <cpp11/integers.hpp> | 33| CharacterVector | strings | writable::strings | <cpp11/strings.hpp> | 34| RawVector | raws | writable::raws | <cpp11/raws.hpp> | 35| List | list | writable::list | <cpp11/list.hpp> | 36| RObject | sexp | | <cpp11/sexp.hpp> | 37| XPtr | | external_pointer | <cpp11/external_pointer.hpp> | 38| Environment | | environment | <cpp11/environment.hpp> | 39| Function | | function | <cpp11/function.hpp> | 40| Environment (namespace) | | package | <cpp11/function.hpp> | 41| wrap | | as_sexp | <cpp11/as.hpp> | 42| as | | as_cpp | <cpp11/as.hpp> | 43| stop | stop | | <cpp11/protect.hpp> | 44| checkUserInterrupt | check_user_interrupt | | <cpp11/protect.hpp> | 45 46## Incomplete list of Rcpp features not included in cpp11 47 48- None of [Modules](https://CRAN.R-project.org/package=Rcpp/vignettes/Rcpp-modules.pdf) 49- None of [Sugar](https://CRAN.R-project.org/package=Rcpp/vignettes/Rcpp-sugar.pdf) 50- Some parts of [Attributes](https://CRAN.R-project.org/package=Rcpp/vignettes/Rcpp-attributes.pdf) 51 - No dependencies 52 - No random number generator restoration 53 - No support for roxygen2 comments 54 - No interfaces 55 56## Read-only vs writable vectors 57 58The 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. 59 60The default classes, e.g. `cpp11::doubles` are *read-only* classes that do not permit modification. 61If you want to modify the data you need to use the classes in the `cpp11::writable` namespace, e.g. `cpp11::writable::doubles`. 62 63In addition use the `writable` variants if you need to create a new R vector entirely in C++. 64 65## Fewer implicit conversions 66 67Rcpp also allows very flexible implicit conversions, e.g. if you pass a `REALSXP` to a function that takes a `Rcpp::IntegerVector()` it is implicitly converted to a `INTSXP`. 68These conversions are nice for usability, but require (implicit) duplication of the data, with the associated runtime costs. 69 70cpp11 throws an error in these cases. If you want the implicit coercions you can add a call to `as.integer()` or `as.double()` as appropriate from R when you call the function. 71 72## Calling R functions from C++ 73 74Calling R functions from C++ is similar to using Rcpp. 75 76```c++ 77Rcpp::Function as_tibble("as_tibble", Rcpp::Environment::namespace_env("tibble")); 78as_tibble(x, Rcpp::Named(".rows", num_rows), Rcpp::Named(".name_repair", name_repair)); 79``` 80 81```c++ 82using namespace cpp11::literals; // so we can use ""_nm syntax 83 84auto as_tibble = cpp11::package("tibble")["as_tibble"]; 85as_tibble(x, ".rows"_nm = num_rows, ".name_repair"_nm = name_repair); 86``` 87 88 89## Appending behavior 90 91One major difference in Rcpp and cpp11 is how vectors are grown. 92Rcpp vectors have a `push_back()` method, but unlike `std::vector()` no additional space is reserved when pushing. 93This makes calling `push_back()` repeatably very expensive, as the entire vector has to be copied each call. 94 95In contrast `cpp11` vectors grow efficiently, reserving extra space. 96Because 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. 97 98```{r, message = FALSE, eval = should_run_benchmarks()} 99library(cpp11test) 100grid <- expand.grid(len = 10 ^ (0:7), pkg = "cpp11", stringsAsFactors = FALSE) 101grid <- rbind( 102 grid, 103 expand.grid(len = 10 ^ (0:4), pkg = "rcpp", stringsAsFactors = FALSE) 104) 105b_grow <- bench::press(.grid = grid, 106 { 107 fun = match.fun(sprintf("%sgrow_", ifelse(pkg == "cpp11", "", paste0(pkg, "_")))) 108 bench::mark( 109 fun(len) 110 ) 111 } 112)[c("len", "pkg", "min", "mem_alloc", "n_itr", "n_gc")] 113saveRDS(b_grow, "growth.Rds", version = 2) 114``` 115 116```{r, echo = FALSE, dev = "svg", fig.ext = "svg", eval = capabilities("cairo")} 117b_grow <- readRDS("growth.Rds") 118library(ggplot2) 119ggplot(b_grow, aes(x = len, y = min, color = pkg)) + 120 geom_point() + 121 geom_line() + 122 bench::scale_y_bench_time() + 123 scale_x_log10( 124 breaks = scales::trans_breaks("log10", function(x) 10^x), 125 labels = scales::trans_format("log10", scales::math_format(10^.x)) 126 ) + 127 coord_fixed() + 128 theme(panel.grid.minor = element_blank()) + 129 labs(title = "log-log plot of vector size vs construction time", x = NULL, y = NULL) 130``` 131 132```{r, echo = FALSE} 133knitr::kable(b_grow) 134``` 135 136## Random Number behavior 137 138Rcpp unconditionally includes calls to `GetRNGstate()` and `PutRNGstate()` before each wrapped function. 139This 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. 140cpp11 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. 141See [R-exts 6.3 - Random number generation](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Random-numbers) for details on these functions. 142 143One convenient way to do safely is to use a simple class: 144 145```cpp 146class local_rng { 147public: 148 local_rng() { 149 GetRNGstate(); 150 } 151 152 ~local_rng(){ 153 PutRNGstate(); 154 } 155}; 156 157void foo() { 158 local_rng rng_state; 159 /* my code using the RNG */ 160} 161``` 162 163## Mechanics of converting a package from Rcpp 164 1651. Add cpp11 to `LinkingTo` 1661. Add C++11 to `SystemRequirements` 1671. Convert all instances of `// [[Rcpp::export]]` to `[[cpp11::register]]` 1681. Clean and recompile the package, e.g. `pkgbuild::clean_dll()` `pkgload::load_all()` 1691. Run tests `devtools::test()` 1701. Start converting function by function 171 - Remember you can usually inter-convert between cpp11 and Rcpp classes by going through `SEXP` if needed. 172 - Converting the code a bit at a time (and regularly running your tests) is the best way to do the conversion correctly and make progress 173 - Doing a separate commit after converting each file (or possibly each function) can make finding any regressions with [git bisect](https://youtu.be/KKeucpfAuuA) much easier in the future. 174 175## Common issues when converting 176 177### STL includes 178 179Rcpp.h includes a number of STL headers automatically, notably `<string>` and `<vector>`, however the cpp11 headers generally do not. If you have errors like 180 181> error: no type named 'string' in namespace 'std' 182 183You will need to include the appropriate STL header, in this case `<string>`. 184 185### R API includes 186 187cpp11 conflicts with macros declared by some R headers unless the macros `R_NO_REMAP` and `STRICT_R_HEADERS` are defined. 188If you include `cpp11/R.hpp` before any R headers these macros will be defined appropriately, otherwise you may see errors like 189 190> R headers were included before cpp11 headers and at least one of R_NO_REMAP or STRICT_R_HEADERS was not defined. 191 192Which indicate that you must either change your include order or add preprocessor definitions for `R_NO_REMAP` and `STRICT_R_HEADERS`. 193Note that transitive includes of R headers (for example, those included by `Rcpp.h`) can also introduce the conflicting macros. 194 195### Type aliases 196 197If you use typedefs for cpp11 types or define custom types you will need to define them in a `pkgname_types.hpp` file so that `cpp_register()` can include it in the generated code. 198 199### `cpp11::stop()` and `cpp11::warning()` with `std::string` 200 201`cpp11::stop()` and `cpp11::warning()` are thin wrappers around `Rf_stop()` and `Rf_warning()`. 202These are simple C functions with a `printf()` API, so do not understand C++ objects like `std::string`. 203Therefore you need to call `obj.c_str()` when passing character data to them. 204 205### Logical vector construction 206 207If you are constructing a length 1 logical vector you may need to explicitly use a `r_bool()` object in the initializer list rather than `TRUE`, `FALSE` or `NA_INTEGER`. 208This issue only occurs with the clang compiler, not gcc. 209When constructing vectors with more than one element this is not an issue 210 211```cpp 212// bad 213cpp11::writable::logicals({FALSE}); 214 215// good 216cpp11::writable::logicals({r_bool(FALSE)}); 217 218// good 219cpp11::writable::logicals({FALSE, NA_LOGICAL}); 220``` 221