Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling another cpp function in templated Rcpp function

Tags:

c++

r

rcpp

I'm trying to create some sort of sapply function in Rcpp, which works as follows:

  • apply_cpp_fun(x, fun, ...) function takes two arguments: vector of any type x, and any cpp function fun, plus optional arguments needed in fun (eg. bool na_rm). In example I keep it simple, just x and fun.

  • I want fun to be applied on selected elements of x (possible fun outputs - bool, int, double, string). I wan't apply to be called multiple times inside apply_cpp_fun.

  • Output of apply_cpp_fun is a vector of any type dependent on fun output (can be different than x). fun is called n-times, to produce each element of output vector res.

I'm trying to achieve this by but each time output turns to be a Rcpp::List instead of Rcpp::Vector<double>.

Here is a code, I didn't write whole body of apply_cpp_fun to keep example shorter. As you can see, even if I pass <double>function, template gets Vector described as double (*)(Rcpp::Vector<14, Rcpp::PreserveStorage>).

  #include <Rcpp.h>

  double cpp_sum(Rcpp::NumericVector x) {
    int n = x.size();
    double cursum = 0;

    for (int i = 0; i < n; i++) {
      cursum += x(i);
    }

     return cursum;
  }

  template <int ITYPE, typename ftype>
  Rcpp::Vector<Rcpp::traits::r_sexptype_traits<ftype>::rtype>
  apply_cpp_fun(Rcpp::Vector<ITYPE>& x,
                ftype fun) {

    int n = x.size();
    double xx = 5.0;

    # typenames
    Rcpp::Rcout << "type of xx: " << demangle(typeid(xx).name()).c_str() << std::endl;
    Rcpp::Rcout << "function type: " << demangle(typeid(ftype).name()).c_str() << std::endl;
    const int OTYPE = Rcpp::traits::r_sexptype_traits<ftype>::rtype;
    Rcpp::Rcout << "SEXP type: " << OTYPE << std::endl;

    # apply fun n-times
    Rcpp::Vector<OTYPE> res(n);
    for (int i = 0; i < n; i++) {
      res(i) = fun(x);
    }

    return res;  # return vector
  }

  // [[Rcpp::export]]
  SEXP cumsum_cpp(Rcpp::NumericVector x) {
    return apply_cpp_fun(x, cpp_sum);
  }

Call function to see result

cumsum_cpp(as.numeric(1:2))
# type of xx: double
# function type: double (*)(Rcpp::Vector<14, Rcpp::PreserveStorage>)
# SEXP type: 19
# [[1]]
# NULL
#
# [[2]]
# NULL


How to fix this to keep applier flexible for input type and output? Thanks for any advice.

like image 913
GoGonzo Avatar asked Jan 04 '20 19:01

GoGonzo


3 Answers

The documentation states that r_sexptype_traits is a

template that returns the SEXP type that is appropriate for the type T, this is allways VECSXP (lists) unless it is specialized

So. Is it specialised for function pointer types? Not as far as I can see, and it’s not clear what the specialisation would return: You seem to want it to return a function’s return type — but that’s not what this metafunction does. Rather, it performs a mapping between C++ and R SEXP types (in other words, the mapping from the SEXPTYPES table).

As noted in a comment by Ralf, you need std::result_of or, if you’re using C++17, std::invoke_result:

template <typename T>
using RcppVec = Rcpp::Vector<Rcpp::traits::r_sexptype_traits<T>::rtype>;

template <int ITYPE, typename FTYPE>
auto apply_cpp_fun(Rcpp::Vector<ITYPE> const& x, FTYPE fun) ->
    RcppVec<typename std::result_of<FTYPE>::type>
{
    …
}
like image 85
Konrad Rudolph Avatar answered Nov 17 '22 22:11

Konrad Rudolph


The following approach to implementing apply_cpp_fun uses the trick of capturing the output type of the function to be applied using decltype and converting it to the appropriate SEXP type with Rcpp::traits::r_sexptype_traits<T>::rtype. If this is captured as a constexpr int then it can be used as the template parameter for creating an Rcpp:Vector of the appropriate type.

The upside of doing it this way is that you don't need to pass any template parameters to apply_cpp_fun.

#include <Rcpp.h>

