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.
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>
{
…
}
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"
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With