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-ultra/skills/add-rcpp-integration" ~/.claude/skills/pjt222-agent-almanac-add-rcpp-integration-ec7b7a && rm -rf "$T"
manifest:
i18n/caveman-ultra/skills/add-rcpp-integration/SKILL.mdsource content
Add Rcpp Integration
Integrate C++ into R pkg via Rcpp → perf-critical ops.
Use When
- R fn too slow, profile confirms bottleneck
- Interface existing C/C++ libs
- Algos benefit compiled (loops, recursion)
- RcppArmadillo → linear algebra
In
- Required: Existing R pkg
- Required: R fn to replace/augment w/ C++
- Optional: External C++ lib
- Optional: RcppArmadillo? (default: plain Rcpp)
Do
Step 1: Rcpp Infra Setup
usethis::use_rcpp()
Does:
- Creates
dirsrc/ - Adds
→ LinkingTo + Imports in DESCRIPTIONRcpp - Creates
w/R/packagename-package.R
+@useDynLib@importFrom Rcpp sourceCpp - Updates
for compiled.gitignore
RcppArmadillo:
usethis::use_rcpp_armadillo()
→
src/ created, DESCRIPTION updated Rcpp LinkingTo + Imports, R/packagename-package.R has @useDynLib.
If err:
usethis::use_rcpp() fails → manually create src/, add LinkingTo: Rcpp + Imports: Rcpp, add #' @useDynLib packagename, .registration = TRUE + #' @importFrom Rcpp sourceCpp to pkg doc file.
Step 2: Write C++ Fn
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; }
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; }
→ C++ src at
src/my_function.cpp w/ valid // [[Rcpp::export]] + roxygen //' docs.
If err: Verify
#include <Rcpp.h> (or <RcppArmadillo.h>), export annotation own line directly above signature, return types map valid Rcpp.
Step 3: Generate RcppExports
Rcpp::compileAttributes() devtools::document()
→
R/RcppExports.R + src/RcppExports.cpp auto-generated.
If err: Check C++ syntax. Ensure
// [[Rcpp::export]] above each exported fn.
Step 4: Verify Compilation
devtools::load_all()
→ Pkg compiles + loads no err.
If err: Check compiler out. Common:
- Missing system headers → install dev libs
- Syntax err → compiler msgs point to line
- Missing
for RcppArmadilloRcpp::depends
Step 5: Tests for Compiled
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_)) })
→ Tests pass → C++ identical to R + edge cases (empty, NA) correct.
If err: NA fail → add explicit NA checks via
NumericVector::is_na(). Empty fail → guard clause zero-length at top.
Step 6: Cleanup Script
Create
src/Makevars:
PKG_CXXFLAGS = -O2
Create
cleanup in pkg root (CRAN):
#!/bin/sh rm -f src/*.o src/*.so src/*.dll
Make executable:
chmod +x cleanup
→
src/Makevars sets compiler flags, cleanup removes objects. Both at pkg root.
If err: Verify
cleanup has exec perms (chmod +x cleanup), Makevars tabs (not spaces) for Makefile rules.
Step 7: Update .Rbuildignore
Handle compiled artifacts:
^src/.*\.o$ ^src/.*\.so$ ^src/.*\.dll$
→
.Rbuildignore patterns prevent compiled objects in tarball, preserve src + Makevars.
If err:
devtools::check() → NOTEs about unexpected files in src/. Adjust patterns → exclude only .o, .so, .dll.
Check
-
compiles no warndevtools::load_all() - Compiled fn produces correct results
- Tests pass edge cases (NA, empty, large)
-
passes no compile warnR CMD check - RcppExports generated + committed
- Perf improvement via benchmarks
Traps
- Forget
: Must regen RcppExports after C++ changescompileAttributes() - Int overflow:
notdouble
for large numericsint - Memory mgmt: Rcpp auto-handles for Rcpp types; no manual
delete - NA handling: C++ doesn't know R's NA. Check
Rcpp::NumericVector::is_na() - Platform portability: Avoid platform-specific C++. Test Win, macOS, Linux.
- Missing
: Pkg doc must@useDynLib@useDynLib packagename, .registration = TRUE
→
— pkg setup before Rcppcreate-r-package
— testing compiled fnswrite-testthat-tests
— CI needs C++ toolchainsetup-github-actions-ci
— compiled pkgs need extra CRAN checkssubmit-to-cran