如何处理可能具有多种类型之一的 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) \
{ \
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)
}
有更好的方法吗?我将此模式用于相当多的功能,冗长已成为一个问题。理想情况下,我有一个单一但简洁的功能,例如(当然不起作用)
arma::uword mat_length(SEXP mat)
{
Rcpp::XPtr<arma::Mat> p(mat);
return p->n_elem;
}
而不是两个函数 + 每个实例的宏,我将 XPtr
像从 R 传递到 C 那样。
奖金问题:基于宏观的方法有什么明显的错误吗?这在某种程度上是低效还是可能导致问题?
要创建可重现的示例,请添加
// [[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);
}
和 运行 Rcpp::sourceCpp()
在 R 中的文件上
到目前为止我能想到的最好的非宏观方法(使用 boost::mp11
)如下:
关键部分:
- 类型列表(
mp11::mp_list
,称为 types
)定义我的类型集
- 辅助元函数
num_type_from_i
和 i_form_num_type
用于查询给定类型 index/index 给定类型 的类型
- 模板化结构
dispatch_impl
,递归使用,提供对类型列表的迭代
dispatch_impl
的特殊版本,用于终止递归
- 一个方便的函数
dispatch_type()
调用dispatch_impl
并定义列表length/max递归深度
- 示例函数对象
MatInit
和 Length
以及它们的 R 接口 mat_init()
和 length()
// [[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);
}
这样可以轻松修改类型列表,除了需要模板化函数对象而不是函数模板之外,length()
等函数的实现相当简洁。
此外,我不必在R和C之间传递数据类型索引,而是可以将索引存储在外部指针结构中。
如果有人看到潜在的问题,我很想听取他们的意见。
我的情况是 Rcpp::XPtr
到犰狳对象(例如 arma::Mat
,它可能是一种支持的数据类型的矩阵)。现在我想写一个查询元素数量的函数。到目前为止我能想到的最好的是以下(受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)
}
有更好的方法吗?我将此模式用于相当多的功能,冗长已成为一个问题。理想情况下,我有一个单一但简洁的功能,例如(当然不起作用)
arma::uword mat_length(SEXP mat)
{
Rcpp::XPtr<arma::Mat> p(mat);
return p->n_elem;
}
而不是两个函数 + 每个实例的宏,我将 XPtr
像从 R 传递到 C 那样。
奖金问题:基于宏观的方法有什么明显的错误吗?这在某种程度上是低效还是可能导致问题?
要创建可重现的示例,请添加
// [[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);
}
和 运行 Rcpp::sourceCpp()
在 R 中的文件上
到目前为止我能想到的最好的非宏观方法(使用 boost::mp11
)如下:
关键部分:
- 类型列表(
mp11::mp_list
,称为types
)定义我的类型集 - 辅助元函数
num_type_from_i
和i_form_num_type
用于查询给定类型 index/index 给定类型 的类型
- 模板化结构
dispatch_impl
,递归使用,提供对类型列表的迭代 dispatch_impl
的特殊版本,用于终止递归- 一个方便的函数
dispatch_type()
调用dispatch_impl
并定义列表length/max递归深度 - 示例函数对象
MatInit
和Length
以及它们的 R 接口mat_init()
和length()
// [[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);
}
这样可以轻松修改类型列表,除了需要模板化函数对象而不是函数模板之外,length()
等函数的实现相当简洁。
此外,我不必在R和C之间传递数据类型索引,而是可以将索引存储在外部指针结构中。
如果有人看到潜在的问题,我很想听取他们的意见。