rguides

Rcpp C++ Integration in R: Speed Up Your R Code with C++

Rcpp lets you write C++ code that runs directly inside R, giving you C++‘s speed without leaving the R environment. When R hits a computational bottleneck — a tight loop, a custom algorithm, or repeated matrix operations — Rcpp C++ integration replaces the slow R code with compiled C++ that runs orders of magnitude faster. This guide covers installation, writing your first C++ function with Rcpp, data type conversions, and benchmarking to measure the real performance gains.

What is Rcpp?

R is excellent for statistics and data analysis, but it can be slow when you’re doing heavy computations. That’s where Rcpp comes in.

Rcpp is a package that lets you write C++ code directly inside R. C++ is a fast, low-level programming language that runs much quicker than R for certain tasks. Rcpp is a bridge between the two languages, handling all the tricky details of converting data back and forth.

Think of Rcpp like a translator: you write C++ (the fast language), and Rcpp translates it so R can understand and use it.

Installing Rcpp

Before you begin, make sure you have a C++ compiler installed on your system:

Now install Rcpp in R:

# Install Rcpp from CRAN
install.packages("Rcpp")

# Load the library
library(Rcpp)

# Check it's installed
packageVersion("Rcpp")

Writing your first C++ function

Here’s a simple example: adding two numbers together. The key insight is that R functions become slow when they involve explicit loops or repeated function calls; C++ excels at exactly those patterns. Let’s start with the pure R version so we can see the contrast with the C++ implementation. In R, you’d write:

add_r <- function(a, b) {
  a + b
}

Here’s the same function written in C++ using Rcpp. The C++ version requires type declarations for both parameters and the return value, and the function body uses C++ syntax for the addition. The // [[Rcpp::export]] attribute above the function signals to Rcpp that this function should be callable from R after compilation:

// Load Rcpp functionality
#include <Rcpp.h>

// This attribute tells Rcpp to expose this function to R
// [[Rcpp::export]]

// The actual C++ function
double add_cpp(double a, double b) {
  return a + b;
}

To use this in R, you have two main options. The first is using sourceCpp() to compile code directly in your R session. This approach keeps everything self-contained in one script: you write the C++ code as a string argument, Rcpp compiles it through your system’s C++ compiler, and the function becomes available as a regular R function without creating any external files. Here is the inline compilation workflow:

library(Rcpp)

# This compiles and loads the C++ function into your R environment
sourceCpp(code = '
#include <Rcpp.h>

// [[Rcpp::export]]
double add_cpp(double a, double b) {
  return a + b;
}
')

# Now call it like any R function
add_cpp(5, 3)  # Returns 8

You can also save your C++ code to a separate file and load it with sourceCpp(). This is the preferred approach for real projects because it keeps your C++ code in a proper file with syntax highlighting and version control, rather than embedding it as a string inside an R script. The function name in R will match the exported C++ function names automatically:

# Save the C++ code to matrix_multiply.cpp, then:
sourceCpp("matrix_multiply.cpp")

Understanding Rcpp data types

C++ needs to know what type of data it’s working with, unlike R which determines types at runtime. Rcpp provides a set of C++ types that mirror R’s data structures, and the conversion happens automatically when values cross the R/C++ boundary. The table below maps each R type to its corresponding Rcpp class, and understanding this mapping is essential because passing the wrong type causes compilation errors rather than runtime warnings. Here are the most common type mappings:

R typeRcpp typeWhat it holds
numeric()Rcpp::NumericVectorDecimal numbers
integer()Rcpp::IntegerVectorWhole numbers
character()Rcpp::CharacterVectorText strings
logical()Rcpp::LogicalVectorTRUE/FALSE
list()Rcpp::ListMixed data
data.frame()Rcpp::DataFrameTabular data

Here’s an example using different vector types. The function accepts three different vector types as arguments and bundles them into a named list for return. Each parameter is typed explicitly in the function signature, so Rcpp knows which conversion to apply when R passes data to the C++ side:

#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::List example_types(
    Rcpp::NumericVector nums,
    Rcpp::IntegerVector ints,
    Rcpp::CharacterVector strs
) {
  // Return all three vectors bundled as a list
  return Rcpp::List::create(
    Rcpp::Named("numbers") = nums,
    Rcpp::Named("integers") = ints,
    Rcpp::Named("strings") = strs
  );
}

Notice the Rcpp::Named() function: this gives names to list elements, just like c(a = 1) in R. The pattern of creating named lists in Rcpp mirrors how you would construct return values in R, making the C++ code feel familiar even with the added type annotations.

A more practical example: adding vectors

A more practical case: adding two vectors element-by-element. While R’s built-in + operator is already vectorized and fast, this example demonstrates the loop-based pattern you would use for operations that R does not provide natively. Compare the R version, which delegates the loop to C under the hood, with the explicit Rcpp loop:

# Pure R version
add_vectors_r <- function(x, y) {
  x + y
}

The C++ equivalent uses a loop because C++ arithmetic operators do not automatically vectorize across elements the way R does. Writing an explicit loop in R would be painfully slow, but in compiled C++ the same loop runs at near-native speed because the compiler optimizes each iteration. Here is the Rcpp version with the loop spelled out:

// C++ version with Rcpp
#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::NumericVector add_vectors_cpp(
    Rcpp::NumericVector x,
    Rcpp::NumericVector y
) {
  // Create output vector of same length
  Rcpp::NumericVector result(x.size());

  // Loop through and add each element
  for (int i = 0; i < x.size(); i++) {
    result[i] = x[i] + y[i];
  }

  return result;
}

