如何处理可能具有多种类型之一的 Rcpp::XPtr [英] How to deal with an Rcpp::XPtr that may have one of several types
问题描述
我的情况是我有一个 Rcpp::XPtr
到犰狳对象(例如 arma::Mat
,它可能是其中一个的矩阵支持的数据类型).现在我想编写一个查询元素数量的函数.到目前为止我能想到的最好的是以下内容(灵感来自 bigstatsr):
#define DISPATCH_DATA_TYPE(CALL) \{ \开关(数据类型)\{ \情况 1: CALL(unsigned short) \情况 2: CALL(unsigned int) \情况 3:CALL(unsigned long) \情况 4:CALL(短)\案例 5: CALL(int) \情况 6:CALL(long) \情况 7: CALL(float) \案例 8: CALL(double) \默认值:throw Rcpp::exception("不支持的数据类型.");\} \}模板 arma::uword mat_length(性别垫){Rcpp::XPtrn_elem;}#define MAT_LENGTH(TYPE) return mat_length(mat);//[[Rcpp::export]]arma::uword mat_length(SEXP mat, int data_type){DISPATCH_DATA_TYPE(MAT_LENGTH)}
有没有更好的方法来做到这一点?我在相当多的函数中使用了这种模式,冗长的问题正在成为一个问题.理想情况下,我有一个单一但简洁的功能,例如(当然不起作用)
arma::uword mat_length(SEXP mat){Rcpp::XPtrp(垫);返回 p->n_elem;}
而不是两个函数 + 一个宏,用于每个实例,其中我将 XPtr
像这样从 R 传递到 C.
额外的问题:基于宏的方法有什么明显的错误吗?这在某种程度上是低效的还是可能导致问题?
要创建可重现的示例,请添加
//[[Rcpp::depends(RcppArmadillo)]]#include //[[Rcpp::export]]SEXP setup_mat(arma::uword n_rows, arma::uword n_cols){arma::mat* res = new arma::mat(n_rows, n_cols);返回 Rcpp::XPtr<arma::mat>(res);}
并对 R 中的文件运行 Rcpp::sourceCpp()
.
目前我能想到的最好的非宏方法(使用 boost::mp11
)如下:>
关键部分:
- 定义我的类型集的类型列表(
mp11::mp_list
,称为types
) - helper 元函数
num_type_from_i
和i_form_num_type
用于在给定索引/给定类型的索引的情况下查询类型 - 模板结构
dispatch_impl
,递归使用,提供对类型列表的迭代 - 用于终止递归的
dispatch_impl
的专用版本 - 一个方便的函数
dispatch_type()
调用dispatch_impl
并定义列表长度/最大递归深度 - 示例函数对象
MatInit
和Length
以及它们的 R 接口mat_init()
和length()
立>
//[[Rcpp::depends(RcppArmadillo)]]//[[Rcpp::plugins(cpp11)]]#include #include #include 命名空间 mp11 = boost::mp11;使用类型 = mp11::mp_list;模板 <std::size_t I>使用 num_type_from_i = mp11::mp_at_c;模板 使用 i_form_num_type = mp11::mp_find;模板 <typename T, std::size_t N>结构 dispatch_impl{模板<std::size_t K, 模板<typename>类 Fn,类型名 ...Ar>静态自动调用(std::size_t i, Ar&&... rg) ->decltype(Fn>()(std::forward(rg)...)){如果(我== 0){返回 Fn()(std::forward(rg)...);}别的{return dispatch_impl::template call(i - 1,std::forward(rg)...);}}};模板 struct dispatch_impl{模板<std::size_t K, 模板<typename>类 Fn,类型名 ...Ar>静态自动调用(std::size_t i, Ar&&... rg) ->decltype(Fn>()(std::forward(rg)...)){如果(我== 0){返回 Fn()(std::forward(rg)...);}别的{throw std::runtime_error("不支持的数据类型.");}}};模板<模板<类型名>类 Fn,类型名 ...Ar>auto dispatch_type(std::size_t type, Ar&&... rg) ->decltype(Fn()(std::forward(rg)...)){使用 n_types = mp11::mp_size;返回 dispatch_impl::template call<0,Fn>(type, std::forward(rg)...);}模板 结构体初始化{SEXP operator()(arma::uword n_rows, arma::uword n_cols){auto res = new arma::Mat(n_rows, n_cols);auto ind = std::size_t{i_form_num_type::value};return Rcpp::XPtr(res, true, Rcpp::wrap(ind));}};//[[Rcpp::export]]SEXP mat_init(arma::uword n_rows, arma::uword n_cols, std::size_t data_type){return dispatch_type(data_type, n_rows, n_cols);}模板 结构长度{arma::uword operator()(SEXP x){返回 Rcpp::XPtr(x)->n_elem;}};//[[Rcpp::export]]arma::uword 长度(性别 x){std::size_t type = Rcpp::as<std::size_t>(R_ExternalPtrTag(x));return dispatch_type(type, x);}
通过这种方式可以轻松修改类型列表,除了需要模板化的函数对象而不是函数模板之外,诸如 length()
之类的函数的实现相当简洁.
此外,我不必在 R 和 C 之间传递数据类型索引,而是可以将索引存储在外部指针结构中.
如果有人发现潜在问题,我很乐意听取他们的意见.
I'm in the situation where I have an Rcpp::XPtr
to an Armadillo object (e.g. arma::Mat
, which may be a matrix of one of the supported data types). Now I'd like to write a function that queries the number of elements. The best I could come up with so far is the following (inspired by bigstatsr):
#define DISPATCH_DATA_TYPE(CALL) \
{ \
switch (data_type) \
{ \
case 1: CALL(unsigned short) \
case 2: CALL(unsigned int) \
case 3: CALL(unsigned long) \
case 4: CALL(short) \
case 5: CALL(int) \
case 6: CALL(long) \
case 7: CALL(float) \
case 8: CALL(double) \
default: throw Rcpp::exception("Unsupported data type."); \
} \
}
template <typename T>
arma::uword mat_length(SEXP mat)
{
Rcpp::XPtr< arma::Mat<T> > p(mat);
return p->n_elem;
}
#define MAT_LENGTH(TYPE) return mat_length<TYPE>(mat);
// [[Rcpp::export]]
arma::uword mat_length(SEXP mat, int data_type)
{
DISPATCH_DATA_TYPE(MAT_LENGTH)
}
Is there a better way of doing this? I'm using this pattern for quite a few functions and the verbosity is becoming a problem. Ideally I'd have a single but concise function, like (doesn't work of course)
arma::uword mat_length(SEXP mat)
{
Rcpp::XPtr<arma::Mat> p(mat);
return p->n_elem;
}
instead of two functions + a macro for every single instance where I pass an XPtr
like that from R to C.
Bonus question: is there anything obviously wrong with the macro-based approach? Is this somehow inefficient or could lead to problems down the line?
To create a reproducible example, add
// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>
// [[Rcpp::export]]
SEXP setup_mat(arma::uword n_rows, arma::uword n_cols)
{
arma::mat* res = new arma::mat(n_rows, n_cols);
return Rcpp::XPtr<arma::mat>(res);
}
and run Rcpp::sourceCpp()
on the file in R.
The best non-macro approach I could come up with so far (using boost::mp11
) is the following:
Key parts:
- a type list (
mp11::mp_list
, calledtypes
) defining my set of types - helper metafunctions
num_type_from_i
andi_form_num_type
to query type given an index/index given a type - a templated struct
dispatch_impl
, used recursively, providing iteration over the type list - a specialized version of
dispatch_impl
for terminating the recursion - a convenience function
dispatch_type()
callingdispatch_impl
and defining the list length/max recursion depth - example function objects
MatInit
andLength
alongside their R interfacesmat_init()
andlength()
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::plugins(cpp11)]]
#include <RcppArmadillo.h>
#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>
namespace mp11 = boost::mp11;
using types = mp11::mp_list<int, float, double>;
template <std::size_t I>
using num_type_from_i = mp11::mp_at_c<types, I>;
template <typename T>
using i_form_num_type = mp11::mp_find<types, T>;
template <typename T, std::size_t N> struct dispatch_impl
{
template <std::size_t K, template<typename> class Fn, typename ...Ar>
static auto call(std::size_t i, Ar&&... rg) ->
decltype(Fn<mp11::mp_at_c<T, 0>>()(std::forward<Ar>(rg)...))
{
if (i == 0)
{
return Fn<mp11::mp_at_c<T, K>>()(std::forward<Ar>(rg)...);
}
else
{
return dispatch_impl<T, N - 1>::template call<K + 1, Fn>(i - 1,
std::forward<Ar>(rg)...);
}
}
};
template <typename T> struct dispatch_impl<T, 1>
{
template <std::size_t K, template<typename> class Fn, typename ...Ar>
static auto call(std::size_t i, Ar&&... rg) ->
decltype(Fn<mp11::mp_at_c<T, 0>>()(std::forward<Ar>(rg)...))
{
if (i == 0)
{
return Fn<mp11::mp_at_c<T, K>>()(std::forward<Ar>(rg)...);
}
else
{
throw std::runtime_error("Unsupported data type.");
}
}
};
template <template<typename> class Fn, typename ...Ar>
auto dispatch_type(std::size_t type, Ar&&... rg) ->
decltype(Fn<num_type_from_i<0>>()(std::forward<Ar>(rg)...))
{
using n_types = mp11::mp_size<types>;
return dispatch_impl<types, std::size_t{n_types::value}>::template call<0,
Fn>(type, std::forward<Ar>(rg)...);
}
template <typename T>
struct MatInit
{
SEXP operator()(arma::uword n_rows, arma::uword n_cols)
{
auto res = new arma::Mat<T>(n_rows, n_cols);
auto ind = std::size_t{i_form_num_type<T>::value};
return Rcpp::XPtr<arma::Mat<T>>(res, true, Rcpp::wrap(ind));
}
};
// [[Rcpp::export]]
SEXP mat_init(arma::uword n_rows, arma::uword n_cols, std::size_t data_type)
{
return dispatch_type<MatInit>(data_type, n_rows, n_cols);
}
template <typename T>
struct Length
{
arma::uword operator()(SEXP x)
{
return Rcpp::XPtr<arma::Mat<T>>(x)->n_elem;
}
};
// [[Rcpp::export]]
arma::uword length(SEXP x)
{
std::size_t type = Rcpp::as<std::size_t>(R_ExternalPtrTag(x));
return dispatch_type<Length>(type, x);
}
This way the list of types can easily be modified and apart from requiring templated function objects instead of function templates, implementation of functions such as length()
is fairly succinct.
Furthermore, I don't have to pass the data type index between R and C, but can store the index within the external pointer structure.
If anyone sees potential issues, I'd be keen on hearing from them.
这篇关于如何处理可能具有多种类型之一的 Rcpp::XPtr的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!