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  35.0  38.50  53.099  47.65  66.2  137.2   100  a 
##      lm 657.1 722.85 792.099 752.35 788.9 1418.3   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_15.0.2-1 Rcpp_1.1.0             inline_0.3.21         
## 
## loaded via a namespace (and not attached):
##  [1] microbenchmark_1.5.0 cli_3.6.5            knitr_1.51          
##  [4] TH.data_1.1-4        rlang_1.1.6          xfun_0.53           
##  [7] otel_0.2.0           jsonlite_2.0.0       zoo_1.8-14          
## [10] htmltools_0.5.8.1    sass_0.4.10          rmarkdown_2.30      
## [13] grid_4.4.3           evaluate_1.0.5       jquerylib_0.1.4     
## [16] MASS_7.3-65          fastmap_1.2.0        mvtnorm_1.3-3       
## [19] yaml_2.3.10          lifecycle_1.0.5      bookdown_0.45       
## [22] compiler_4.4.3       multcomp_1.4-29      codetools_0.2-20    
## [25] sandwich_3.1-1       rstudioapi_0.17.1    blogdown_1.22       
## [28] lattice_0.22-7       digest_0.6.37        R6_2.6.1            
## [31] splines_4.4.3        Matrix_1.7-4         bslib_0.10.0        
## [34] tools_4.4.3          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/