如何处理可能具有多种类型之一的 Rcpp::XPtr [英] How to deal with an Rcpp::XPtr that may have one of several types

查看:71
本文介绍了如何处理可能具有多种类型之一的 Rcpp::XPtr的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的情况是我有一个 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_ii_form_num_type 用于在给定索引/给定类型的索引的情况下查询类型
  • 模板结构 dispatch_impl,递归使用,提供对类型列表的迭代
  • 用于终止递归的 dispatch_impl 的专用版本
  • 一个方便的函数 dispatch_type() 调用 dispatch_impl 并定义列表长度/最大递归深度
  • 示例函数对象 MatInitLength 以及它们的 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, called types) defining my set of types
  • helper metafunctions num_type_from_i and i_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() calling dispatch_impl and defining the list length/max recursion depth
  • example function objects MatInit and Length alongside their R interfaces mat_init() and 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);
}

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