The C++ version looks longer, but for large vectors it runs significantly faster. Key things to notice:

  • x.size() gets the vector length
  • Indexing starts at 0 (unlike R’s 1-based indexing)
  • We return the result vector, and Rcpp handles converting it back to R

Working with matrices

Rcpp also handles matrices. The type is Rcpp::NumericMatrix. One important difference: C++ uses zero-based indexing, while R uses one-based.

#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::NumericMatrix matrix_multiply(
    Rcpp::NumericMatrix A,
    Rcpp::NumericMatrix B
) {
  // Get dimensions
  int rows = A.nrow();
  int cols = B.ncol();
  int mid = A.ncol();  // must equal B.nrow()

  // Create result matrix
  Rcpp::NumericMatrix C(rows, cols);

  // Matrix multiplication
  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
      double sum = 0;
      for (int k = 0; k < mid; k++) {
        sum += A(i, k) * B(k, j);
      }
      C(i, j) = sum;
    }
  }

  return C;
}

After compiling the matrix multiplication function, you can call it from R exactly like any other R function. The C++ code handles the loop nesting internally, so the R interface stays simple: pass two numeric matrices and receive their product. Make sure the inner dimensions match, or the function will access memory out of bounds since the C++ version does not include runtime dimension checks:

# First, save the C++ code above to matrix_multiply.cpp
sourceCpp("matrix_multiply.cpp")

A <- matrix(1:4, nrow = 2)
B <- matrix(5:8, nrow = 2)
matrix_multiply(A, B)

Performance comparison

When should you use Rcpp? The microbenchmark package measures execution time precisely, running each expression many times and reporting the median. For operations on large vectors, the C++ version avoids R’s interpreter overhead on every element access, which is why even simple loops can be dramatically faster. Here is a benchmark comparing the R and C++ vector addition functions:

# Define the R version
add_vectors_r <- function(x, y) x + y

# Define the C++ version
library(Rcpp)
sourceCpp(code = '
#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::NumericVector add_vectors_cpp(Rcpp::NumericVector x, Rcpp::NumericVector y) {
  Rcpp::NumericVector result(x.size());
  for (int i = 0; i < x.size(); i++) result[i] = x[i] + y[i];
  return result;
}
')

# Run the benchmark
library(microbenchmark)
x <- runif(10000)
y <- runif(10000)

microbenchmark(
  r_version = add_vectors_r(x, y),
  cpp_version = add_vectors_cpp(x, y)
)

For small vectors (under 1000 elements), the difference is barely noticeable. But as your data grows, C++ typically runs 10 to 100 times faster depending on the operation.

A good rule: only rewrite in C++ the parts of your code that are actually slow. Use R’s built-in profiling tools first to find bottlenecks.

Debugging your Rcpp code

When something goes wrong in your C++ code, you need ways to debug it. Two useful tools are printing output for inspection and raising errors to halt execution on bad inputs.

Printing output during C++ execution helps you trace what your code is doing:

#include <Rcpp.h>

// [[Rcpp::export]]
void debug_example(int x) {
  Rcpp::Rcout << "The value is: " << x << std::endl;
}

Rcout works like R’s cat() or print(), writing output to the R console. Use it to inspect intermediate values during debugging or to report progress from long-running C++ loops. Unlike R’s print(), Rcout does not add any formatting, so you control exactly what appears and where line breaks go.

For handling invalid inputs that should stop execution immediately, Rcpp provides a mechanism equivalent to R’s stop() function. Catching bad arguments early prevents confusing segmentation faults or garbage output that would otherwise occur when C++ code receives unexpected data:

#include <Rcpp.h>

// [[Rcpp::export]]
double safe_divide(double a, double b) {
  if (b == 0) {
    Rcpp::stop("Cannot divide by zero!");
  }
  return a / b;
}

Rcpp::stop() works like stop() in R; it halts execution and shows an error message.

Common beginner mistakes

A few things to watch out for when starting out:

  1. Forgetting the export attribute means your function won’t be visible to R. Without // [[Rcpp::export]], the C++ compiler still compiles the code, but Rcpp does not expose the function to the R session.

  2. Type mismatches can cause subtle bugs. If R passes an integer but your C++ expects a double, Rcpp will attempt a conversion, but the result may not be what you intended. Be explicit about types in your function signatures.

  3. Indexing errors are common because C++ uses 0-based indexing, not R’s 1-based. Accessing element 0 in C++ corresponds to element 1 in R, and looping from 0 to n-1 is the C++ convention.

  4. Memory leaks are rarely an issue because Rcpp handles most memory management automatically through reference counting and garbage collection integration.

Using Rcpp in packages

Once you’re comfortable with sourceCpp(), you might want to use Rcpp in a proper R package. Rcpp provides helper functions to set this up:

# Create a new package with Rcpp
library(Rcpp)
Rcpp::RcppPackage.skeleton("my_package")

# This creates the basic package structure with:
# - inst/include/ for header files
# - src/ for C++ source code
# - LinkToRcpp in DESCRIPTION

For more advanced usage, the Rcpp attributes // [[Rcpp::depends()]] and // [[Rcpp::plugins()]] let you specify package dependencies and compiler plugins.

Wrapping up

Rcpp lets you bring C++ speed into your R workflow without rewriting everything. Start small: find a slow function in your code, rewrite just that part in C++ using Rcpp, and compare the performance.

The Rcpp package documentation at https://cran.r-project.org/web/packages/Rcpp/index.html has many more examples. The Rcpp Gallery at https://gallery.rcpp.org is especially useful for real-world recipes.

Give it a try with something simple first, then gradually tackle more complex problems as you get comfortable.

See also