我遇到的情况是我对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;
}

而不是为每个实例分配两个宏+一个宏,在该实例中,我从R到C传递了一个XPtr

额外的问题:基于宏的方法是否有明显的错误?这是某种方式的效率低下还是会导致一系列问题?

要创建可复制的示例,请添加

// [[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()

最佳答案

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

关键部分:

  • 一个类型列表(mp11::mp_list,称为types),定义了我的一组类型
  • 帮助程序元函数num_type_from_ii_form_num_type在给定索引的情况下查询类型/在给定类型
  • 的情况下查询索引
  • 递归使用的模板化结构dispatch_impl,在类型列表
  • 上提供迭代
  • 用于终止递归
  • dispatch_impl的特殊版本
  • 便捷功能dispatch_type()调用dispatch_impl并定义列表长度/最大递归深度
  • 示例函数对象MatInitLength及其R接口(interface)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之间传递数据类型索引,但是可以将索引存储在外部指针结构中。

    如果有人看到潜在的问题,我很想听听他们的意见。

    关于c++ - 如何处理可能具有几种类型之一的Rcpp::XPtr,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57296033/

    10-11 22:53
    查看更多