R Package Integration with Modern Reusable C++ Code Using Rcpp - Part 3

by Daniel Hanson

Daniel Hanson is a full-time lecturer in the Computational Finance & Risk Management program within the Department of Applied Mathematics at the University of Washington.

In the previous post in this series, we looked at some design considerations when integrating standard and reusable C++ code into an R package. Specific emphasis was on Rcpp’s role in facilitating a means of communication between R and the C++ code, particularly highlighting a few of the C++ functions in the Rcpp namespace that conveniently and efficiently pass data between an R numeric vector and a C++ std::vector<double> object.

Today, we will look at a specific example of implementing the interface. We will see how to configure code that allows the use of standard reusable C++ in an R package, without having to modify it with any R or Rcpp-specific syntax. It is admittedly a simple and toy example, but the goal is to provide a starting point that can be easily extended for more real world examples.

The Code

To get started, let’s have a look at the code we will use for our demonstration. It is broken into three categories, consistent with the design considerations from the previous post:

  • Standard and reusable C++: No dependence on R or Rcpp
  • Interface level C++: Uses functions in the Rcpp namespace
  • R functions exported by the interface level: Same names as in the interface level

Standard and Reusable C++ Code

In this example, we will use a small set of C++ non-member functions, and two classes. There is a declaration (header) file for the non-member functions, say NonmemberCppFcns.h, and another with class declarations for two shape classes, Square and Circle, called ConcreteShapes.h. Each of these is accompanied by a corresponding implementation file, with file extension .cpp, as one might expect in a more realistic C++ code base.

Nonmember C++ Functions

These functions are shown and described here, as declared in the following C++ header file:

#include <vector>
// Adds two real numbers
double add(double x, double y);
// Sorts a vector of real numbers and returns it
std::vector<double> sortVec(std::vector<double> v);
// Computes the product of the LCM and GCD of two integers, 
// using C++17 functions std::lcm(.) and std::gcd(.)
int prodLcmGcd(int m, int n);

The last function uses recently added features in the C++ Standard Library, to show that we can use C++17.

C++ Classes

The two classes in our reusable code base are declared in the ConcreteShapes.h file, as shown and described here. Much like textbook C++ examples, we’ll write classes for two geometric shapes, each with a member function to compute the area of its corresponding object.

#include <cmath>
class Circle
{
public:
  Circle(double radius);
  
  // Computes the area of a circle with given radius
  double area() const;
  
private:
  double radius_;
};
class Square
{
public:
  Square(double side);
  
  // Computes the area of a square with given side length
  double area() const;
private:
  double side_;
};

Interface Level C++

Now, the next step is to employ Rcpp, namely for the following essential tasks:

  • Export the interface functions to R
  • Facilitate data exchange between R and C++ container objects

An interface file containing functions designated for export to R does not require a header file with declarations; one can think of it as being analogous to a .cpp file that contains the main() function in a C++ executable project. In addition, the interface can be contained in one file, or split into multiple files. For demonstration, I have written two such files: CppInterface.cpp, which provides the interface to the non-member functions above, and CppInterface2.cpp, which does the same for the two C++ classes.

Interface to Non-Member C++ Functions:

Let’s first have a look the CppInterface.cpp interface file, which connects R with the nonmember functions in our C++ code base:

#include "NonmemberCppFcns.h"
#include <vector>
#include <Rcpp.h>     
// Nonmember Function Interfaces:
// [[Rcpp::export]]
int rAdd(double x, double y)
{
  // Call the add(.) function in the reusable C++ code base:
  return add(x, y);
}
// [[Rcpp::export]]
Rcpp::NumericVector rSortVec(Rcpp::NumericVector v)
{
  // Transfer data from NumericVector to std::vector<double>
  auto stlVec = Rcpp::as<std::vector<double>>(v); 
  
  // Call the reusable sortVec(.) function, with the expected
  // std::vector<double> argument:
  stlVec = sortVec(stlVec);
  
  // Reassign the results from the vector<double> return object
  // to the same NumericVector v, using Rcpp::wrap(.):
  v = Rcpp::wrap(stlVec);
  
  // Return as an Rcpp::NumericVector:
  return v;
}
// C++17 example:
// [[Rcpp::export]]
int rProdLcmGcd(int m, int n)
{
  return prodLcmGcd(m, n);
}
Included Declarations:

