在模板化 Rcpp 函数中调用另一个 cpp 函数
Calling another cpp function in templated Rcpp function
我正在尝试在 Rcpp 中创建某种 sapply 函数,其工作方式如下:
apply_cpp_fun(x, fun, ...)
函数有两个参数:任何类型的向量 x
和任何 cpp 函数 fun
,加上 fun
中所需的可选参数(例如 bool na_rm
)。在示例中,我保持简单,只是 x
和 fun
.
我希望 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
,模板也会将 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);
}
调用函数查看结果
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
如何解决此问题以保持应用程序对输入类型和输出的灵活性?
感谢您的任何建议。
以下实现 apply_cpp_fun
的方法使用的技巧是使用 decltype
捕获要应用的函数的输出类型,并使用 Rcpp::traits::r_sexptype_traits<T>::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 自己的答案中给出的示例。
The documentation 表示 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
所以。 是它专用于函数指针类型吗?就我所见,还不清楚特化会是什么 return:你似乎希望它成为 return 函数的 return 类型——但这不是这个元函数的作用。相反,它执行 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
之前已知输出,因此可以使用附加参数而不是从传入参数的函数继承类型。只需在模板参数列表的开头添加<int OTYPE, ...>
,然后从table.
中调用相应的SEXP类型编号apply_cpp_fun<SEXPTYPE_no>(...)
要应用的函数
#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
我正在尝试在 Rcpp 中创建某种 sapply 函数,其工作方式如下:
apply_cpp_fun(x, fun, ...)
函数有两个参数:任何类型的向量x
和任何 cpp 函数fun
,加上fun
中所需的可选参数(例如bool na_rm
)。在示例中,我保持简单,只是x
和fun
.我希望
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
,模板也会将 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);
}
调用函数查看结果
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
如何解决此问题以保持应用程序对输入类型和输出的灵活性? 感谢您的任何建议。
以下实现 apply_cpp_fun
的方法使用的技巧是使用 decltype
捕获要应用的函数的输出类型,并使用 Rcpp::traits::r_sexptype_traits<T>::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 自己的答案中给出的示例。
The documentation 表示 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
所以。 是它专用于函数指针类型吗?就我所见,还不清楚特化会是什么 return:你似乎希望它成为 return 函数的 return 类型——但这不是这个元函数的作用。相反,它执行 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
之前已知输出,因此可以使用附加参数而不是从传入参数的函数继承类型。只需在模板参数列表的开头添加<int OTYPE, ...>
,然后从table.
apply_cpp_fun<SEXPTYPE_no>(...)
要应用的函数
#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