Tue, 30 Sep 2025

#051: A Neat Little Rcpp Trick

Welcome to post 51 in the R4 series.

A while back I realized I should really just post a little more as not all post have to be as deep and introspective as for example the recent-ish ‘two cultures’ post #49.

So this post is a neat little trick I (somewhat belatedly) realized somewhat recently. The context is the ongoing transition from (Rcpp)Armadillo 14.6.3 and earlier to (Rcpp)Armadillo 15.0.2 or later. (I need to write a bit more about that, but that may require a bit more time.) (And there are a total of seven (!!) issue tickets managing the transition with issue #475 being the main ‘parent’ issue, please see there for more details.)

In brief, the newer and current Armadillo no longer allows C++11 (which also means it no longer allowes suppression of deprecation warnings …). It so happens that around a decade ago packages were actively encouraged to move towards C++11 so many either set an explicit SystemRequirements: for it, or set CXX_STD=CXX11 in src/Makevars{.win}. CRAN has for some time now issued NOTEs asking for this to be removed, and more recently enforced this with actual deadlines. In RcppArmadillo I opted to accomodate old(er) packages (using this by-now anti-pattern) and flip to Armadillo 14.6.3 during a transition period. That is what the package does now: It gives you either Armadillo 14.6.3 in case C++11 was detected (or this legacy version was actively selected via a compile-time #define), or it uses Armadillo 15.0.2 or later.

So this means we can have either one of two versions, and may want to know which one we have. Armadillo carries its own version macros, as many libraries or projects do (R of course included). Many many years ago (git blame points to sixteen and twelve for a revision) we added the following helper function to the package (full source here, we show it here without the full roxygen2 comment header)

// [[Rcpp::export]]
Rcpp::IntegerVector armadillo_version(bool single) {

    // These are declared as constexpr in Armadillo which actually does not define them
    // They are also defined as macros in arma_version.hpp so we just use that
    const unsigned int major = ARMA_VERSION_MAJOR;
    const unsigned int minor = ARMA_VERSION_MINOR;
    const unsigned int patch = ARMA_VERSION_PATCH;

    if (single) {
        return Rcpp::wrap(10000 * major + 100 * minor + patch) ;
    } else {
        return Rcpp::IntegerVector::create(Rcpp::Named("major") = major,
                                           Rcpp::Named("minor") = minor,
                                           Rcpp::Named("patch") = patch);
    }
}

It either returns a (named) vector of the standard ‘major’, ‘minor’, ‘patch’ form of the common package versioning pattern, or a single integer which can used more easily in C(++) via preprocessor macros. And this being an Rcpp-using package, we can of course access either easily from R:

> library(RcppArmadillo)
> armadillo_version(FALSE)
major minor patch 
   15     0     2 
> armadillo_version(TRUE)
[1] 150002
>

Perfectly valid and truthful. But … cumbersome at the R level. So when preparing for these (Rcpp)Armadillo changes in one of my package, I realized I could alter such a function and set the S3 type to package_version. (Full version of one such variant here)

// [[Rcpp::export]]
Rcpp::List armadilloVersion() {
    // create a vector of major, minor, patch
    auto ver = Rcpp::IntegerVector::create(ARMA_VERSION_MAJOR, ARMA_VERSION_MINOR, ARMA_VERSION_PATCH);
    // and place it in a list (as e.g. packageVersion() in R returns)
    auto lst = Rcpp::List::create(ver);
    // and class it as 'package_version' accessing print() etc methods
    lst.attr("class") = Rcpp::CharacterVector::create("package_version", "numeric_version");
    return lst;
}

Three statements each to

  • create the integeer vector of known dimensions and compile-time known value
  • embed it in a list (as that is what the R type expects)
  • set the S3 class which is easy because Rcpp accesses attributes and create character vectors

and return the value. And now in R we can operate more easily on this (using three dots as I didn’t export it from this package):

> RcppDE:::armadilloVersion()
[1] ‘15.0.2
> RcppDE:::armadilloVersion() >= "15.0.0"
[1] TRUE
>

An object of class package_version inheriting from numeric_version can directly compare against a (human- but not normally machine-readable) string like “15.0.0” because the simple S3 class defines appropriate operators, as well as print() / format() methods as the first expression shows. It is these little things that make working with R so smooth, and we can easily (three statements !!) do so from Rcpp-based packages too.

The underlying object really is merely a list containing a vector:

> unclass(RcppDE:::armadilloVersion())
[[1]]
[1] 15  0  2

> 

but the S3 “glue” around it makes it behave nicely.

So next time you are working with an object you plan to return to R, consider classing it to take advantage of existing infrastructure (if it exists, of course). It’s easy enough to do, and may smoothen the experience at the R side.

This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.

/code/r4 | permanent link