问题
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.
回答1:
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.
来源:https://stackoverflow.com/questions/57296033/how-to-deal-with-an-rcppxptr-that-may-have-one-of-several-types