template<typename Func, typename Input>
SEXP apply_cpp_fun(Input& v, Func f)
{
  int n = v.size();
  constexpr int t = Rcpp::traits::r_sexptype_traits<decltype(f(v, n))>::rtype;
  Rcpp::Vector<t> result(n);

  for (int i = 0; i < n; i++) result(i) = f(v, i);
  return result;
}

Suppose we have the following functions to be applied:

#include <string>
#include <vector>
// [[Rcpp::plugins("cpp11")]]

Rcpp::String as_string(Rcpp::NumericVector const& x, int i) {
  return std::to_string(x[i]);
}

double as_numeric(Rcpp::NumericVector const& x, int i) {
  return x[i];
}

Then we can apply them using apply_cpp_fun and export to R like this:

// [[Rcpp::export]]
Rcpp::NumericVector test1_tmpl(Rcpp::NumericVector x) 
{
  return apply_cpp_fun(x, as_numeric);
}

// [[Rcpp::export]]
Rcpp::StringVector test2_tmpl(Rcpp::NumericVector x) 
{
  return apply_cpp_fun(x, as_string);
}

Now in R:

test1_tmpl(1:5)
# [1] 1 2 3 4 5

test2_tmpl(1:5)
# [1] "1.000000" "2.000000" "3.000000" "4.000000" "5.000000"

Note

Although the OP accepted my original answer of working with std:: types and simply passing them in and out using Rcpp's native conversions, it was pointed out by @KonradRudolph that this involves unnecessary copies. After some further clarifications and suggestions by the OP, I changed my answer to the above with the OP's permission, and have used the examples given in the OP's own answer.

like image 1
Allan Cameron Avatar answered Nov 17 '22 22:11

Allan Cameron


Other solution is to add another template parameter specifying the output type. As output is known before calling apply_cpp_fun, one can use additional parameter instead of inherit type from the function passed in argument. One needs only to add <int OTYPE, ...> at the beginning of template parameter list, and then call apply_cpp_fun<SEXPTYPE_no>(...) with relevant SEXP-type number from the table.

Functions to be applied

#include <Rcpp.h>
#include <string>
#include <vector>
// [[Rcpp::plugins("cpp11")]]

Rcpp::String as_string(Rcpp::NumericVector const& x, int i) {
  return std::to_string(x[i]);
}

double as_numeric(Rcpp::NumericVector const& x, int i) {
  return x[i];
}

Applier


template <int OTYPE, int ITYPE, typename FTYPE>
Rcpp::Vector<OTYPE> apply_cpp_fun(Rcpp::Vector<ITYPE> const& x, FTYPE fun) {
  int n = x.size();
  Rcpp::Vector<OTYPE>   res(n);

  for (int i = 0; i < n; i++) {
    res[i] = fun(x, i);
  }

  return res;
}

Exported functions

// [[Rcpp::export]]
Rcpp::NumericVector test1_tmpl(Rcpp::NumericVector x) {
  return apply_cpp_fun<14>(x, as_numeric);
}

// [[Rcpp::export]]
Rcpp::StringVector test2_tmpl(Rcpp::NumericVector x) {
  return apply_cpp_fun<16>(x, as_string);
}

R output

test1_tmpl(1:5)
# [1] 1 2 3 4 5

test2_tmpl(1:5)
# [1] "1.000000" "2.000000" "3.000000" "4.000000" "5.000000"

Solution is optimal in terms of performance as it doesn't cast any type nor copy object - same speed as no-templated Rcpp function.

Function to be compared

// [[Rcpp::export]]
Rcpp::StringVector test2(Rcpp::NumericVector x) {
  int n = x.size();
  Rcpp::StringVector   res(n);

  for (int i = 0; i < n; i++) {
    res[i] = as_string(x, i);
  }

  return res;
}

Benchmark

x <- runif(10000)
microbenchmark::microbenchmark(
  test2_tmpl(x),
  test2(x),
  times = 1000L
)

# Unit: milliseconds
#           expr      min       lq     mean   median       uq      max neval
#  test2_tmpl(x) 3.456110 3.620221 4.367001 3.870608 4.469028 34.37925  1000
#       test2(x) 3.439571 3.617877 4.313639 3.851150 4.302168 77.42430  1000
like image 1
GoGonzo Avatar answered Nov 18 '22 00:11

GoGonzo