25.3 Other classes

You’ve already seen the basic vector classes (IntegerVector, NumericVector, LogicalVector, CharacterVector) and their scalar (int, double, bool, String) equivalents. Rcpp also provides wrappers for all other base data types. The most important are for lists and data frames, functions, and attributes, as described below. Rcpp also provides classes for more types like Environment, DottedPair, Language, Symbol, etc, but these are beyond the scope of this chapter.

25.3.1 Lists and data frames

Rcpp also provides List and DataFrame classes, but they are more useful for output than input. This is because lists and data frames can contain arbitrary classes but C++ needs to know their classes in advance. If the list has known structure (e.g., it’s an S3 object), you can extract the components and manually convert them to their C++ equivalents with as(). For example, the object created by lm(), the function that fits a linear model, is a list whose components are always of the same type. The following code illustrates how you might extract the mean percentage error (mpe()) of a linear model. This isn’t a good example of when to use C++, because it’s so easily implemented in R, but it shows how to work with an important S3 class. Note the use of .inherits() and the stop() to check that the object really is a linear model.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
double mpe(List mod) {
  if (!mod.inherits("lm")) stop("Input must be a linear model");

  NumericVector resid = as<NumericVector>(mod["residuals"]);
  NumericVector fitted = as<NumericVector>(mod["fitted.values"]);

  int n = resid.size();
  double err = 0;
  for(int i = 0; i < n; ++i) {
    err += resid[i] / (fitted[i] + resid[i]);
  }
  return err / n;
}
mod <- lm(mpg ~ wt, data = mtcars)
mpe(mod)
#> [1] -0.0154

25.3.2 Functions

You can put R functions in an object of type Function. This makes calling an R function from C++ straightforward. The only challenge is that we don’t know what type of output the function will return, so we use the catchall type RObject.

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
RObject callWithOne(Function f) {
  return f(1);
}
callWithOne(function(x) x + 1)
#> [1] 2
callWithOne(paste)
#> [1] "1"

Calling R functions with positional arguments is obvious:

f("y", 1);

But you need a special syntax for named arguments:

f(_["x"] = "y", _["value"] = 1);

25.3.3 Attributes

All R objects have attributes, which can be queried and modified with .attr(). Rcpp also provides .names() as an alias for the name attribute. The following code snippet illustrates these methods. Note the use of ::create(), a class method. This allows you to create an R vector from C++ scalar values:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector attribs() {
  NumericVector out = NumericVector::create(1, 2, 3);

  out.names() = CharacterVector::create("a", "b", "c");
  out.attr("my-attr") = "my-value";
  out.attr("class") = "my-class";

  return out;
}

For S4 objects, .slot() plays a similar role to .attr().