使用 Rcpp 模块字段将自定义类型公开给 R 生成编译
Expose custom type to R using Rcpp Modules field produces compilation
我经常在 C++ 中使用 std::map<std::string, arma::vec>
,所以我编写了自定义 as
和 wrap
模板来处理 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。
这就是我所做的:
- 使用
Rcpp::Rcpp.package.skeleton(module = TRUE)
创建了程序包框架
- 已删除内容
- 已添加 /inst/include/anRpackage.h which contains the
as
and wrap
templates. This file will be added to RcppExports.cpp
automatically (see 2.5)
- 添加了 /src/foo.cpp,其中包含 class 定义和模块导出
- 已添加 in_and_out.cpp 包含
in_and_out
函数(如上)仅用于演示目的
- 调用了
Rcpp::compileAttributes()
和 devtools::load_all()
编译失败:
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 build
和 R CMD check
的直接包装(我确实使用 rcmdcheck
包),因为我发现 devtools
混淆了事情并使人们更难看到正在发生或没有发生的事情。但这些只是个人喜好;您似乎是 devtools
的忠实粉丝,所以也许您最终会使其为自己所用(c.f。邮件列表中您未回答的问题)。
我喜欢模块,因为它们很简单。我也喜欢 as<>()
和 wrap()
扩展器的简单使用。您在 in_and_out.cpp
中的建议用途是雄心勃勃的。也许只是使用外部指针,这样的结构或映射会更快——这很难说。
编辑: 正如所讨论的,我很好奇一个快速的 最小 存在证明。我刚刚向您发送了一个缩减包,用于在 this PR #1 to your repo.
中执行 as<>()
和 wrap()
转换器
我经常在 C++ 中使用 std::map<std::string, arma::vec>
,所以我编写了自定义 as
和 wrap
模板来处理 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。 这就是我所做的:
- 使用
Rcpp::Rcpp.package.skeleton(module = TRUE)
创建了程序包框架
- 已删除内容
- 已添加 /inst/include/anRpackage.h which contains the
as
andwrap
templates. This file will be added toRcppExports.cpp
automatically (see 2.5) - 添加了 /src/foo.cpp,其中包含 class 定义和模块导出
- 已添加 in_and_out.cpp 包含
in_and_out
函数(如上)仅用于演示目的 - 调用了
Rcpp::compileAttributes()
和devtools::load_all()
编译失败:
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 build
和 R CMD check
的直接包装(我确实使用 rcmdcheck
包),因为我发现 devtools
混淆了事情并使人们更难看到正在发生或没有发生的事情。但这些只是个人喜好;您似乎是 devtools
的忠实粉丝,所以也许您最终会使其为自己所用(c.f。邮件列表中您未回答的问题)。
我喜欢模块,因为它们很简单。我也喜欢 as<>()
和 wrap()
扩展器的简单使用。您在 in_and_out.cpp
中的建议用途是雄心勃勃的。也许只是使用外部指针,这样的结构或映射会更快——这很难说。
编辑: 正如所讨论的,我很好奇一个快速的 最小 存在证明。我刚刚向您发送了一个缩减包,用于在 this PR #1 to your repo.
中执行as<>()
和 wrap()
转换器