| Title: | A 'C++20' API for R |
|---|---|
| Description: | A header-only 'C++20' API for manipulating R data structures from 'C++'. Provides 'C++20' concepts specific to R, custom scalar and vector classes with built-in NA handling, automatic object protection, 'SIMD' (single-instruction-multiple-data), parallelisation, and a streamlined system for registering 'C++' functions, including templates, to R. Full API reference and documentation are available at <https://nicchr.github.io/cppally/>. |
| Authors: | Nick Christofides [aut, cre, cph] (ORCID: <https://orcid.org/0000-0002-9743-7342>), Martin Leitner-Ankerl [cph] (Author of bundled ankerl::unordered_dense library), Malte Skarupke [cph] (Author of bundled ska_sort library), Posit Software, PBC [cph] (SEXP protection mechanism in r_protect.h inspired by cpp11) |
| Maintainer: | Nick Christofides <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0.9000 |
| Built: | 2026-06-06 13:25:52 UTC |
| Source: | https://github.com/nicchr/cppally |
Register C++ functions to be callable from R. C++ functions decorated with
[[cppally::register]] will be registered (including template functions).
cpp_register( path = ".", quiet = !is_interactive(), extension = c(".cpp", ".cc") )cpp_register( path = ".", quiet = !is_interactive(), extension = c(".cpp", ".cc") )
path |
Path to package root directory. |
quiet |
If |
extension |
The file extension to use for the generated src/cppally file. Options are either '.cpp' (the default) or '.cc'. |
The paths to the generated R and C++ source files.
cpp11-style helpers to compile cppally code outside of a cppally-linked package context.
cpp_source() compiles and loads a single C++ file for use in R,
either from an expression or a cpp file.
This may include multiple C++ functions.
cpp_eval() evaluates a single C++ expression and returns the result.
For example cpp_eval('get_threads()') will run the C++ function
cppally::get_threads() and return the number of OMP threads currently set
for use. For expressions no return result, the call is evaluated and returns
NULL invisibly.
cpp_source( file, code = NULL, env = parent.frame(), clean = TRUE, quiet = TRUE, debug = FALSE, preserve_altrep = FALSE, check_factors = FALSE, check_data_frames = FALSE, copy_on_modify = FALSE, cxx_std = Sys.getenv("CXX_STD", "CXX20"), dir = tempfile() ) cpp_eval( code, env = curr_env(), clean = TRUE, quiet = TRUE, debug = FALSE, preserve_altrep = FALSE, check_factors = FALSE, check_data_frames = FALSE, copy_on_modify = FALSE, simplify = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX20") )cpp_source( file, code = NULL, env = parent.frame(), clean = TRUE, quiet = TRUE, debug = FALSE, preserve_altrep = FALSE, check_factors = FALSE, check_data_frames = FALSE, copy_on_modify = FALSE, cxx_std = Sys.getenv("CXX_STD", "CXX20"), dir = tempfile() ) cpp_eval( code, env = curr_env(), clean = TRUE, quiet = TRUE, debug = FALSE, preserve_altrep = FALSE, check_factors = FALSE, check_data_frames = FALSE, copy_on_modify = FALSE, simplify = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX20") )
file |
C++ file. |
code |
For |
env |
Environment where R functions should be defined. |
clean |
Should files be cleaned up after sourcing? Default is |
quiet |
Should compiler output be suppressed? Default is |
debug |
Should C++ code be compiled in a debug build?
Default is |
preserve_altrep |
Should ALTREP vectors be preserved by avoiding
materialisation where possible? Default is |
check_factors |
Should factor levels be validated when using
|
check_data_frames |
Should data frames be validated when constructing
|
copy_on_modify |
Should copy-on-modify be used everywhere? Default is
|
cxx_std |
C++ standard to use. Should be >= C++20. |
dir |
Directory to store the source files.
The default is a temporary directory via |
simplify |
Applies to |
cpp_source() invisibly compiles the C++ code and registers
the [[cppally::register]] tagged functions to R. cpp_eval() returns the results of the evaluated C++ expressions.
library(cppally) library(bit64) cpp_eval('print("hello world!")') # Default values of all cppally scalars cpp_eval(c( 'r_lgl()', 'r_int()', 'r_dbl()', 'r_int64()', 'r_str()', 'r_raw()', 'r_cplx()', 'r_date()', 'r_psxct()' )) cpp_source(code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_dbl add(r_dbl x, r_dbl y){ return x + y; } ', debug = TRUE) add(1, 2) add(2, NA) ### ALTREP ### # cppally also supports lazy ALTREP materialisation as an opt-in feature. # To opt-in, set `preserve_altrep = TRUE` cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_int last_altrep_unaware(r_vec<r_int> x){ r_int out; r_size_t n = x.length(); if (n > 0){ out = x.get(n - 1); } return out; } ', debug = TRUE ) cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_int last_altrep_aware(r_vec<r_int> x){ r_int out; r_size_t n = x.length(); if (n > 0){ out = x.get(n - 1); } return out; } ', debug = TRUE, preserve_altrep = TRUE ) library(bench) mark(last_altrep_aware(1:10^5)) # No materialisation mark(last_altrep_unaware(1:10^5)) # Materialises full vector ### Copy-on-modify ### # cppally supports copy-on-modify as an opt-in feature # It is disabled by default because it incurs a major performance penalty # and has been deemed not worth it even for the safety benefits # That being said, if you prefer absolute safety over speed then you can # enable it globally via `cppally::use_copy_on_modify()` or # via the arg `copy_on_modify` if using `cpp_source()` cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_vec<r_int> reverse(r_vec<r_int> x){ x.rev(); // in-place reverse return x; } ', copy_on_modify = TRUE ) x <- c(1L, 2L, 3L) reverse(x) x # x was preserved and not updated by reference (as expected) x <- sample.int(10^5) mark(reverse(x)) # Memory allocated, therefore x was copied before reversing # The cppally preferred approach is to allocate a fresh vector or copy the # existing vector cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_vec<r_int> cppally_reverse(r_vec<r_int> x){ r_vec<r_int> out = shallow_copy(x); out.rev(); return out; } ', copy_on_modify = FALSE ) mark( r_reverse = rev(x), cppally_copy_on_modify_reverse = reverse(x), cppally_no_copy_on_modify_reverse = cppally_reverse(x) )library(cppally) library(bit64) cpp_eval('print("hello world!")') # Default values of all cppally scalars cpp_eval(c( 'r_lgl()', 'r_int()', 'r_dbl()', 'r_int64()', 'r_str()', 'r_raw()', 'r_cplx()', 'r_date()', 'r_psxct()' )) cpp_source(code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_dbl add(r_dbl x, r_dbl y){ return x + y; } ', debug = TRUE) add(1, 2) add(2, NA) ### ALTREP ### # cppally also supports lazy ALTREP materialisation as an opt-in feature. # To opt-in, set `preserve_altrep = TRUE` cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_int last_altrep_unaware(r_vec<r_int> x){ r_int out; r_size_t n = x.length(); if (n > 0){ out = x.get(n - 1); } return out; } ', debug = TRUE ) cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_int last_altrep_aware(r_vec<r_int> x){ r_int out; r_size_t n = x.length(); if (n > 0){ out = x.get(n - 1); } return out; } ', debug = TRUE, preserve_altrep = TRUE ) library(bench) mark(last_altrep_aware(1:10^5)) # No materialisation mark(last_altrep_unaware(1:10^5)) # Materialises full vector ### Copy-on-modify ### # cppally supports copy-on-modify as an opt-in feature # It is disabled by default because it incurs a major performance penalty # and has been deemed not worth it even for the safety benefits # That being said, if you prefer absolute safety over speed then you can # enable it globally via `cppally::use_copy_on_modify()` or # via the arg `copy_on_modify` if using `cpp_source()` cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_vec<r_int> reverse(r_vec<r_int> x){ x.rev(); // in-place reverse return x; } ', copy_on_modify = TRUE ) x <- c(1L, 2L, 3L) reverse(x) x # x was preserved and not updated by reference (as expected) x <- sample.int(10^5) mark(reverse(x)) # Memory allocated, therefore x was copied before reversing # The cppally preferred approach is to allocate a fresh vector or copy the # existing vector cpp_source( code = ' #include <cppally.hpp> using namespace cppally; [[cppally::register]] r_vec<r_int> cppally_reverse(r_vec<r_int> x){ r_vec<r_int> out = shallow_copy(x); out.rev(); return out; } ', copy_on_modify = FALSE ) mark( r_reverse = rev(x), cppally_copy_on_modify_reverse = reverse(x), cppally_no_copy_on_modify_reverse = cppally_reverse(x) )
devtools::document() to support cppally package developmentA wrapper around devtools::document() to support cppally package development
document(pkg = ".", roclets = NULL, quiet = FALSE)document(pkg = ".", roclets = NULL, quiet = FALSE)
pkg |
See |
roclets |
See |
quiet |
See |
Invisibly updates roxygen documentation, compiles C++ code and exports cppally tagged functions to R.
devtools::load_all() specifically for cppallyA wrapper around devtools::load_all() specifically for cppally
load_all(path = ".", debug = FALSE, ...)load_all(path = ".", debug = FALSE, ...)
path |
Path to package. |
debug |
Should package be built without optimisations?
Default is |
... |
Further arguments passed on to |
Invisibly registers cppally tagged functions and compiles C++ code.
CPPALLY_CHECK_DATA_FRAMES flag to MakevarsAdds a flag to Makevars which enables stricter validation on data frames
at the point of r_df construction. This ensures that column lengths are
always valid, avoiding potential R crashes downstream.
The default behaviour is NOT to validate column lengths, enabling faster
r_df creating from SEXP.
use_check_data_frames()use_check_data_frames()
Invisibly adds the CPPALLY_CHECK_DATA_FRAMES flag to Makevars.
CPPALLY_CHECK_FACTORS flag to MakevarsAdds a flag to Makevars which enables stricter validation on factor levels
at the point of r_factors construction. This avoids creating r_factors
objects with invalid levels and avoiding potential R crashes.
The default behaviour is NOT to validate factor levels, which is naturally
faster when calling C++ functions that take r_factors inputs.
use_check_factors()use_check_factors()
Invisibly adds the CPPALLY_CHECK_FACTORS flag to Makevars.
CPPALLY_COPY_ON_MODIFY flag to MakevarsAdds a flag to Makevars which enables copy-on-modify behaviour like in R.
This ensures you can never modify object B's data by modifying object A
(unless you do it manually via r_vec::data()).
The default behaviour is that r_vec::set() always modifies-in-place with
no checks to shared or referenced objects. This default behaviour is
generally much faster.
Adding this global flag effectively kills most parallelisation.
To achieve copy-on-modify we have to check whether the r_sexp
object being modified is referenced anywhere else at each and every
call to set(). This check (via r_sexp::is_exclusive()) is not thread-safe
and therefore most cppally parallelisation is disabled
once CPPALLY_COPY_ON_MODIFY is set.
use_copy_on_modify()use_copy_on_modify()
Invisibly adds the CPPALLY_COPY_ON_MODIFY flag to Makevars.
usethis style helper to add the necessary setup to a new package to help users get started with writing C++ code.
use_cppally()use_cppally()
Invisibly sets up the necessary conditions for developing a package with cppally.
Adds a flag to Makevars which enables lazy materialisation of ALTREP vectors.
use_preserve_altrep_flag()use_preserve_altrep_flag()
Invisibly adds the CPPALLY_PRESERVE_ALTREP flag to Makevars.