Agent-almanac add-rcpp-integration

install
source · Clone the upstream repo
git clone https://github.com/pjt222/agent-almanac
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/caveman-lite/skills/add-rcpp-integration" ~/.claude/skills/pjt222-agent-almanac-add-rcpp-integration && rm -rf "$T"
manifest: i18n/caveman-lite/skills/add-rcpp-integration/SKILL.md
source content

Add Rcpp Integration

Integrate C++ code into an R package using Rcpp for performance-critical operations.

When to Use

  • R function is too slow and profiling confirms a bottleneck
  • Need to interface with existing C/C++ libraries
  • Implementing algorithms that benefit from compiled code (loops, recursion)
  • Adding RcppArmadillo for linear algebra operations

Inputs

  • Required: Existing R package
  • Required: R function to replace or augment with C++
  • Optional: External C++ library to interface with
  • Optional: Whether to use RcppArmadillo (default: plain Rcpp)

Procedure

Step 1: Set Up Rcpp Infrastructure

usethis::use_rcpp()

This:

  • Creates
    src/
    directory
  • Adds
    Rcpp
    to LinkingTo and Imports in DESCRIPTION
  • Creates
    R/packagename-package.R
    with
    @useDynLib
    and
    @importFrom Rcpp sourceCpp
  • Updates
    .gitignore
    for compiled files

For RcppArmadillo:

usethis::use_rcpp_armadillo()

Got:

src/
directory created, DESCRIPTION updated with
Rcpp
in LinkingTo and Imports, and
R/packagename-package.R
contains
@useDynLib
directive.

If fail: If

usethis::use_rcpp()
fails, manually create
src/
, add
LinkingTo: Rcpp
and
Imports: Rcpp
to DESCRIPTION, and add
#' @useDynLib packagename, .registration = TRUE
and
#' @importFrom Rcpp sourceCpp
to the package-level documentation file.

Step 2: Write C++ Function

Create

src/my_function.cpp
:

#include <Rcpp.h>
using namespace Rcpp;

//' Compute cumulative sum efficiently
//'
//' @param x A numeric vector
//' @return A numeric vector of cumulative sums
//' @export
// [[Rcpp::export]]
NumericVector cumsum_cpp(NumericVector x) {
  int n = x.size();
  NumericVector out(n);
  out[0] = x[0];
  for (int i = 1; i < n; i++) {
    out[i] = out[i - 1] + x[i];
  }
  return out;
}

For RcppArmadillo:

#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]

//' Matrix multiplication using Armadillo
//'
//' @param A A numeric matrix
//' @param B A numeric matrix
//' @return The matrix product A * B
//' @export
// [[Rcpp::export]]
arma::mat mat_mult(const arma::mat& A, const arma::mat& B) {
  return A * B;
}

Got: C++ source file exists at

src/my_function.cpp
with valid
// [[Rcpp::export]]
annotation and roxygen-style
//'
documentation comments.

If fail: Verify the file uses

#include <Rcpp.h>
(or
<RcppArmadillo.h>
for Armadillo), that the export annotation is on its own line directly above the function signature, and that return types map to valid Rcpp types.

Step 3: Generate RcppExports

Rcpp::compileAttributes()
devtools::document()

Got:

R/RcppExports.R
and
src/RcppExports.cpp
generated automatically.

If fail: Check C++ syntax errors. Ensure

// [[Rcpp::export]]
tag is present above each exported function.

Step 4: Verify Compilation

devtools::load_all()

Got: Package compiles and loads without errors.

If fail: Check compiler output for errors. Common issues:

  • Missing system headers: Install development libraries
  • Syntax errors: C++ compiler messages point to the line
  • Missing
    Rcpp::depends
    attribute for RcppArmadillo

Step 5: Write Tests for Compiled Code

test_that("cumsum_cpp matches base R", {
  x <- c(1, 2, 3, 4, 5)
  expect_equal(cumsum_cpp(x), cumsum(x))
})

test_that("cumsum_cpp handles edge cases", {
  expect_equal(cumsum_cpp(numeric(0)), numeric(0))
  expect_equal(cumsum_cpp(c(NA_real_, 1)), c(NA_real_, NA_real_))
})

Got: Tests pass, confirming the C++ function produces identical results to the R equivalent and handles edge cases (empty vectors, NA values) correctly.

If fail: If tests fail on NA handling, add explicit NA checks in the C++ code using

NumericVector::is_na()
. If tests fail on empty input, add a guard clause for zero-length vectors at the top of the function.

Step 6: Add Cleanup Script

Create

src/Makevars
:

PKG_CXXFLAGS = -O2

Create

cleanup
in package root (for CRAN):

#!/bin/sh
rm -f src/*.o src/*.so src/*.dll

Make executable:

chmod +x cleanup

Got:

src/Makevars
sets compiler flags and
cleanup
script removes compiled objects. Both files exist at the package root level.

If fail: Verify

cleanup
has execute permissions (
chmod +x cleanup
) and that
Makevars
uses tabs (not spaces) for indentation if adding Makefile-style rules.

Step 7: Update .Rbuildignore

Ensure compiled artifacts are handled:

^src/.*\.o$
^src/.*\.so$
^src/.*\.dll$

Got:

.Rbuildignore
patterns prevent compiled object files from being included in the package tarball, while preserving source files and Makevars.

If fail: Run

devtools::check()
and look for NOTEs about unexpected files in
src/
. Adjust
.Rbuildignore
patterns to exclude only
.o
,
.so
, and
.dll
files.

Validation

  • devtools::load_all()
    compiles without warnings
  • Compiled function produces correct results
  • Tests pass for edge cases (NA, empty, large inputs)
  • R CMD check
    passes with no compilation warnings
  • RcppExports files are generated and committed
  • Performance improvement confirmed with benchmarks

Pitfalls

  • Forgetting
    compileAttributes()
    : Must regenerate RcppExports after changing C++ files
  • Integer overflow: Use
    double
    instead of
    int
    for large numeric values
  • Memory management: Rcpp handles memory automatically for Rcpp types; don't manually
    delete
  • NA handling: C++ doesn't know about R's NA. Check with
    Rcpp::NumericVector::is_na()
  • Platform portability: Avoid platform-specific C++ features. Test on Windows, macOS, and Linux.
  • Missing
    @useDynLib
    : The package-level doc must include
    @useDynLib packagename, .registration = TRUE

Related Skills

  • create-r-package
    - package setup before adding Rcpp
  • write-testthat-tests
    - testing compiled functions
  • setup-github-actions-ci
    - CI must have C++ toolchain
  • submit-to-cran
    - compiled packages need extra CRAN checks