3

My goal is to build a dataset class and a model class, and expose both of them to R. The model class has a train() method that takes a reference to a dataset instance, and this seems to be at the root of my issue. Here's what this looks like

//glue.cpp

#include <Rcpp.h>

class MyData
{
public:
  MyData() = default;
};

class MyModel
{
public:
  MyModel() = default;
  void train(const MyData& data) { Rcpp::Rcout << "training model... "; };
};

// Expose MyData
RCPP_MODULE(MyData){
  Rcpp::class_<MyData>("MyData")
  .constructor()
  ;
}

// Expose MyModel
RCPP_MODULE(MyModel){
  Rcpp::class_<MyModel>("MyModel")
  .constructor()
  .method("train", &MyModel::train)
  ;
}

(I should note that this file, glue.cpp, is embedded in an R package.) When I remove this line, .method("train", &MyModel::train), I can compile this without error via pkgbuild::compile_dll(). With it, I get the nasty error below

─  installing *source* package ‘SimpleCppModel’ ...
   ** libs
   clang++  -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I"/Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include" -I/usr/local/include  -std=c++14 -fPIC  -Wall -g -O2  -c RcppExports.cpp -o RcppExports.o
   clang++  -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I"/Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include" -I/usr/local/include  -std=c++14 -fPIC  -Wall -g -O2  -c glue.cpp -o glue.o
   In file included from glue.cpp:3:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp.h:27:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/RcppCommon.h:168:
   In file included from /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:25:
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/internal/Exporter.h:31:28: error: no matching constructor for initialization of 'MyData'
                       Exporter( SEXP x ) : t(x){}
                                            ^ ~
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:87:41: note: in instantiation of member function 'Rcpp::traits::Exporter<MyData>::Exporter' requested here
               ::Rcpp::traits::Exporter<T> exporter(x);
                                           ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/as.h:152:26: note: in instantiation of function template specialization 'Rcpp::internal::as<MyData>' requested here
           return internal::as<T>(x, typename traits::r_type_traits<T>::r_category());
                            ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/InputParameter.h:72:54: note: in instantiation of function template specialization 'Rcpp::as<MyData>' requested here
           ConstReferenceInputParameter(SEXP x_) : obj( as<T>(x_) ){}
                                                        ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:129:58: note: in instantiation of member function 'Rcpp::ConstReferenceInputParameter<MyData>::ConstReferenceInputParameter' requested here
           typename Rcpp::traits::input_parameter<U0>::type x0(args[0]);
                                                            ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_CppMethod.h:127:5: note: in instantiation of member function 'Rcpp::CppMethod1<MyModel, void, const MyData &>::operator()' requested here
       CppMethod1( Method m) : method_class(), met(m) {} 
       ^
   /Library/Frameworks/R.framework/Versions/3.5/Resources/library/Rcpp/include/Rcpp/module/Module_generated_method.h:59:27: note: in instantiation of member function 'Rcpp::CppMethod1<MyModel, void, const MyData &>::CppMethod1' requested here
       AddMethod( name_, new CppMethod1<Class,RESULT_TYPE,U0>(fun), valid, docstring);
                             ^
   glue.cpp:30:4: note: in instantiation of function template specialization 'Rcpp::class_<MyModel>::method<void, const MyData &>' requested here
     .method("train", &MyModel::train)
      ^
   glue.cpp:5:7: note: candidate constructor (the implicit copy constructor) not viable: cannot convert argument of incomplete type 'SEXP' (aka 'SEXPREC *') to 'const MyData' for 1st argument
   class MyData
         ^
   glue.cpp:5:7: note: candidate constructor (the implicit move constructor) not viable: cannot convert argument of incomplete type 'SEXP' (aka 'SEXPREC *') to 'MyData' for 1st argument
   glue.cpp:8:3: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
     MyData() = default;
     ^
   1 error generated.
   make: *** [glue.o] Error 1
   ERROR: compilation failed for package ‘SimpleCppModel’
─  removing ‘/private/var/folders/dn/9lp6j6j14t1137ftnnk27wyw0000gn/T/RtmpBqDLoF/devtools_install_4d775ed17ea2/SimpleCppModel’
Error in processx::run(bin, args = real_cmdargs, stdout_line_callback = real_callback(stdout),  : 
  System command error

What gives?

Ben
  • 20,038
  • 30
  • 112
  • 189
  • 3
    Do you have converter methods, ie `as<>` and `wrap`? How is Rcpp supposed to know how to get data in and out of `MyModel` ? – Dirk Eddelbuettel Mar 16 '19 at 23:17
  • 1
    Thanks Dirk, that makes sense. I'll try working through your tutorial on [Custom Templated as and wrap Functions within Rcpp](http://gallery.rcpp.org/articles/custom-templated-wrap-and-as-for-seamingless-interfaces/) and see if I can apply it to my situation. Much appreciated. – Ben Mar 16 '19 at 23:28
  • 1
    I am not denying that it is all still a wee bit complicated and intertwined -- hence multiple vignettes and a book. So start _as simple as possible_ with the module example and then go from there. For _atomistic_ types (`double` etc) and Rcpp-known types it works. For the rest you can make it work with more effort. – Dirk Eddelbuettel Mar 16 '19 at 23:30
  • @coatless derp! So sorry. Unfortunately it's too late to edit my comment :( For the record and search engines, that's James Balamuta's article, [Custom Templated as and wrap Functions within Rcpp](http://gallery.rcpp.org/articles/custom-templated-wrap-and-as-for-seamingless-interfaces/). – Ben Mar 18 '19 at 17:01

2 Answers2

4

As mentioned by Dirk in the comments, you will need as<>and wrap specializations for MyData. In your case, you can use the easiest solution from the "Extending Rcpp" vignette: RCPP_EXPOSED_CLASS. You just have to be careful when you include which header from Rcpp:

#include <Rcpp.h>

class MyData
{
public:
  MyData() = default;
};
RCPP_EXPOSED_CLASS(MyData)

class MyModel
{
public:
  MyModel() = default;
  void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; };
};

// Expose MyData
RCPP_MODULE(MyData){
  Rcpp::class_<MyData>("MyData")
  .constructor()
  ;
}

// Expose MyModel
RCPP_MODULE(MyModel){
  Rcpp::class_<MyModel>("MyModel")
  .constructor()
  .method("train", &MyModel::train)
  ;
}

/***R
myData <- new(MyData)
myModel <- new(MyModel)
myModel$train(myData) 
 */

As you found out in your answer, this even works after including Rcpp.h. It still makes sense to go through the mentioned gallery article as well the vignette, though.

Ralf Stubner
  • 26,263
  • 3
  • 40
  • 75
  • Thanks Ralf, this is the right answer to the question I asked (and I'll mark it as so), but unfortunately I made my example too simple. MyData actually has a method like `void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; };`. I.e. the MyData class depends on NumericMatrix which is in Rcpp.h, so this technique of loading Rcpp.h after declaring MyData gives an error. – Ben Mar 18 '19 at 15:15
  • @Ben That makes it really tricky, since you cannot extend Rcpp after including `Rcpp.h`. Can't you use some other data type for the data? – Ralf Stubner Mar 18 '19 at 15:29
3

First I should state that the example in my question is overly simple. In reality, the MyData class has a method like void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; }; it depends on Rcpp::NumericMatrix, so I have to #include <Rcpp.h> before I declare MyData.

Here are two solutions I've found, but I haven't used them enough to know if there are any "gotchas".

Solution 1 - RCPP_EXPOSED_CLASS() macro

Here I use the RCPP_EXPOSED_CLASS() macro as described by Romain Francois. (Thanks Ralf, for pointing out this nugget to me.)

#include <Rcpp.h>

class MyData;
class MyModel;

RCPP_EXPOSED_CLASS(MyData)
RCPP_EXPOSED_CLASS(MyModel)

class MyData
{
public:
  MyData() = default;
  void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; };
};

  class MyModel
  {
  public:
    MyModel() = default;
    void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; };
  };


// Expose MyData
RCPP_MODULE(MyData){
  Rcpp::class_<MyData>("MyData")
  .constructor()
  .method("fill", &MyData::fill)
  ;
}

// Expose MyModel
RCPP_MODULE(MyModel){
  Rcpp::class_<MyModel>("MyModel")
  .constructor()
  .method("train", &MyModel::train)
  ;
}

/***R
myData <- new(MyData)
myModel <- new(MyModel)
myModel$train(myData)
*/

Solution 2 - Use an Rcpp::XPtr

This solution is based on the design pattern discussed here. Basically, I just create a pointer to the C++ object and use wrapper functions to handle it. Here's an example of how that might look.

#include <Rcpp.h>

class MyData
{
public:
  MyData() = default;
  void fill(const Rcpp::NumericMatrix& dat) { Rcpp::Rcout << "filling data... " << std::endl; };
};

class MyModel
{
public:
  MyModel() = default;
  void train(const MyData& data) { Rcpp::Rcout << "training model... " << std::endl; };
};

// [[Rcpp::export]]
SEXP make_dataset() {
  MyData* datPtr = new MyData();
  Rcpp::XPtr<MyData> datXPtr(datPtr);
  return datXPtr;
}

// [[Rcpp::export]]
SEXP make_model() {
  MyModel* mdlPtr = new MyModel();
  Rcpp::XPtr<MyModel> mdlXPtr(mdlPtr);
  return mdlXPtr;
}

// [[Rcpp::export]]
void train_model(SEXP mdlXPtr, SEXP datXPtr) {
  Rcpp::XPtr<MyModel> mdlPtr(mdlXPtr);
  Rcpp::XPtr<MyData> datPtr(datXPtr);
  mdlPtr->train(*datPtr);
}

/***R
myData <- make_dataset()
myModel <- make_model()
train_model(myModel, myData)
*/

The downside to this method is that, AFAIK, there isn't an obvious way to check the type of object an XPtr is pointing to. For example, it's not obvious to me how to invalidate bad calls like train_model(myData, myModel).

Ben
  • 20,038
  • 30
  • 112
  • 189