I'm trying to expose a simple Student class in C++ to R using Rcpp within a package. Here's my setup...
// Student.hpp
#ifndef Student_hpp
#define Student_hpp
#include <string>
#include <vector>
class Student
{
public:
// Constructor
Student(std::string name, int age, bool male);
// Getters
std::string GetName();
int GetAge();
bool IsMale();
std::vector<int> GetFavoriteNumbers();
// Methods
bool LikesBlue();
private:
// Member variables
std::string name;
int age;
bool male;
std::vector<int> favoriteNumbers;
};
#endif /* Student_hpp */
Student.cpp
// Student.cpp
#include "Student.hpp"
// Constructor
Student::Student(std::string name, int age, bool male) {
this->name = name;
this->age = age;
this->male = male;
this->favoriteNumbers = {2, 3, 5, 7, 11};
}
// Getters
bool Student::IsMale() { return male; }
int Student::GetAge() { return age; }
std::string Student::GetName() { return name; }
std::vector<int> Student::GetFavoriteNumbers() { return favoriteNumbers; }
// Methods
bool Student::LikesBlue() {
return (male || age >= 10);
}
glue.cpp
// glue.cpp
// To use c++11, first run: Sys.setenv("PKG_CXXFLAGS"="-std=c++11") ...or use a Makevars file
#include <Rcpp.h>
#include "Student.hpp"
using namespace Rcpp;
//' Simulate a student
//'
//' @export
// [[Rcpp::export]]
std::vector<int> simulate_student() {
Student s = Student("bob", 10, true);
return s.GetFavoriteNumbers();
}
// Expose (some of) the Student class
RCPP_MODULE(Student){
class_<Student>("Student")
.constructor<std::string,int,bool>()
.method("LikesBlue", &Student::LikesBlue)
;
}
#' student
#'
#' A cool package
#'
#' Imports
#' @useDynLib student, .registration = TRUE
#' @importFrom Rcpp sourceCpp
"_PACKAGE"
Rcpp::loadModule(module = "Student", TRUE)
After calling devtools::document()
, I get the following NAMESPACE file
# Generated by roxygen2: do not edit by hand
export(simulate_student)
importFrom(Rcpp,sourceCpp)
useDynLib(student, .registration = TRUE)
Now, after doing clean and rebuild from RStudio (i.e. R CMD INSTALL --preclean --no-multiarch --with-keep.source student
) the package compiles and loads without issue. If I run the simulate_student()
function, I get the expected result. However, when I try to make a new Student object with stud <- new(Student)
I get Error in .getClassesFromCache(Class) : object 'Student' not found
This issue seems similar to this SO post but the accepted answer does not seem to solve my issue. Furthermore, I've looked into the Annoy source code mentioned by Dirk, but I don't see any helpful differences between that code and mine except for the RCPP_EXPOSED_CLASS_NODECL(AnnoyEuclidean)
snippets which I've tried placing in my code but also do not help.
Glancing at the two packages, the main difference is related to how imports and exports of functions are setup.
In particular, both the methods
and Rcpp
package are missing as full imports in the NAMESPACE
file. In addition, instead of individually exporting each function, there is a global export pattern used.
useDynLib(RcppAnnoy, .registration=TRUE)
import(methods, Rcpp)
exportPattern("^[[:alpha:]]+") # export all identifiers starting with letters
https://github.com/eddelbuettel/rcppannoy/blob/1ce871ae52730098ddc7355597613e8313e23ac9/NAMESPACE#L1-L3
roxygen2:
#' @useDynLib RcppStudent, .registration = TRUE
#' @import methods Rcpp
#' @exportPattern "^[[:alpha:]]+"
NAMESPACE
:
# Generated by roxygen2: do not edit by hand
exportPattern("^[[:alpha:]]+")
import(Rcpp)
import(methods)
useDynLib(RcppStudent, .registration = TRUE)
https://github.com/r-pkg-examples/rcpp-modules/blob/ca5d13ddd391d9fd4ffb50b4306131d2839f8af5/NAMESPACE#L5-L7
The answer and comments to this SO post helped me. It seems I needed to
1) change @importFrom Rcpp sourceCpp
to @import Rcpp
and
2) Add export(Student)
to my NAMESPACE file (although I'm searching how to do this nicely with roxygen2 instead of inserting this manually)
After the above changes I can run
> stud <- new(Student, name = "Ben", age = 26, male = T)
> stud$LikesBlue()
[1] TRUE
UPDATE
It seems I can add #' @export Student
to student.R for roxygen to automatically add the export(Student)
bit to my NAMESPACE, that way I don't have to modify NAMESPACE manually.
UPDATE2
I've documented my steps in this blog post
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With