Contents

Exposing a C++ Student Class With Rcpp

Intro

The purpose of this tutorial is to document my notes as I experiment with exposing a simple C++ Student class to R using Rcpp. It’s more or less part two of a series of articles on Rcpp. (See part one here).

Setup

In part one of my tutorial on using Rcpp, we discussed how to use Rcpp’s sourceCpp() function to compile a C++ file with simple C++ functions. My next question was “How can I use Rcpp to expose more complicated C++ code that uses multiple files including header files?” For example, a simple C++ Student class might be made up of a Student.hpp header file that declares the class and a Student.cpp file that implements the class, like so

//  Student.hpp

#ifndef Student_hpp
#define Student_hpp

#include <string>
#include <vector>

class Student
{
public:

    // Constructor
    Student(std::string name, int age, bool male);

    // Getters
    std::string GetName();
    int GetAge();
    bool IsMale();
    std::vector<int> GetFavoriteNumbers();

    // Methods
    bool LikesBlue();

private:

    // Member variables
    std::string name;
    int age;
    bool male;
    std::vector<int> favoriteNumbers;
};

#endif /* Student_hpp */
//  Student.cpp

#include "Student.hpp"

// Constructor
Student::Student(std::string name, int age, bool male) {
  this->name = name;
  this->age = age;
  this->male = male;
  this->favoriteNumbers = {2, 3, 5, 7, 11};
}

// Getters
bool Student::IsMale() { return male; }
int Student::GetAge() { return age; }
std::string Student::GetName() { return name; }
std::vector<int> Student::GetFavoriteNumbers() { return favoriteNumbers; }

// Methods
bool Student::LikesBlue() {
    return (male || age >= 10);
}

Based on this StackOverflow question and answer, the easiest solution is to create our own R package in order to use our own header files.

1. Create an R Package

Hadley Wickham’s free and open source book, R packages, is a great reference for building R packages. It even includes a section on using compiled code. The following steps are derived from his book.

  1. In RStudio, File > New Project > New Directory > R Package
  2. Set the package name to whatever you want. I called mine student
  3. To set up our package with Rcpp, we need to install.packages("usethis") and then run usethis::use_rcpp().
    1. This should automatically create a directory named src within the root of our package. This is where we’ll dump our C++ code.
    2. This also prompts us to copy some code into an R file of our package. I recommend dumping this code into an R file with the same name as your package. You can put your package documentation there too. For me, this looks like
# student.R

#' student
#'
#' My cool package
#'
#' Imports
#' @useDynLib student, .registration = TRUE
#' @importFrom Rcpp sourceCpp
"_PACKAGE"
  1. We need to tell R that we want to use a C++11 compiler (or C++14, C++17, …). We can do this in two different ways:
    1. Run Sys.setenv("PKG_CXXFLAGS"="-std=c++11"). Note that this only lasts for the duration of your session, so you’ll have to rerun this everytime you start a fresh session.
    2. Create a file named Makevars (no extension) inside src/ like the following
# Makevars
PKG_CXXFLAGS=-std=c++11

2. Setup C++ files

  1. First add our Student.hpp and Student.cpp files to the src/ directory
  2. Now we need to write some Rcpp “glue” code to expose our Student class to R. Start by creating a glue.cpp file in the src/ directory like the following
// glue.cpp
// To use c++11, first run: Sys.setenv("PKG_CXXFLAGS"="-std=c++11")  ...or use a Makevars file

#include <Rcpp.h>
#include "Student.hpp"
using namespace Rcpp;

//' Simulate a student
//'
//' @export
// [[Rcpp::export]]
std::vector<int> simulate_student() {
  Student s = Student("bob", 10, true);
  return s.GetFavoriteNumbers();
}

Here, we make a function simulate_student() that creates a Student object and then returns the Student’s favorite numbers as an integer vector (that Rcpp will automatically convert to a numeric vector in R). We place the // [[Rcpp::export]] tag to make simulate_student() callable from R.

3. Build and Test

  1. Run pkgbuild::compile_dll() to compile your C++ code
  2. Run devtools::document() to automatically build the NAMESPACE file package documentation. For me, this ends up looking like
# Generated by roxygen2: do not edit by hand

importFrom(Rcpp,sourceCpp)
useDynLib(student, .registration = TRUE)

Before we expose the Student class to R, let’s test the simulate_student() function.

  1. Run devtools::load_all() to compile the C++ code and load our package
  2. Run simulate_student() in the conosle
> simulate_student()
[1]  2  3  5  7 11

4. Expose the Student class to R

Now we’d like to expose our Student class to R so that we can make and play with Student objects. Rcpp modules makes this relatively painless.

  1. First we set up an RCPP_MODULE within our glue.cpp file. We’ll expose the constructor (required) and the LikesBlue() method to R.
// glue.cpp

#include <Rcpp.h>
#include "Student.hpp"
using namespace Rcpp;

// Expose (some of) the Student class
RCPP_MODULE(Student){
  class_<Student>("Student")
  .constructor<std::string,int,bool>()
  .method("LikesBlue", &Student::LikesBlue);
}
  1. Next we need to insert the snippet Rcpp::loadModule(module = "Student", what = TRUE) into any R file in order to load the module. I’ll put this in student.R. While we’re there, we also need to change @importFrom Rcpp sourceCpp to @importFrom Rcpp and add @export Student. (These things affect the way roxygen generates the NAMESPACE file.)
# student.R

#' student
#'
#' My cool package
#'
#' Imports
#' @useDynLib student, .registration = TRUE
#' @export Student
#' @import Rcpp
"_PACKAGE"

Rcpp::loadModule(module = "Student", TRUE)
  1. Update the NAMESPACE and documentation with devtools::document(). Compile and load the package with devtools::load_all()
  2. Test it in the console
> stud <- new(Student, name = "Ben", age = 26, male = T)
    
> stud
C++ object <0x107a0f770> of class 'Student' <0x101b66bc0>
  
> str(stud)
Reference class 'Rcpp_Student' [package "student"] with 0 fields
 list()
 and 17 methods, of which 3 are  possibly relevant:
   finalize, initialize, LikesBlue
 
> stud$LikesBlue()
[1] TRUE