【问题标题】:How to deal with an Rcpp::XPtr that may have one of several types如何处理可能具有多种类型之一的 Rcpp::XPtr
【发布时间】:2019-07-31 17:19:27
【问题描述】:

我的情况是,我有一个指向 Armadillo 对象的 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);
}

并对 R 中的文件运行 Rcpp::sourceCpp()

【问题讨论】:

  • 我要说的第一件事是使用宏#define DISPATCH_DATA_TYPE(CALL) { 看起来很糟糕——你为什么要这样做而不是一个适当的(可能是constexpr)函数?第二; case 1: CALL(unsigned short) \ case 2: CALL(unsigned int) \ case 3: CALL(unsigned long) 等等,这些真的应该落入下一个 case 吗?这看起来是错误的 - 我本来希望那里有一些 break 语句..
  • @JesperJuhl 感谢您对我的问题感兴趣。我看你不喜欢宏观方法。至于我为什么要这样做,恐怕我没有一个好的答案:我在某个地方看到了这个,并认为它会以合理的方式解决我的问题。这更像是一种审美厌恶,还是客观上对此有什么不好?如果您认为这应该以不同的方式进行,您会这么好心并草拟一个替代解决方案吗?缺少breaks 有问题吗? CALLs 包含 return 语句。

标签: c++ templates rcpp rcpparmadillo


【解决方案1】:

到目前为止我能想到的最好的非宏观方法(使用boost::mp11)如下:

关键部分:

  • 定义我的类型集的类型列表(mp11::mp_list,称为types
  • 帮助元函数 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 <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 之间传递数据类型索引,而是可以将索引存储在外部指针结构中。

如果有人发现潜在问题,我会很乐意听取他们的意见。

【讨论】:

    猜你喜欢
    • 2022-06-14
    • 2020-07-15
    • 2017-12-09
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多