【问题标题】:Calling another cpp function in templated Rcpp function在模板化 Rcpp 函数中调用另一个 cpp 函数
【发布时间】:2020-01-04 19:44:48
【问题描述】:

我正在尝试在 Rcpp 中创建某种 sapply 函数,其工作原理如下:

  • apply_cpp_fun(x, fun, ...) 函数有两个参数:任何类型的向量x,以及任何 cpp 函数fun,加上fun 中所需的可选参数(例如bool na_rm)。在示例中,我保持简单,只需 xfun

  • 我希望将 fun 应用于 x 的选定元素(可能的 fun 输出 - bool、int、double、string)。我不想申请在apply_cpp_fun 内被多次调用。

  • apply_cpp_fun 的输出是依赖于fun 输出的任何类型的向量(可以不同于x)。 fun 被调用 n 次,以产生输出向量 res 的每个元素。

我试图通过但每次输出变为Rcpp::List 而不是Rcpp::Vector<double> 来实现这一点。

这是一个代码,我没有写整个 apply_cpp_fun 以使示例更短。如您所见,即使我通过<double>function,模板也会得到描述为double (*)(Rcpp::Vector<14, Rcpp::PreserveStorage>) 的Vector。

  #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);
  }

调用函数查看结果

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


如何解决这个问题以保持应用程序对输入类型和输出的灵活性? 感谢您的建议。

【问题讨论】:

标签: c++ r rcpp


【解决方案1】:

The documentation 声明 r_sexptype_traits 是一个

返回适合类型 T 的 SEXP 类型的模板,除非它是专门的,否则这总是 VECSXP(列表)

所以。 它是专门用于函数指针类型的吗?就我所见,目前还不清楚特化会返回什么:您似乎希望它返回一个函数的返回类型——但这不是这个元函数所做的。相反,它执行 C++ 和 R SEXP 类型之间的映射(换句话说,来自SEXPTYPES table 的映射)。

正如 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>
{
    …
}

【讨论】:

  • 您的解决方案似乎最适合我的需求,但我在调用apply_cpp_fun 时遇到了问题,因为我收到了替换错误。我已将您的提议放入我的示例中,无法再编译 - my gist
【解决方案2】:

以下实现apply_cpp_fun 的方法使用decltype 捕获要应用的函数的输出类型并使用Rcpp::traits::r_sexptype_traits&lt;T&gt;::rtype 将其转换为适当的SEXP 类型的技巧。如果这被捕获为constexpr int,那么它可以用作模板参数来创建适当类型的Rcpp:Vector

这样做的好处是您不需要将任何模板参数传递给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;
}

假设我们要应用以下函数:

#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];
}

然后我们可以使用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);
}

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

注意

虽然 OP 接受了我使用 std:: 类型的原始答案,并使用 Rcpp 的本机转换简单地将它们传入和传出,但 @KonradRudolph 指出这涉及不必要的副本。经过OP的进一步澄清和建议,我在OP的许可下将我的答案更改为上述,并使用了OP自己的答案中给出的示例。

【讨论】:

  • 谢谢,在您的示例中,输出向量类型取自 x 但我需要它依赖于函数输出。可能存在输入是数字向量而输出是字符串向量(连接)的情况。
  • 在这种情况下,模板函数的第二个参数需要是指向函数的指针,该函数的类型是在编译时计算的。看看我的编辑,你想要什么。
  • 不错!这为我提供了我需要的解决方案的几乎完整路径。问题:如果可调用函数有多个参数怎么办?例如:std::string nth_string(std::vector&lt;double&gt; x, int i) return std::to_string(x[i]);U(*f)(std::vector&lt;T&gt;) 有单个参数时如何传递函数?
  • 您可以重载模板以接受另一个参数。已添加示例。
  • 这个代码不是强制(昂贵的)复制吗,OP 的代码没有,因为它不需要在 std::vectorRcpp::Vector 之间移动?
【解决方案3】:

其他解决方案是添加另一个指定输出类型的模板参数。由于在调用apply_cpp_fun 之前知道输出,因此可以使用附加参数而不是从传入参数的函数继承类型。只需在模板参数列表的开头添加&lt;int OTYPE, ...&gt;,然后从table 中调用apply_cpp_fun&lt;SEXPTYPE_no&gt;(...) 与相关的SEXP 类型编号。

要应用的功能

#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];
}

申请者


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;
}

导出的函数

// [[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 输出

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"

解决方案在性能方面是最佳的,因为它不强制转换任何类型或复制对象 - 与无模板 Rcpp 函数的速度相同。

要比较的功能

// [[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;
}

基准测试

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

【讨论】:

  • 过去几天这个问题一直让我抓狂。我最终找到了一种避免将模板参数传递给函数的方法。请参阅我更新的编辑,如果您从导出的函数中删除 模板参数,它将作为此答案中“应用程序”的替代替代品。经您许可,我想使用您自己的示例更新我的答案,以使其成为实际答案。
猜你喜欢
  • 2013-11-13
  • 2014-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
相关资源
最近更新 更多