使用 Rcpp 模块字段将自定义类型公开给 R 生成编译

Expose custom type to R using Rcpp Modules field produces compilation

我经常在 C++ 中使用 std::map<std::string, arma::vec>,所以我编写了自定义 aswrap 模板来处理 R-C++ 转换。下面是一个最小的代表:


// [[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)
*/

上面的东西很有魅力,所以我试着把这些东西打包成一个包。我做了一个最小的包来演示这个问题。可以找到here。 这就是我所做的:

编译失败:

     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’

注释掉.field("testmap", &Foo::testmap)编译成功,in_and_out可以正常使用。 知道为什么它可以使用 sourceCpp 但相同的代码不能使用 package.skeleton 编译吗?

提前致谢。

我可以看到您 post(在 map() 上)的错误,但我认为这可能是一个转移注意力的问题。当我尝试构建和安装你的包时(在我将你的 for 循环索引变量从 unsigned 更改为标准签名 int 以避免一点噪音之后)我看到编译失败

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){}
      |                                          ^~~~

这也表明你可能没有达到你希望的那么远。您正在为编写扩展器的(可以说是非常重要的)部分而苦苦挣扎。所以如果我是你,我可能会首先决定我是想要 as<> 还是 wrap() 方便,或者我是否想要模块——因为我无法立即想到一个可以同时完成这两个功能的示例包.两者之间的某些设置可能(或可能不会,我对此不是很确定)会妨碍对方。

就我个人而言,我也更喜欢 R CMD buildR CMD check 的直接包装(我确实使用 rcmdcheck 包),因为我发现 devtools 混淆了事情并使人们更难看到正在发生或没有发生的事情。但这些只是个人喜好;您似乎是 devtools 的忠实粉丝,所以也许您最终会使其为自己所用(c.f。邮件列表中您未回答的问题)。

我喜欢模块,因为它们很简单。我也喜欢 as<>()wrap() 扩展器的简单使用。您在 in_and_out.cpp 中的建议用途是雄心勃勃的。也许只是使用外部指针,这样的结构或映射会更快——这很难说。

编辑: 正如所讨论的,我很好奇一个快速的 最小 存在证明。我刚刚向您发送了一个缩减包,用于在 this PR #1 to your repo.

中执行 as<>()wrap() 转换器