1

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.

BerriJ
  • 913
  • 7
  • 18
  • 1
    Glancing at your repo you may have crossed your own wires. There is a top level directory with package files and then there is _another_ package directory `anRpackage` inside it. That may have something to do with your issues. – Dirk Eddelbuettel Jan 02 '22 at 18:38
  • My bad, I forgot to delete that dir. However, deleting it does not change the outcome. I updated the repo just now. – BerriJ Jan 02 '22 at 18:43

1 Answers1

3

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.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • Thank you very much for this answer. It does make things a bit clearer. It looks like I have to dig deeper into Part 3.3 of https://cran.r-project.org/web/packages/Rcpp/vignettes/Rcpp-extending.pdf. Concerning devtools, well, it's a habit. – BerriJ Jan 02 '22 at 18:59
  • Concerning modules vs. attributes: until now I was pretty successful combining both ;). – BerriJ Jan 02 '22 at 19:01
  • 1
    Yes there is no fundamental reason why they can't play together. `as<>()` and `wrap()` hooks are provided for the compiler, Modules do their thing -- but a _simple_ demo example for the Rcpp Gallery would be nice. – Dirk Eddelbuettel Jan 02 '22 at 19:20
  • 1
    After a break of a few hours I just got back to this and chipped away at it removing Modules as well as RcppArmadillo and have a minimal package with just `as<>` and `wrap`. I find it helps to have minimally viable exitence proofs. One could now add Modules, or RcppArmadillo, or both. – Dirk Eddelbuettel Jan 02 '22 at 22:21
  • 1
    You should also feel free to actually say so in the way that is common on this site. I.e. upvotes and accepted answers are always appreciated. – Dirk Eddelbuettel Jan 02 '22 at 22:47
  • 1
    Thank you again, adding Modules was no problem at all. I don't get armadillo to work but that's not a deal-breaker for me since NumericVector has all the features I need. – BerriJ Jan 03 '22 at 00:16
  • New Day and a fresh look at it: I managed to get it working with arma. The caveat was the #input order that I messed up yesterday. I'll definitely update the Repo (and maybe write a small blog post). – BerriJ Jan 03 '22 at 12:19
  • Cool. And yes, and update at the repo would be a good start. – Dirk Eddelbuettel Jan 03 '22 at 13:18
  • Just pushed it. Added a Readme, a couple of demo functions, and also you as the second author in DESCRIPTION (hope that's okay). You are now also allowed to push to that repo directly. – BerriJ Jan 03 '22 at 16:37
  • Thanks, saw the invite email before I saw this. Renaming is good too. We may want to (eventually ?) pick a more descriptive name. RcppExtMod maybe as shorthand for extensions and module? – Dirk Eddelbuettel Jan 03 '22 at 16:43
  • 1
    RcppExtMod sounds great. I'll rename it when I'm back at the desk. – BerriJ Jan 03 '22 at 17:01
  • 1
    Could toss 'Example' in at the end, there are some precedents. Or just 'Ex' to stay with shorthand. – Dirk Eddelbuettel Jan 03 '22 at 17:07