From prototype to production, choosing the right R and C++ tool in Rcpp

Jul 6, 2025·
Thiago de Paula Oliveira
Thiago de Paula Oliveira
· 5 min read

1 Prototype at light-speed with inline

inline::cxxfunction() compiles, links and returns an R function from a character string, ideal for quick sketches or teaching.

library(inline)
library(Rcpp)
## 
## Attaching package: 'Rcpp'
## The following object is masked from 'package:inline':
## 
##     registerPlugin
src <- '
  // Declare two Rcpp numeric vectors, populated from the R arguments `a` and `b`
  Rcpp::NumericVector xa(a), xb(b);

  // Cache the lengths of those vectors in plain C++ ints for fast reuse
  int na = xa.size(), nb = xb.size();

  // Pre-allocate the output vector, with length (na + nb − 1) for a full 
  // discrete convolution
  Rcpp::NumericVector out(na + nb - 1);

  // Walk over every element of the first vector
  for (int i = 0; i < na; ++i)
    // Multiply the i-th element of `a` with every element of `b`
    for (int j = 0; j < nb; ++j)
        // Accumulate the product at the correct lag/index in the result
        out[i + j] += xa[i] * xb[j];

  // Return the filled result vector back to R
  return out;
'

conv <- cxxfunction(signature(a = "numeric", b = "numeric"),
                    body = src, plugin = "Rcpp")
conv(1:4, 2:5)
## [1]  2  7 16 30 34 31 20
#> [1]  2  7 16 30 34 31 20

Choose inline when:

  • you need an answer now without creating extra files;
  • you are benchmarking or exploring an API shape;
  • you want to inject ad-hoc #include blocks or compiler flags.

1.1 Level-up with plugins and Rcpp attributes

1.1.1 4.1 Plugins

Plugins bolt extra headers and libraries onto an inline or attributes build—no manual -I/-L juggling. The built-in RcppArmadillo plugin lets you write BLAS-backed linear algebra on the fly:

library(RcppArmadillo)
src <- '
  // Armadillo + Rcpp headers are supplied automatically by the plugin
  using namespace Rcpp;

  /* ---- 1. Map the inputs -------------------------------------------------- */
  Rcpp::NumericVector  yr_(yr);            // wrap SEXP as NumericVector
  Rcpp::NumericMatrix  Xr_(Xr);            // wrap SEXP as NumericMatrix

  int n = Xr_.nrow();                      // rows  = #obs
  int k = Xr_.ncol();                      // cols  = #predictors

  // zero-copy Armadillo views
  arma::colvec y( yr_.begin(), yr_.size(), false );
  arma::mat    X( Xr_.begin(), n, k,       false );

  /* ---- 2. OLS -------------------------------------------------------------- */
  arma::colvec coef = arma::solve(X, y);
  arma::colvec res  = y - X * coef;
  double s2         = arma::dot(res, res) / (n - k);
  arma::colvec se   = arma::sqrt( s2 * arma::diagvec( arma::inv(X.t() * X) ) );

  /* ---- 3. Ship results back to R ------------------------------------------ */
  return List::create(_["coef"] = coef,
                      _["se"]   = se,
                      _["df"]   = n - k);
'

fastLm <- cxxfunction(signature(yr = "numeric", Xr = "numeric"),
                      body   = src,
                      plugin = "RcppArmadillo")

# Usage 
set.seed(123)

n <- 1000
k <- 3
X <- cbind(1, matrix(rnorm(n * k), n, k))  # 1st col = intercept
beta  <- c(5, 0.8, -1.2, 0.5)                 # true coefficients
y  <- X %*% beta + rnorm(n, sd = 2)           # synthetic data
res <- fastLm(y, X)
str(res)
## List of 3
##  $ coef: num [1:4, 1] 4.984 0.797 -1.216 0.603
##  $ se  : num [1:4, 1] 0.0628 0.0636 0.0625 0.0642
##  $ df  : int 996
fit <- lm(y ~ X[,2] + X[,3] + X[,4])  # lm() adds its own intercept

cbind(fastLm = res$coef,
      lm     = coef(fit))
