8

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!

SymbolixAU
  • 25,502
  • 4
  • 67
  • 139
Mark Bower
  • 569
  • 2
  • 16
  • 3
    c.f. Custom Templated as and wrap Functions within Rcpp on the Rcpp Gallery @ http://gallery.rcpp.org/articles/custom-templated-wrap-and-as-for-seamingless-interfaces/ – coatless Jun 29 '18 at 22:54
  • 2
    BTW It is called 'Rcpp' so the cpp are lower case. And it has documentation, including an entire vignette dedicated to this (not trivial but important) topic. In short, you need to to write converters. – Dirk Eddelbuettel Jun 30 '18 at 01:47
  • By chance I saw this morning that you posted an additional question as "answer", which is now gone. In case it got deleted as "not an answer": Either post a new question or [edit] your question to add the "bonus" material. BTW, the example you posted would not compile for various reasons. Please try to make examples that can be run without modifications. This not only helps us helping you but also helps you to isolate the problem. Quite often one solves the problem when one tries to build a MWE. – Ralf Stubner Jul 03 '18 at 07:49

1 Answers1

12

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
Ralf Stubner
  • 26,263
  • 3
  • 40
  • 75
  • This worked for me with `sourceCpp()`; what changes are required to put this into a package? I get: `RcppExports.cpp:9:1: error: ‘HEADER_INFO’ does not name a type` and `RcppExports.cpp:14:34: error: ‘getHeaderInfo’ was not declared in this scope rcpp_result_gen = Rcpp::wrap(getHeaderInfo());` ... `ERROR: compilation failed` – nsheff Apr 02 '20 at 19:30
  • @nsheff Good question! Have a look at https://cran.r-project.org/web/packages/Rcpp/vignettes/Rcpp-attributes.pdf, in particular section "2.5. Types in Generated Code". – Ralf Stubner Apr 03 '20 at 21:06
  • 1
    @RalfStubner thanks, I was able to get this to work using `*_types.h` – nsheff Apr 03 '20 at 21:16