简体   繁体   English

在模板化 Rcpp 函数中调用另一个 cpp 函数

[英]Calling another cpp function in templated Rcpp function

I'm trying to create some sort of sapply function in Rcpp, which works as follows:我正在尝试在 Rcpp 中创建某种 sapply 函数,其工作原理如下:

  • 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 ). apply_cpp_fun(x, fun, ...)函数接受两个参数:任何类型的向量x和任何 cpp 函数fun ,加上fun所需的可选参数(例如bool na_rm )。 In example I keep it simple, just x and fun .在示例中,我保持简单,只是xfun

  • I want fun to be applied on selected elements of x (possible fun outputs - bool, int, double, string).我希望将fun应用于x选定元素(可能的fun输出 - bool、int、double、string)。 I wan't apply to be called multiple times inside apply_cpp_fun .我不想申请在apply_cpp_fun内被多次apply_cpp_fun

  • Output of apply_cpp_fun is a vector of any type dependent on fun output (can be different than x ). apply_cpp_fun输出是依赖于fun输出的任何类型的向量(可以不同于x )。 fun is called n-times, to produce each element of output vector res . fun被称为 n 次,以产生输出向量res每个元素。

I'm trying to achieve this by but each time output turns to be a Rcpp::List instead of Rcpp::Vector<double> .我试图通过但每次输出变成Rcpp::List而不是Rcpp::Vector<double>来实现这一点。

Here is a code, I didn't write whole body of apply_cpp_fun to keep example shorter.这是一个代码,我没有写整个apply_cpp_fun来使示例更短。 As you can see, even if I pass <double>function , template gets Vector described as double (*)(Rcpp::Vector<14, Rcpp::PreserveStorage>) .如您所见,即使我通过<double>function ,模板也会将 Vector 描述为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 文档指出r_sexptype_traits是一个

template that returns the SEXP type that is appropriate for the type T, this is allways VECSXP (lists) unless it is specialized返回适合类型 T 的 SEXP 类型的模板,除非它是专门的,否则总是 VECSXP(列表)

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 ).相反,它执行 C++ 和 R SEXP 类型之间的映射(换句话说,来自SEXPTYPES的映射)。

As noted in a comment by Ralf, you need std::result_of or, if you're using C++17, std::invoke_result :正如 Ralf 在评论中指出的那样,您需要std::result_of或者,如果您使用的是 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 .以下实现apply_cpp_fun使用了使用decltype捕获要应用的函数的输出类型并使用Rcpp::traits::r_sexptype_traits<T>::rtype将其转换为适当的 SEXP 类型的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.如果这是作为constexpr int捕获的,那么它可以用作创建适当类型的Rcpp:Vector的模板参数。

The upside of doing it this way is that you don't need to pass any template parameters to apply_cpp_fun .这样做的好处是您不需要将任何模板参数传递给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:然后我们可以使用apply_cpp_fun应用它们并像这样导出到 R:

// [[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:现在在 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.尽管 OP 接受了我使用std::类型并简单地使用 Rcpp 的本机转换将它们传入和传出的原始答案,但@KonradRudolph 指出这涉及不必要的副本。 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.在 OP 进一步澄清和提出建议之后,我在 OP 的许可下更改了上述答案,并使用了 OP 自己的答案中给出的示例。

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.由于在调用apply_cpp_fun之前输出是已知的,因此可以使用附加参数而不是从传入参数的函数继承类型。 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 .一个只需要添加<int OTYPE, ...>在模板参数列表的开头,然后调用apply_cpp_fun<SEXPTYPE_no>(...)从相关SEXP型号码

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 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"

Solution is optimal in terms of performance as it doesn't cast any type nor copy object - same speed as no-templated Rcpp function.解决方案在性能方面是最佳的,因为它不强制转换任何类型或复制对象 - 与无模板 Rcpp 函数的速度相同。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM