Error exposing c++ class method with reference parameter using Rcpp modules

六眼飞鱼酱① 提交于 2021-02-10 08:36:41

问题


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?


回答1:


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.




回答2:


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).



来源:https://stackoverflow.com/questions/55202440/error-exposing-c-class-method-with-reference-parameter-using-rcpp-modules

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!