Home > Blockchain >  Expose custom type to R using Rcpp Modules field produces compilation
Expose custom type to R using Rcpp Modules field produces compilation

Time:01-03

I'm frequently using std::map<std::string, arma::vec> in c so I wrote custom as and wrap templates to handle the R-C conversion. Below is a minimal reprex:


// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>

// forward declarations
namespace Rcpp
{
    template <>
    inline std::map<std::string, arma::vec> as(SEXP matsexp)
    {
        Rcpp::NumericMatrix mat(matsexp);

        std::vector<std::string> cn = Rcpp::as<std::vector<std::string>>(Rcpp::colnames(mat));

        std::map<std::string, arma::vec> map;

        for (unsigned int n = 0; n < mat.ncol(); n  )
        {
            map[cn[n]] = mat.column(n);
        }

        return map;
    }

    template <>
    inline SEXP wrap(const std::map<std::string, arma::vec> &map)
    {
        Rcpp::NumericMatrix mat(map.begin()->second.n_elem, map.size());
        // Get all keys of the map
        std::vector<std::string> keys;
        for (auto const &x : map)
        {
            keys.push_back(x.first);
        }
        // Get all values of the map
        std::vector<arma::vec> values;
        for (auto const &x : map)
        {
            values.push_back(x.second);
        }
        // Fill the matrix
        for (unsigned int n = 0; n < mat.ncol(); n  )
        {
            for (unsigned int m = 0; m < mat.nrow(); m  )
            {
                mat(m, n) = values[n](m);
            }
        }
        // Set column names
        Rcpp::CharacterVector colnames(keys.size());
        for (unsigned int n = 0; n < keys.size(); n  )
        {
            colnames[n] = keys[n];
        }
        Rcpp::colnames(mat) = colnames;

        return Rcpp::wrap(mat);
    }
}

// [[Rcpp::export]]
std::map<std::string, arma::vec> in_and_out(std::map<std::string, arma::vec> &map)
{
    return map;
}

class Foo
{
public:
    Foo() = default;
    std::map<std::string, arma::vec> testmap;
};

RCPP_MODULE(FooEx)
{
    using namespace Rcpp;
    class_<Foo>("Foo")
        .constructor()
        .field("testmap", &Foo::testmap);
}

/***R
A <- matrix(1:9, ncol = 3, dimnames = list(NULL, c("a", "b", "c")))
print("Using the Foo class from within R")
foo <- new(Foo)
foo$testmap <- A
print(foo$testmap)
print("Calling in_and_out()")
in_and_out(A)
*/

The above works like a charm so I tried to bring that stuff into a package. I made a minimal package to demonstrate the problem. It can be found here. This is what I did:

  • Created package skeleton using Rcpp::Rcpp.package.skeleton(module = TRUE)
  • Removed stuff
  • Added /inst/include/anRpackage.h which contains the as and wrap templates. This file will be added to RcppExports.cpp automatically (see 2.5)
  • Added /src/foo.cpp which contains the class definition and module export
  • Added in_and_out.cpp contains the in_and_out function (as above) just for demo purposes
  • Called Rcpp::compileAttributes() and devtools::load_all()

