Search code examples
rcpp

In Rcpp, how to get a user-defined structure from C into R


Am using Rcpp packages and can get my C function to compile and run in R, but now I want to return a large, user-defined data structure to R. The fields in the structure are either numbers or strings - no new or odd types within the structure. The example below is simplified and doesn't compile, but it conveys the idea of my problem.

typedef struct {
  char*    firstname[128];
  char*    lastname[128];
  int      nbrOfSamples;
} HEADER_INFO;

// [[Rcpp::export]]
HEADER_INFO* read_header(Rcpp::StringVector strings) {
  FILE *fp;
  MEF_HEADER_INFO *header;
    
  char * filename = (char*)(strings(0));
  char * password = (char*)(strings(1));
    
  header = (HEADER_INFO*)malloc(sizeof(HEADER_INFO));
  memset(header, 0, sizeof(HEADER_INFO));
    
  fp = fopen(filename, "r");
  (void)read_header(header, password);
  return header;
}

I'm pretty sure that I could package the entries in the header back into a StringVector, but that seems like a brute-force approach. My question is whether a more elegant solution exists. It is not clear to me what form such a structure would even have in R: a named List?

Thanks!


Solution

  • The right structure in R depends on what your struct looks like exactly. A named list is the most general one. Here a simple sample implementation for a wrap function as referred to in the comments:

    #include <RcppCommon.h>
    
    typedef struct {
      char*   firstname[128];
      char*   lastname[128];
      int      nbrOfSamples;
    } HEADER_INFO;
    
    namespace Rcpp {
      template <>
      SEXP wrap(const HEADER_INFO& x);
    }
    
    #include <Rcpp.h>
    
    namespace Rcpp {
      template <>
      SEXP wrap(const HEADER_INFO& x) {
        Rcpp::CharacterVector firstname(x.firstname, x.firstname + x.nbrOfSamples);
        Rcpp::CharacterVector lastname(x.lastname, x.lastname + x.nbrOfSamples);
        return Rcpp::wrap(Rcpp::List::create(Rcpp::Named("firstname") = firstname,
                          Rcpp::Named("lastname") = lastname,
                          Rcpp::Named("nbrOfSamples") = Rcpp::wrap(x.nbrOfSamples)));
      };
    }
    
    //  [[Rcpp::export]]
    HEADER_INFO getHeaderInfo() {
      HEADER_INFO header;
      header.firstname[0] = (char*)"Albert";
      header.lastname[0] = (char*)"Einstein";
      header.firstname[1] = (char*)"Niels";
      header.lastname[1] = (char*)"Bohr";
      header.firstname[2] = (char*)"Werner";
      header.lastname[2] = (char*)"Heisenberg";
      header.nbrOfSamples = 3;
      return header;
    }
    
    /*** R
    getHeaderInfo()
     */
    

    Output:

    > getHeaderInfo()
    $firstname
    [1] "Albert" "Niels"   "Werner"
    
    $lastname
    [1] "Einstein"   "Bohr"       "Heisenberg"
    
    $nbrOfSamples
    [1] 3
    

    However, for this particular case a data.frame would be more natural to use, which can be achieved by replacing above wrap with:

      template <>
      SEXP wrap(const HEADER_INFO& x) {
        Rcpp::CharacterVector firstname(x.firstname, x.firstname + x.nbrOfSamples);
        Rcpp::CharacterVector lastname(x.lastname, x.lastname + x.nbrOfSamples);
        return Rcpp::wrap(Rcpp::DataFrame::create(Rcpp::Named("firstname") = firstname,
                                                  Rcpp::Named("lastname") = lastname));
      };
    

    Output:

    > getHeaderInfo()
      firstname   lastname
    1    Albert   Einstein
    2     Niels       Bohr
    3    Werner Heisenberg