The NonmemberCppFcns.h declaration file is included at the top with #include, just as it would in a standalone C++ application, so that the interface will recognize these functions that reside in the reusable code base. The STL vector declaration is required, as we shall soon see. And, the key in making the interface work resides in the Rcpp.h file, which provides access to very useful C++ functions in the Rcpp namespace.

Function implementations:

Each of these functions is designated for export to R when the package is built, by placing the // [[Rcpp::export]] tag just above the each function signature, as shown above. In this particular example, each interface function simply calls a function in the reusable code base. For example, the rAdd(.) function simply calls the add(.) function in the reusable C++ code. In the absence of a user-defined namespace, the interface function name must be different from the function it calls to prevent name clash errors during the build, so I have simply chosen to prefix an r to the name of each interface function.

Note that the rSort(.) function takes in an Rcpp::NumericVector object, v. This type will accept data passed in from R as a numeric vector and present it as a C++ object. Then, so that we can call a function in our code base, such as sort(.), which expects a std::vector<double> type as its input, Rcpp also provides the Rcpp::as<.>(.) function that facilitates the transfer of data from an Rcpp::NumericVector object to the STL container:

auto stlVec = Rcpp::as<std::vector<double>>(v);

Rcpp also gives us a function that will transfer data from a std::vector<double> type being returned from our reusable C++ code back into an Rcpp::NumericVector, so that the results can be passed back to R as a familiar numeric vector type:

v = Rcpp::wrap(stlVec);

As the std::vector<double> object is the workhorse C++ STL containers in quantitative work, these two Rcpp functions are a godsend.

Remark 1: There is no rule that says an interface function can only call a single function in the reusable code; one can use whichever functions or classes that are needed to get the job done, just like with any other C++ function. I’ve merely kept it simple here for demonstration purposes.

Remark 2: The tag // [[Rcpp::plugins(cpp17)]] is sometimes placed at the top of a C++ source file in online examples related to Rcpp and C++17. I have not found this necessary in my own code, however, as long as the Makeconf file has been updated for C++17, as described in the first post in this series.

Interface to C++ Classes:

We now turn our attention to the second interface file, CppInterface2.cpp, which connects R with the C++ classes in our reusable code. It is shown here:

#include "ConcreteShapes.h"
// Class Member Function Interfaces:
// Interface to Square member
// function area(.):
// [[Rcpp::export]]
double squareArea(double side)
{
  Square sq(side);
  return sq.area();
}
// Interface to Circle member
// function area(.):
// [[Rcpp::export]]
double circleArea(double radius)
{
  Circle circ(radius);
  return circ.area();
}

This is again nothing terribly sophisticated, but the good news is it shows the process of creating instances of classes from the code base is not difficult at all. We first #include only the header file containing these class declarations; Rcpp.h is not required here, as we are not using any functions in the the Rcpp namespace.

To compute the area of a square, the side length is input in R as a simple numeric type and passed to the interface function as a C++ double. The Square object, sq, is constructed with the side argument, and its area() member function performs said calculation and returns the result. The process is trivally similar for the circleArea(.) function.

R Functions Exported by the Interface Level

To wrap up this discussion, let’s look at the functions an R user will have available after we build the package in RStudio (coming next in this series). Each of these functions will be exported from their respective C++ interface functions as regular R functions, namely:

  • rAdd(.)
  • rSortVec(.)
  • rProdLcmGcd(.)
  • squareArea(.)
  • circleArea(.)

The package user will not need to know or care that the core calculations are being performed in C++. Visually, we can represent the associations as shown in the following diagram:

Mapping R Package Functions to Reusable C++

The solid red line represents a “Chinese Wall” that separates our code base from the interface and allows us to maintain it as standard and reusable C++.

Summary

This concludes our example of configuring code that allows the use of standard reusable C++ in an R package, without having to modify it with any R or Rcpp-specific syntax. In the next post, we will examine how to actually build this code into an R package by leveraging the convenience of Rcpp and RStudio, and deploy it for any number of R users. The source code will also be made available so that you can try it out for yourself.

Share Comments · · ·

You may leave a comment below or discuss the post in the forum community.rstudio.com.