The development repository for cpp11 is https://github.com/r-lib/cpp11.
First install any dependencies needed for development.
install.packages("remotes") ::install_deps(dependencies = TRUE)remotes
You can load the package in an interactive R session
Or run the tests with
test() will also re-compile the package if needed, so
you do not always have to run
If you change the cpp11 headers you will need to clean and recompile the cpp11test package
Generally when developing the C++ headers I run R with its working
directory in the
cpp11test directory and use
devtools::test() to run the cpp11tests.
To calculate code coverage of the cpp11 package run the following
cpp11 root directory.
This project uses clang-format (version 10) to automatically format the c++ code.
You can run
make format to re-format all code in the
project. If your system does not have
10, this can be installed using a homebrew tap at the
command line with
brew install r-lib/taps/clang-format@10.
You may need to link the newly installed version 10. To do so, run
brew unlink clang-format followed by
brew link clang-format@10.
Alternatively many IDEs support automatically running
clang-format every time files are written.
cpp11 is a header only library, so all source code exposed to users
lives in inst/include.
R code used to register functions and for
cpp11::cpp_source() is in R/. Tests for
only the code in
R/ is in tests/testthat/
The rest of the code is in a separate cpp11test/
package included in the source tree. Inside cpp11test/src
the files that start with
test- are C++ tests using the Catch
support in testthat. In addition there are some regular R tests in cpp11test/tests/testthat/.
All of the basic r_vector classes are class templates, the base
template is defined in cpp11/r_vector.hpp
The template parameter is the type of value the
particular R vector stores, e.g.
cpp11::doubles. This differs from Rcpp, whose first
template parameter is the R vector type, e.g.
There are two different coercion functions
as_sexp() takes a C++ object and coerces it to a SEXP
object, so it can be used in R.
as_cpp<>() is a
template function that takes a SEXP and creates a C++ object from it
The various methods for both functions are defined in cpp11/as.hpp
This is definitely the most complex part of the cpp11 code, with extensive use of template metaprogramming. In particular the substitution failure is not an error (SFINAE) technique is used to control overloading of the functions. If we could use C++20 a lot of this code would be made simpler with Concepts, but alas.
The most common C++ types are included in the test suite and should work without issues, as more exotic types are used in real projects additional issues may arise.
Some useful links on SFINAE
cpp11 uses an idea proposed by Luke Tierney to use a double linked list with the head preserved to protect objects cpp11 is protecting.
Each node in the list uses the head (
CAR) part to point
to the previous node, and the
CDR part to point to the next
TAG is used to point to the object being
protected. The head and tail of the list have
CDR pointers respectively.
preserved.insert() with a regular R object will
add a new node to the list and return a protect token corresponding to
the node added. Calling
preserved.release() on this
returned token will release the protection by unlinking the node from
the linked list.
This scheme scales in O(1) time to release or insert an object vs
O(N) or worse time with
These functions are defined in protect.hpp
In R 3.5+ cpp11 uses
R_UnwindProtect to protect (most)
calls to the R API that could fail. These are usually those that
allocate memory, though in truth most R API functions could error along
some paths. If an error happends under
cpp11 will throw a C++ exception. This exception is caught by the try
catch block defined in the
BEGIN_CPP11 macro in cpp11/declarations.hpp.
The exception will cause any C++ destructors to run, freeing any
resources held by C++ objects. After the try catch block exits the R
error unwinding is then continued by
a normal R error results.
In R versions prior to 3.5
R_UnwindProtect() is not
available. Unfortunately the options to emulate it are not ideal.
R_TopLevelExec()works to avoid the C long jump, but because the code is always run in a top level context any errors or messages thrown cannot be caught by
tryCatch()or similar techniques.
R_TryCatch()is not available prior to R 3.4, and also has a serious bug in R 3.4 (fixed in R 3.5).
tryCatch()function which contains an expression that runs a C function which then runs the C++ code would be an option, but implementing this is convoluted and it would impact performance, perhaps severely.
cpp11::unwind_protect()be a no-op for these versions. This means any resources held by C++ objects would leak, including cpp11::r_vector / cpp11::sexp objects.
None of these options is perfect, here are some pros and cons for each.
If packages are concerned about the leaked memory they can call
cpp11::preserved.release_all() as needed to release the
current protections for all objects managed by cpp11. This is not done
automatically because in some cases the protections should persist
.Call() boundry, e.g. in vroom altrep objects