Search code examples
c++rrcpp

Mixing Rcpp modules and Rcpp::export


I want to expose a C++ class and a function that takes objects of that class as a parameter to R. I have to following simplified example. I created a package using

Rscript -e 'Rcpp::Rcpp.package.skeleton("soq")'

and put the following code in soq_types.h

#include <RcppCommon.h>
#include <string>

class Echo {
  private:
  std::string message;
  public:
  Echo(std::string message) : message(message) {}
  Echo(SEXP);

  std::string get() { return message; }
};

#include <Rcpp.h>

using namespace Rcpp;

RCPP_MODULE(echo_module) {
  class_<Echo>("Echo")
  .constructor<std::string>()
  .method("get", &Echo::get)
  ;
};

//// [[Rcpp::export]]
void shout(Echo e) {
  Rcout << e.get() << "!" << std::endl;
}

Note that the last comment has extra slashes and doesn't cause the function to be exported. When I now run:

$> Rscript -e 'Rcpp::compileAttributes()'
$> R CMD INSTALL .

R> library(Rcpp)
R> suppressMessages(library(inline))
R> library(soq)
R> echo_module <- Module("echo_module", getDynLib("soq"))
R> Echo <- echo_module$Echo
R> e <- new(Echo, "Hello World")
R> print(e$get())

everything is fine. Unfortunately, if I enable the Rcpp::export, do compileAttributes() and re-install I get:

** testing if installed package can be loaded from temporary location
Error: package or namespace load failed for ‘soq’ in dyn.load(file, DLLpath = DLLpath, ...):
 unable to load shared object '/home/brj/R/x86_64-pc-linux-gnu-library/3.6/00LOCK-soq/00new/soq/libs/soq.so':
  /home/brj/R/x86_64-pc-linux-gnu-library/3.6/00LOCK-soq/00new/soq/libs/soq.so: undefined symbol: _ZN4EchoC1EP7SEXPREC
Error: loading failed
Execution halted
ERROR: loading failed

My question is: how do I get both to work?

I'm on R.3.6.3 and

R> sessionInfo()
....
other attached packages:
[1] inline_0.3.15 Rcpp_1.0.4.6 
....

Addendum

For those trying to follow the example above: it's very important that the source file is exactly named <package_name>_types.h. Otherwise, the auto-generated RcppExports.cpp will not #include it and thus the Echo class will not be defined there. This will cause a compilation error.


Solution

  • The error message is complaining about the declared but not defined Echo(SEXP), which is meant to extend Rcpp::as<>. For classes handled by Rcpp modules it is easier to use the RCPP_EXPOSED_* macros:

     #include <Rcpp.h>
    
    class Echo {
    private:
        std::string message;
    public:
        Echo(std::string message) : message(message) {}
    
        std::string get() { return message; }
    };
    
    RCPP_EXPOSED_AS(Echo);
    
    using namespace Rcpp;
    
    RCPP_MODULE(echo_module) {
        class_<Echo>("Echo")
        .constructor<std::string>()
        .method("get", &Echo::get)
        ;
    };
    
    // [[Rcpp::export]]
    void shout(Echo e) {
        Rcout << e.get() << "!" << std::endl;
    }
    
    /***R
    e <- new(Echo, "Hello World")
    print(e$get())
    shout(e)
    */ 
    

    Output:

    > Rcpp::sourceCpp('62228538.cpp')
    
    > e <- new(Echo, "Hello World")
    
    > print(e$get())
    [1] "Hello World"
    
    > shout(e)
    Hello World!
    

    All this was not inside a package but using Rcpp::sourceCpp. I would expect it to work in a package as well, though.