Passing user-supplied C++ functions

Dirk Eddelbuettel — written Jan 21, 2013 — source

Baptiste asked on StackOverflow about letting users supply C++ functions for use with Armadillo / RcppArmadillo. This posts helps with an extended answer. There is nothing specific about Armadillo here, this would the same way with Eigen, the GSL or any other library a user wants to support (and provides his or her own as<>() and wrap() converters which we already have for Armadillo, Eigen and the GSL).

To set the stage, let us consider two simple functions of a vector

// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>

using namespace arma; 
using namespace Rcpp;

vec fun1_cpp(const vec& x) {	// a first function 
    vec y = x + x;
    return (y);
}

vec fun2_cpp(const vec& x) {	// and a second function
    vec y = 10*x;
    return (y);
}

These are pretty boring and standard functions, and we could simple switch between them via if/else statements. Where it gets interesting is via the SEXP wrapping offered by XPtr below.

But before we get there, let us do this one step at a time.

This typedef is important and just says that funcPtr will take a const reference to a vec and return a vector – just like our two functions above

typedef vec (*funcPtr)(const vec& x);

The following function takes a string argument, picks a function and returns it wrapped as an external pointer SEXP. We could return this to R as well.

// [[Rcpp::export]]
XPtr<funcPtr> putFunPtrInXPtr(std::string fstr) {
    if (fstr == "fun1")
        return(XPtr<funcPtr>(new funcPtr(&fun1_cpp)));
    else if (fstr == "fun2")
        return(XPtr<funcPtr>(new funcPtr(&fun2_cpp)));
    else
        return XPtr<funcPtr>(R_NilValue); // runtime error as NULL no XPtr
}

A simple test of this function follows. First a function using it:

// [[Rcpp::export]]
vec callViaString(const vec x, std::string funname) {
    XPtr<funcPtr> xpfun = putFunPtrInXPtr(funname);
    funcPtr fun = *xpfun;
    vec y = fun(x);
    return (y);
}

And then a call, showing access to both functions:

callViaString(1:3, "fun1")
     [,1]
[1,]    2
[2,]    4
[3,]    6
callViaString(1:3, "fun2")
     [,1]
[1,]   10
[2,]   20
[3,]   30

But more interestingly, we can also receive a function pointer via the SEXP wrapping:

fun <- putFunPtrInXPtr("fun1")

And use it in this function which no longer switches:

// [[Rcpp::export]]
vec callViaXPtr(const vec x, SEXP xpsexp) {
    XPtr<funcPtr> xpfun(xpsexp);
    funcPtr fun = *xpfun;
    vec y = fun(x);
    return (y);
}

As seen here:

callViaXPtr(1:4, fun)
     [,1]
[1,]    2
[2,]    4
[3,]    6
[4,]    8

This is a reasonably powerful and generic framework offered by Rcpp and sitting on top of R’s external pointers.

tags: armadillo 

Related Articles