Compilation fails with:

     192 |       map(const _Compare& __comp,
         |           ~~~~~~~~~~~~~~~~^~~~~~
   /usr/include/c  /9/bits/stl_map.h:183:7: note: candidate: ‘std::map<_Key, _Tp, _Compare, _Alloc>::map() [with _Key = std::__cxx11::basic_string<char>; _Tp = arma::Col<double>; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, arma::Col<double> > >]’
     183 |       map() = default;
         |       ^~~
   /usr/include/c  /9/bits/stl_map.h:183:7: note:   candidate expects 0 arguments, 1 provided
   make: *** [/usr/lib/R/etc/Makeconf:177: foo.o] Error 1
   ERROR: compilation failed for package ‘anRpackage’

Commenting out .field("testmap", &Foo::testmap) makes compilation successful and in_and_out can be used without problems. Any Idea why it is working using sourceCppbut the same code does not compile using the package.skeleton?

Thanks in advance.

CodePudding user response:

I can see the error you post (on map()) but I think that may be a red herring. When I try to build and install your package (and after I change your for loop index variable from unsigned to standard signed int to avoid a little bit of noise) I see compilation failing with

edd@rob:/tmp/berri/rcpp_test(master)$ install2.r -l ../lib/ anRpackage_1.0.tar.gz                                                                                                                                  
* installing *source* package ‘anRpackage’ ...                                                                                                                                                                     
** using staged installation                                                                                                                                                                                       
** libs                                                                                                                                                                                                            
ccache g  -11 -I"/usr/share/R/include" -DNDEBUG  -I'/usr/local/lib/R/site-library/Rcpp/include' -I'/usr/local/lib/R/site-library/RcppArmadillo/include'    -fpic  -g -O3 -Wall -pipe -pedantic  -c RcppExports.cpp 
-o RcppExports.o                                                                                         
ccache g  -11 -I"/usr/share/R/include" -DNDEBUG  -I'/usr/local/lib/R/site-library/Rcpp/include' -I'/usr/local/lib/R/site-library/RcppArmadillo/include'    -fpic  -g -O3 -Wall -pipe -pedantic  -c foo.cpp -o foo.o
In file included from /usr/local/lib/R/site-library/Rcpp/include/Rcpp/as.h:25,                                                                                                                                     
                 from /usr/local/lib/R/site-library/Rcpp/include/RcppCommon.h:168,                                                                                                                                 
                 from /usr/local/lib/R/site-library/RcppArmadillo/include/RcppArmadilloForward.h:25,     
                 from /usr/local/lib/R/site-library/RcppArmadillo/include/RcppArmadillo.h:29,                                                                                                                      
                 from foo.cpp:1:
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/internal/Exporter.h: In instantiation of ‘Rcpp::traits::Exporter<T>::Exporter(SEXP) [with T = std::map<std::__cxx11::basic_string<char>, arma::Col<double> >; SEXP 
= SEXPREC*]’:
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/as.h:87:41:   required from ‘T Rcpp::internal::as(SEXP, Rcpp::traits::r_type_generic_tag) [with T = std::map<std::__cxx11::basic_string<char>, arma::Col<double> >;
 SEXP = SEXPREC*]’
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/as.h:152:31:   required from ‘T Rcpp::as(SEXP) [with T = std::map<std::__cxx11::basic_string<char>, arma::Col<double> >; SEXP = SEXPREC*]’
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/module/Module_Field.h:36:72:   required from ‘void Rcpp::class_<Class>::CppProperty_Getter_Setter<PROP>::set(Class*, SEXP) [with PROP = std::map<std::__cxx11::basi
c_string<char>, arma::Col<double> >; Class = Foo; SEXP = SEXPREC*]’
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/module/Module_Field.h:36:10:   required from here
/usr/local/lib/R/site-library/Rcpp/include/Rcpp/internal/Exporter.h:31:42: error: no matching function for call to ‘std::map<std::__cxx11::basic_string<char>, arma::Col<double> >::map(SEXPREC*&)’
   31 |                     Exporter( SEXP x ) : t(x){}
      |                                          ^~~~

and that too me suggests that you simply may not have gotten as far as you had hoped. You are struggling with the (arguably non-trivial) part of writing extenders. So if I were you I would maybe work on deciding first whether I would want as<> or wrap() convenience, or whether I want Modules -- as I can't immediately think of an example package that does both. Some of the setup between the two may (or may not, I am not perfectly sure on that) get in the way of the other.

Personally, I am also happier with the direct wrappers around R CMD build and R CMD check (where I do use the rcmdcheck package) as I find that devtools obfuscates things and makes it harder to see what is, or isn't happening. But those are just personal preferences; you seem to be a big fan of devtools so maybe you will end up making it work for yourself (c.f. your unanswered question on the mailing list).

I like Modules for their simplicity. I also like simple-enough uses of the as<>() and wrap() extenders. Your suggested use in in_and_out.cpp is ambitious. Maybe just using an external pointer so such structures or maps is quicker -- it is hard to tell.

Edit: As discussed, I was curious aboud a quick minimal existence proof. I just sent you a reduced package doing the as<>() and wrap() converters in this PR #1 to your repo.

  • Related