##                                lm
## (Intercept)  4.9844816  4.9844816
## X[, 2]       0.7974000  0.7974000
## X[, 3]      -1.2162403 -1.2162403
## X[, 4]       0.6029614  0.6029614
microbenchmark::microbenchmark(
  fastLm = fastLm(y, X),
  lm     = { lm(y ~ X[,2] + X[,3] + X[,4]) },
  times  = 100
)
## Unit: microseconds
##    expr     min       lq      mean   median       uq      max neval cld
##  fastLm  43.001  46.6515  64.69696  59.2515   80.051  130.201   100  a 
##      lm 856.101 923.0515 995.95507 944.1510 1004.351 1445.001   100   b

1.1.2 4.2 Rcpp Attributes

  • Attributes internalise inline’s magic behind [[Rcpp::export]].
  • sourceCpp() compiles a file;
  • cppFunction() compiles from a string;
  • evalCpp() runs an expression, all while caching builds transparently.
cpp_src <- paste(
  '#include <Rcpp.h>',
  'using namespace Rcpp;',
  '',
  '// [[Rcpp::export]]',
  'int fibonacci(int x) {',
  '  return (x < 2) ? x : fibonacci(x - 1) + fibonacci(x - 2);',
  '}',
  sep = "\n"
)
writeLines(cpp_src, "fibonacci.cpp")   # main copy
Rcpp::sourceCpp("fibonacci.cpp")
fibonacci(20)     #> 6765
## [1] 6765

Use attributes for almost everything once you move beyond exploratory work.

1.2 Quick decision guide

TaskReach for …
Package / production codeRcpp Attributes
One-off sketches, benchmarks, Stack Overflow postinline::cxxfunction()
Extra C++ libraries (Armadillo, Eigen, GSL, …)Plugins (built-in or custom)
Low-level debugging with gdbManual R CMD SHLIB + helper flags
Propagate C++ exceptions cleanlyEnsure BEGIN_RCPP/END_RCPP are present (added automatically by inline / Attributes)

1.3 Take-aways

  • Match R’s compiler or live with hard-to-trace crashes.
  • .Call() via Rcpp is the modern native interface—type, which is safe and header-driven.
  • inline and Attributes shrink the compile–link–load loop to a single line.
  • Plugins keep code portable while tapping heavy C++ libraries.
  • BEGIN_RCPP/END_RCPP keep yourC++ errors readable from R.

2 Reproducibility

sessionInfo()
## R version 4.4.3 (2025-02-28 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 22631)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=English_United Kingdom.utf8 
## [2] LC_CTYPE=English_United Kingdom.utf8   
## [3] LC_MONETARY=English_United Kingdom.utf8
## [4] LC_NUMERIC=C                           
## [5] LC_TIME=English_United Kingdom.utf8    
## 
## time zone: Europe/London
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] RcppArmadillo_14.6.0-1 Rcpp_1.1.0             inline_0.3.21         
## 
## loaded via a namespace (and not attached):
##  [1] microbenchmark_1.4.10 cli_3.6.2             knitr_1.50           
##  [4] TH.data_1.1-3         rlang_1.1.6           xfun_0.52            
##  [7] jsonlite_1.8.8        zoo_1.8-12            htmltools_0.5.8.1    
## [10] sass_0.4.9            rmarkdown_2.29        grid_4.4.3           
## [13] evaluate_1.0.4        jquerylib_0.1.4       MASS_7.3-64          
## [16] fastmap_1.2.0         mvtnorm_1.2-5         yaml_2.3.10          
## [19] lifecycle_1.0.4       bookdown_0.40         compiler_4.4.3       
## [22] multcomp_1.4-28       codetools_0.2-20      sandwich_3.1-1       
## [25] rstudioapi_0.17.1     blogdown_1.21         lattice_0.22-6       
## [28] digest_0.6.35         R6_2.6.1              splines_4.4.3        
## [31] Matrix_1.7-2          bslib_0.7.0           tools_4.4.3          
## [34] survival_3.8-3        cachem_1.1.0

Did you find this page helpful? Consider sharing it 🙌

3 How to cite this post

Oliveira, T. de Paula. (2025, August 9). From prototype to production, choosing the right R and C++ tool in Rcpp.
https://prof-thiagooliveira.netlify.app/post/rcpp-start-with-r/