2. Using Rcpp in an R package (1)
Intro to Rcpp.package.skeleton
and related tools
In this section we will learn how to build a basic R
package containing Rcpp
code. The material provided here relies mainly on the Rcpp-attributes and Rcpp-package vignettes.
The Rcpp
package provides an extension of the utils::package.skeleton
function, which automatically creates a source R
package which uses several features of Rcpp
. This is used as follows:
library(Rcpp)
Rcpp.package.skeleton("mypack")
Let’s examine the content of the automatically generated mypack
package:
system("ls -1R mypack/")
mypack:
DESCRIPTION
man
NAMESPACE
R
Read-and-delete-me
src
mypack/man:
mypack-package.Rd
rcpp_hello_world.Rd
mypack/R:
RcppExports.R
mypack/src:
RcppExports.cpp
rcpp_hello_world.cpp
You should be already familiar with the structure of an R
package, otherwise see here, for instance. Here we are particularly interested in the Rcpp
-related components of the package, hence let’s look at the C++
code in src/rcpp_hello_world.cpp
:
printFile <- function(o, n = 1e5) cat(readChar(o, n))
printFile("mypack/src/rcpp_hello_world.cpp")
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
List rcpp_hello_world() {
CharacterVector x = CharacterVector::create( "foo", "bar" ) ;
NumericVector y = NumericVector::create( 0.0, 1.0 ) ;
List z = List::create( x, y ) ;
return z ;
}
This is an example C++
function generated by Rcpp.package.skeleton
. We are not interested in what the function does (despite its name, it doesn’t say “hello”!), what matters to us is that the // [[Rcpp::export]]
attribute indicates that this function should be exported. That is, this is not an internal function, but it should be available to users of the mypack
package.
To make rcpp_hello_world
accessible at R
level, Rcpp.package.skeleton
calls compileAttributes
(we will see how to use this function later), which creates a C++
and R
wrapper for this function. The C++
wrapper can in found in:
printFile("mypack/src/RcppExports.cpp")
// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#include <Rcpp.h>
using namespace Rcpp;
#ifdef RCPP_USE_GLOBAL_ROSTREAM
Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();
Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
#endif
// rcpp_hello_world
List rcpp_hello_world();
RcppExport SEXP _mypack_rcpp_hello_world() {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
rcpp_result_gen = Rcpp::wrap(rcpp_hello_world());
return rcpp_result_gen;
END_RCPP
}
static const R_CallMethodDef CallEntries[] = {
{"_mypack_rcpp_hello_world", (DL_FUNC) &_mypack_rcpp_hello_world, 0},
{NULL, NULL, 0}
};
RcppExport void R_init_mypack(DllInfo *dll) {
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
Let’s examine the content of this file:
- the first two lines indicate that the file has been generated by
compileAttributes
. If we wanted to edit this file
manually, we would have to remove those lines first, otherwise our edits would be overwritten if we were to call
compileAttributes
again;
- then there is the definition of the
RcppExport SEXP _mypack_rcpp_hello_world()
. This is a standard Rcpp
wrapper for
rcpp_hello_world
, which makes it accessible from R
via .Call
(remember that the latter requires function inputs
and output of class SEXP
). Doing sourceCpp(rcpp_hello_world.cpp)
would generate a similar wrapper,
but with a different name.
- then
static const R_CallMethodDef CallEntries
creates an object which lists all the C++
function that will be
accessible from R
via .Call
. In our case, the only exported C++
function is _mypack_rcpp_hello_world
.
The CallEntries
object then appears in the definition of the R_init_mypack
function. The purpose of this
part of the code is registring the native routine (e.g., C++
functions) provided by the package. In particular,
the .Call
interface needs to find the code for the C++
function being called and registring such routines
via R_registerRoutines
facilitates the search process. For the purpose of this course, understanding how
registration works is not important, but you might want to read the
relevant section of
the Writing R Extensions manual if you want more details.
As we have just seen, RcppExports.cpp
contains the Rcpp
wrapper (callable via .Call
) to the rcpp_hello_world
C++
function. The corresponding R
wrapper is here:
printFile("mypack/R/RcppExports.R")
# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
rcpp_hello_world <- function() {
.Call(`_mypack_rcpp_hello_world`)
}
This is a straightfoward wrapper, analogous those generated via sourceCpp
.
Let’s look at the NAMESPACE
file:
printFile("mypack/NAMESPACE")
useDynLib(mypack, .registration=TRUE)
importFrom(Rcpp, evalCpp)
exportPattern("^[[:alpha:]]+")
The directive useDynLib(mypack, .registration=TRUE)
makes so that all the registered routines are loaded in R
. For example, after installing the package:
system("R CMD build mypack")
system("R CMD INSTALL mypack")
we can do:
mypack:::"_mypack_rcpp_hello_world"
$name
[1] "_mypack_rcpp_hello_world"
$address
<pointer: 0x563320e5b420>
attr(,"class")
[1] "RegisteredNativeSymbol"
$dll
DLL name: mypack
Filename: /home/runner/work/_temp/Library/mypack/libs/mypack.so
Dynamic lookup: FALSE
$numParameters
[1] 0
attr(,"class")
[1] "CallRoutine" "NativeSymbolInfo"
which gives information about the routine and we can call the C++
code directly via:
.Call("_mypack_rcpp_hello_world", PACKAGE = "mypack")
[[1]]
[1] "foo" "bar"
[[2]]
[1] 0 1
See here for more details on useDynLib
. The line exportPattern("^[[:alpha:]]+")
makes so that all the R
functions whose name starts with a letter of the alphabet will be exported. Then, importFrom(Rcpp, evalCpp)
is there because packages using Rcpp
need to import something (anything from Rcpp
). This requirement is related to a bug in R
, hence importing something from Rcpp
might not be needed in the future.
Finally, we look at the Description:
printFile("mypack/DESCRIPTION")
Package: mypack
Type: Package
Title: What the Package Does in One 'Title Case' Line
Version: 1.0
Date: 2023-02-06
Author: Your Name
Maintainer: Your Name <your@email.com>
Description: One paragraph description of what the package does as one
or more full sentences.
License: GPL (>= 2)
Imports: Rcpp (>= 1.0.10)
LinkingTo: Rcpp
This is fairly standard, the only thing to notice is that Rcpp
appear both in LinkingTo
and in Imports
. Once the above-mentioned bug will be solved, Rcpp
will need to appear only in LinkingTo
(unless we genuinely need to import R
functions from Rcpp
in our package). The latter is a declaration which allows R
to retrieve the header file of the target package (Rcpp
in this case) and to link them to the compiled code in our package. Hence, it cannot be used to link to a C++
library (e.g., the MKL
library) which is not contained in a R
package. Linking to external libraries must be done manually using makefiles and it is not covered here.
Adding a C++
function to the package
In the previous section we described the structure of a basic package created by Rcpp.package.skeleton
. Here we explain how to add further C++
functions to the package and how to export them. Suppose that we would like to add the following function to our mypack
package:
printFile("dotRcpp.cpp")
#include <Rcpp.h>
using namespace Rcpp;
//' Dot product in Rcpp.
//'
//' @param x1 numeric vector
//' @param x2 numeric vector
//' @return dot product, that is \code{t(x1)%*%x2}
// [[Rcpp::export(dotRcpp)]]
NumericVector dotRcpp_I(NumericVector x1, NumericVector x2)
{
NumericVector out(1);
out[0] = sum(x1 * x2);
return out;
}
This function simply calculates the dot product of two numeric vectors. Notice that the function definition is preceded by some comments starting with //' Dot product in Rcpp.
. These will be used by compileAttributes
to build the documentation of this function. To demostrate this, we must move the function to the folder containing the source code:
system("cp dotRcpp.cpp mypack/src/dotRcpp.cpp")
then we compile the Rcpp
attributes:
compileAttributes("mypack")
The R
wrapper to our C++
function can be found here:
printFile("mypack/R/RcppExports.R")
# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#' Dot product in Rcpp.
#'
#' @param x1 numeric vector
#' @param x2 numeric vector
#' @return dot product, that is \code{t(x1)%*%x2}
dotRcpp <- function(x1, x2) {
.Call(`_mypack_dotRcpp_I`, x1, x2)
}
rcpp_hello_world <- function() {
.Call(`_mypack_rcpp_hello_world`)
}
As you can see the dots dotRcpp
function is preceded by roxygen
comments, which can be used to produce the corresponding .Rd
files using roxygenize("mypack")
. dotRcpp
is a wrapper around the C++
function _mypack_dotRcpp_I
, which is itself a wrapper around the internal C++
function dotRcpp_I
, and it is defined here:
printFile("mypack/src/RcppExports.cpp")
// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#include <Rcpp.h>
using namespace Rcpp;
#ifdef RCPP_USE_GLOBAL_ROSTREAM
Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();
Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
#endif
// dotRcpp_I
NumericVector dotRcpp_I(NumericVector x1, NumericVector x2);
RcppExport SEXP _mypack_dotRcpp_I(SEXP x1SEXP, SEXP x2SEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< NumericVector >::type x1(x1SEXP);
Rcpp::traits::input_parameter< NumericVector >::type x2(x2SEXP);
rcpp_result_gen = Rcpp::wrap(dotRcpp_I(x1, x2));
return rcpp_result_gen;
END_RCPP
}
// rcpp_hello_world
List rcpp_hello_world();
RcppExport SEXP _mypack_rcpp_hello_world() {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
rcpp_result_gen = Rcpp::wrap(rcpp_hello_world());
return rcpp_result_gen;
END_RCPP
}
static const R_CallMethodDef CallEntries[] = {
{"_mypack_dotRcpp_I", (DL_FUNC) &_mypack_dotRcpp_I, 2},
{"_mypack_rcpp_hello_world", (DL_FUNC) &_mypack_rcpp_hello_world, 0},
{NULL, NULL, 0}
};
RcppExport void R_init_mypack(DllInfo *dll) {
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
The mechanisms is the same described in the previous section. Notice that the compileAttribute
will be called automatically when you build your package using Rstudio or devtools
.