问题
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