Sharing C++ Functions Between R Packages

Packaging header-only C++ functions for use from another package

programming
cpp
Author

James Balamuta

Published

June 19, 2019

This post is a version of the rcpp-shared-cpp-functions README from the R&D Rcpp collection of compiled-code examples. The repository holds the full, runnable source.

This small example R package demonstrates how to expose a set of C++ functions defined in one R package so that they can be reused from another R package, using a C++ header-only library distributed through LinkingTo. It is one of the R&D Rcpp prototype packages, a collection of minimal examples for integrating compiled code with R.

The RcppHeaderSharing R package provides an example of providing a set of C++ functions in one R package to be used by other R packages. There are multiple approaches to facilitate such a framework. Within this project, the focus is on distributing C++ header files within one of the R packages by making a C++ header-only library through the extensive use of inline keyword.

Usage

To install the package, you must first have a compiler on your system that is compatible with R. For help on obtaining a compiler consult either macOS or Windows guides.

With a compiler in hand, one can then install the package from GitHub by:

# install.packages("remotes")
remotes::install_github("coatless-rd-rcpp/rcpp-shared-cpp-functions")
library("RcppHeaderSharing")

Implementation Details

This project focuses on showcasing how to package in R a C++ header-only library. By approaching sharing C++ functions in this manner, the complexity is reduced at the expense of compile time during development and initial installation. Complexity reduction arises from the library not needing to be initially compiled, packaged, and then installed on a local user’s computer.

Principally, header-only libraries combine both the function definition and implementation within the header files denoted by .h or .hpp. In comparison, traditional C++ implementations that rely upon .cpp and .hpp file pairing result in the header files (.hpp) containing only a minimal “bones” public-facing implementation while the meat of the implementation is done in .cpp.

.
├── DESCRIPTION                      # Package metadata
├── LICENSE                          # Code license
├── NAMESPACE                        # Function and dependency registration
├── R                                # R functions
   ├── RcppExports.R                # Autogenerated R to C++ bindings by Rcpp
   └── RcppHeaderSharing-package.R  # Package documentation
├── README.md
├── RcppHeaderSharing.Rproj
├── inst                             # C++ Headers
   └── include
       ├── RcppHeaderSharing.h      # Main header file
       ├── hello
       │   └── say_hello.h          # Individual header file with inline func
       └── rcpp_math
           └── add_numbers.h
├── man                              # Package Documentation
   ├── RcppHeaderSharing-package.Rd
   └── add_numbers.Rd
└── src                              # Compiled Code
    ├── Makevars                     # Set compilation flag to include headers
    ├── Makevars.win
    ├── RcppExports.cpp              # Autogenerated R Bindings
    └── export-inline-header.cpp     # Surface into R a C++ header function.

Headers

The shared C++ code is placed under inst/include/ so that, once the package is installed, the headers are copied into the installed package’s include/ directory, the location other packages reach through LinkingTo. The headers are organized around a single entry-point header, RcppHeaderSharing.h, that includes Rcpp.h once and then pulls in each individual header using relative "" includes:

#ifndef RcppHeaderSharing_RcppHeaderSharing_H
#define RcppHeaderSharing_RcppHeaderSharing_H

#include <Rcpp.h>

#include "hello/say_hello.h"
#include "rcpp_math/add_numbers.h"

#endif

Each header is wrapped in an inclusion guard named after the package and the header (RcppHeaderSharing_<name>_H) so it is processed only once, and each function is sandboxed inside a RcppHeaderSharing namespace so that consumers retrieve it with a namespaced call such as RcppHeaderSharing::add_numbers():

#ifndef RcppHeaderSharing_add_numbers_H
#define RcppHeaderSharing_add_numbers_H

namespace RcppHeaderSharing {

inline Rcpp::NumericVector add_numbers(Rcpp::NumericVector x, double y) {
  return x + y;
}

}

#endif

So the package can compile against its own headers, src/Makevars (and src/Makevars.win on Windows) add the include directory to the compiler flags:

PKG_CXXFLAGS=-I../inst/include/

Inline

Because the function definitions live entirely in the headers, those headers are #included into multiple translation units, both within this package and in any package that links against it. Repeating a function’s full definition across translation units would ordinarily violate the One Definition Rule and produce “multiple definition” errors at link time. Marking each function inline is what resolves this: it permits the identical definition to appear in every translation unit that includes the header and instructs the linker to collapse those copies into one. The inline keyword is therefore the mechanism that makes a header-only library possible.

// The `inline` keyword lets this definition be included in many translation
// units without a One Definition Rule violation (from inst/include/hello/say_hello.h).
namespace RcppHeaderSharing {

inline void say_hello() {
  Rcpp::Rcout << "hello!" << std::endl;
}

}

Inclusion in another R package

In the other R package, modify the DESCRIPTION file’s LinkingTo: field to include the package name.

LinkingTo: Rcpp, package_name
Imports:
    Rcpp (>= 0.12.11)

Within a C++ file in src/, then add:

#include <Rcpp.h>
#include <package_name.h>

Retrieve the function definitions with a namespaced function call, e.g.

package_name::function_name()

Reference Implementations

Samples of using making available functions via a C++ header-only library R package:

Source

The full package source is available on GitHub, and a rendered package site is published at rd-rcpp.thecoatlessprofessor.com/rcpp-shared-cpp-functions.