Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expose simple C++ Student class to R using Rcpp modules

Tags:

c++

r

rcpp

I'm trying to expose a simple Student class in C++ to R using Rcpp within a package. Here's my setup...

src/

Student.hpp
//  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)
  ;
}

R/

student.R
#' 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

NAMESPACE
# 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.

like image 481
Ben Avatar asked Dec 06 '18 20:12

Ben


2 Answers

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.

RcppAnnoy

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

RcppStudent

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

like image 105
coatless Avatar answered Nov 10 '22 09:11

coatless


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

like image 34
Ben Avatar answered Nov 10 '22 07:11

Ben