基于switch语句初始化具有不同类型的变量

时间:2014-07-08 02:48:02

标签: rcpp

我正在开发Rcpp中的一些函数,这些函数对big.matrix包中的bigmemory个对象进行操作。这些对象作为Rcpp对象传递给SEXP,然后我必须将其转换为XPtr<BigMatrix>,然后转换为MatrixAccessor对象以访问矩阵的元素。

例如,如果我想实现一个获得对角线的函数:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::depends(BH, bigmemory)
#include <bigmemory/MatrixAccessor.hpp>

#include <numeric>

// [[Rcpp::export]]
NumericVector GetDiag(SEXP pmat) {
  XPtr<BigMatrix> xpMat(pMat); // allows you to access attributes
  MatrixAccessor<double> mat(*xpMat); // allows you to access matrix elements

  NumericVector diag(xpMat->nrow()); // Assume matrix is square
  for (int i = 0; i < xpMat->nrow(); i++) { 
    diag[i] = mat[i][i];
  }
  return diag;
}

如果R中的big.matrix对象填充了双精度数,则此函数可以很好地工作。

但是,如果在整数矩阵(例如diag(as.big.matrix(matrix(1:9, 3))@address))上调用此函数,则会导致垃圾,因为MatrixAccessor已初始化为<double>

在内部,big.matrix个对象可以有四种类型:

void typeof(SEXP pMat) {
  XPtr<BigMatrix> xpMat(pMat);
  int type = xpMat->matrix_type();
  type == 1 // char
  type == 2 // short
  type == 4 // int
  type == 8 // double
}

由于我们所做的只是访问矩阵的元素,diag函数应该能够处理这些类型中的每一个。但是现在,由于我们的函数签名是NumericVector,我将忽略字符矩阵。

为了解决这个问题,我想我可以抛出一个switch语句,在运行时使用适当的类型初始化相应的mat

// [[Rcpp::export]]
NumericVector GetDiag(SEXP pmat) {
  XPtr<BigMatrix> xpMat(pMat);
  // Determine the typeof(pmat), and initialize accordingly:
  switch(xpMat->matrix_type()) {
    case == 1:
    {
       // Function expects to return a NumericVector.
       throw; 
    }
    case == 2:
    {
      MatrixAccessor<short> mat(*xpMat);
      break;
    }
    case == 4:
    {
      MatrixAccessor<int> mat(*xpMat);
      break;
    }
    case == 8:
    {
      MatrixAccessor<double> mat(*xpMat);
    }
  }
  MatrixAccessor<double> mat(*xpMat); // allows you to access matrix elements

  NumericVector diag(xpMat->nrow()); // Assume matrix is square
  for (int i = 0; i < xpMat->nrow(); i++) { 
    diag[i] = mat[i][i];
  }
  return diag;
}

但是,这会导致编译器错误,因为我已经在第一个mat中声明了case之后重新定义diag

我能看到这样做的唯一方法是编写三个不同的mat函数,每个类型一个,其代码相同,但{{1}}的初始化除外。还有更好的方法吗?

2 个答案:

答案 0 :(得分:8)

在这些情况下,您通常需要分离逻辑:首先,您有一个调度函数将SEXP类型编组为某种编译时类型,以及一个单独的(模板化)函数来处理实际工作。一些(不完整的)示例代码:

#include <Rcpp.h>
using namespace Rcpp;

// the actual generic implementation
template <typename T>
T GetDiag_impl(T const& pMat) {
  // logic goes here
}

// the dispatch code

// [[Rcpp::export]]
SEXP GetDiag(SEXP pMat) {
  switch (TYPEOF(pMat)) {
  case INTSXP: return GetDiag_impl<IntegerMatrix>(pMat);
  case REALSXP: return GetDiag_impl<NumericMatrix>(pMat);
  <...>
  }
}

答案 1 :(得分:8)

感谢Kevin Ushey关于分离调度和功能逻辑的指示。所需的模板代码与Kevin的建议完全不同,以保证自己的答案。

要编写一个通常适用于所有类型的big.matrix的函数,模式如下:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::depends(BH, bigmemory)]]
#include <bigmemory/MatrixAccessor.hpp>

// Generic Implementation of GetDiag:
template <typename T>
NumericVector GetDiag_impl(XPtr<BigMatrix> xpMat, MatrixAccessor<T> mat) {
  NumericVector diag(xpMat->nrow()); // Assume matrix is square
  for (unsigned int i = 0; i < xpMat->nrow(); i++) { 
    diag[i] = mat[i][i];
  }
  return diag;
}

// Dispatch code
// [[Rcpp::export]]
NumericVector GetDiag(SEXP pMat) {
  XPtr<BigMatrix> xpMat(pMat);
  switch(xpMat->matrix_type()) {
    case 1:
      return GetDiag_impl(xpMat, MatrixAccessor<char>(*xpMat));
    case 2:
      return GetDiag_impl(xpMat, MatrixAccessor<short>(*xpMat));
    case 4:
      return GetDiag_impl(xpMat, MatrixAccessor<int>(*xpMat));
    case 8:
      return GetDiag_impl(xpMat, MatrixAccessor<double>(*xpMat));
    default:
      // This should be impossible to reach, but shuts up the compiler
      throw Rcpp::exception("Unknown type of big.matrix detected! Aborting.");
  }
}

您无需模拟GetDiag_impl的返回类型:所有四种big.matrix类型都以R形式存储为数字(有关&#39; char&#的讨论,请参阅This Answer 39; big.matrix对象)。