Rcpp::interfaces 的 C++ 接口不适用于返回 std::pair 的函数
C++ interface with Rcpp::interfaces not working for a function returning std::pair
我想为 return std::pair
使用 Rcpp::interface
的包中的函数提供 C++ 接口。但是,编译器会抛出一大堆错误,从以下内容开始:
.../Rcpp/include/Rcpp/internal/Exporter.h:31:31: error: no matching
function for call to ‘std::pair<int, int>::pair(SEXPREC*&)’
Exporter( SEXP x ) : t(x){}
这是一个简单的例子:
#include <Rcpp.h>
#include <utility>
// [[Rcpp::interfaces(cpp)]]
// [[Rcpp::export]]
std::pair<int, int> bla()
{
return std::make_pair(1,1);
}
此示例函数生成的代码如下所示:
inline std::pair<int, int> bla() {
typedef SEXP(*Ptr_bla)();
static Ptr_bla p_bla = NULL;
if (p_bla == NULL) {
validateSignature("std::pair<int, int>(*bla)()");
p_bla = (Ptr_bla)R_GetCCallable("testinclude", "testinclude_bla");
}
RObject rcpp_result_gen;
{
RNGScope RCPP_rngScope_gen;
rcpp_result_gen = p_bla();
}
if (rcpp_result_gen.inherits("interrupted-error"))
throw Rcpp::internal::InterruptedException();
if (rcpp_result_gen.inherits("try-error"))
throw Rcpp::exception(as<std::string>(rcpp_result_gen).c_str());
return Rcpp::as<std::pair<int, int> >(rcpp_result_gen);
}
这是一个错误还是哪里出了问题?
However, the compiler throws a shitload of errors, starting with:
.../Rcpp/include/Rcpp/internal/Exporter.h:31:31: error: no matching
function for call to ‘std::pair<int, int>::pair(SEXPREC*&)’
Exporter( SEXP x ) : t(x){}
正如德克所指出的,这个错误(通常是引用 Exporter.h 或 wrap.h 的任何错误) 是通过使用 // [[Rcpp::export]]
属性触发的,该属性(尝试)生成将 std::pair<int, int>
转换为 R 知道如何处理的东西所需的适当样板代码(即某种类型的 SEXP
).
根据您的评论
But I don't want to return it back to R at all [...]
你的情况很好,因为这意味着你不必经历编写处理 std::pair<int, int>
的转换器函数的麻烦——只需删除 // [[Rcpp::export]]
声明,然后将处理上述错误消息。
进入问题的实质,
I simply want to use some C++ functions in another package
我可以为您推荐两种方法,顺便说一下,它们都没有使用 // [[Rcpp::interfaces]]
属性。
简单的方法
我假设您提供的示例是您实际用例的简化,但如果可能的话,尽您所能提供一个 header-only 接口 .虽然这种方法存在潜在的缺点(例如讨论 in this question),但它将大大简化您尝试做的事情,并且 IMO,这远远超过额外几分钟编译时间的成本。如果您打算提供严格模板 类 和/或函数的界面,那么生活是美好的,因为无论如何这将是您唯一的选择。
为了演示,请考虑接口包的以下目录结构:
# nathan@nathan-deb:/tmp$ tree hinterface/
# hinterface/
# ├── DESCRIPTION
# ├── inst
# │ └── include
# │ ├── hinterface
# │ │ └── hinterface.hpp
# │ └── hinterface.h
# ├── NAMESPACE
# ├── R
# └── src
# ├── hinterface.cpp
# ├── Makevars
# └── Makevars.win
您首先创建目录 inst/
和 inst/include/
,因为这将导致 R 将 header 文件 hinterface.h
复制到 hinterface
库中当软件包安装在用户机器上时的目录。此外,我还创建了 inst/include/hinterface/
和 hinterface.hpp
,其中包含实现:
#ifndef hinterface__hinterface__hpp
#define hinterface__hinterface__hpp
#include <Rcpp.h>
#include <utility>
namespace hinterface {
inline std::pair<int, int> bla()
{ return std::make_pair(1, 1); }
} // hinterface
#endif // hinterface__hinterface__hpp
这不是绝对必要的,但这是一个合理的约定,尤其是当您有很多 header 文件时。向上移动一级,hinterface.h
文件——客户实际上将其包含在他们的源代码中——包含以下内容:
#ifndef hinterface__hinterface__h
#define hinterface__hinterface__h
#include "hinterface/hinterface.hpp"
// possibly other
// header files
// to include
#endif // hinterface__hinterface__h
在src/
目录下,创建Makevars
和Makevars.win
,每个包含
PKG_CPPFLAGS = -I../inst/include
以及您可能需要设置的任何其他必要的编译器选项。最后,我添加了一个虚拟源文件只是为了使包能够构建,但如果您实际上要导出一个或多个 C++ 函数,则没有必要这样做:
#include "hinterface.h"
void noop() { return; }
在包 hclient
中,它将从 hinterface
包调用 bla
,事情更简单:
# nathan@nathan-deb:/tmp$ tree hclient/
# hclient/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
# ├── hclient.cpp
用户需要做的所有事情(假设包是通过 Rcpp::Rcpp.package.skeleton
从 R 生成的)将 hinterface
添加到 DESCRIPTION
文件中的 LinkingTo
字段,
LinkingTo: Rcpp,
hinterface
在其源文件中添加对 // [[Rcpp::depends(hinterface)]]
属性的调用,并包含 hinterface.h
:
// hclient.cpp
// [[Rcpp::depends(hinterface)]]
#include <Rcpp.h>
#include <hinterface.h>
// [[Rcpp::export]]
void call_bla()
{
std::pair<int, int> x = hinterface::bla();
std::printf(
"x.first = %d\nx.second = %d\n",
x.first, x.second
);
}
构建这个包,我们可以看到它通过从 R 调用它按预期工作:
hclient::call_bla()
# x.first = 1
# x.second = 1
艰难的道路
在这种方法中,因为你真的只会在你的 header 文件中提供一个接口(因此客户端包中的代码将需要 link 到实施),你将需要跳过障碍来安抚 link 人,这从来都不是一个有趣的时刻。此外,它给客户端包带来了比以前更多的负担,尽管您可以在某种程度上减轻这种负担,如后文所示。
事不宜迟,interface
布局如下:
# nathan@nathan-deb:/tmp$ tree interface/
# interface/
# ├── DESCRIPTION
# ├── inst
# │ └── include
# │ └── interface.h
# ├── NAMESPACE
# ├── R
# │ └── libpath.R
# └── src
# ├── bla.cpp
# ├── Makevars
# └── Makevars.win
由于我们不再在 *.hpp
或 *.h
文件中实现 bla
,接口 header、interface.h
将仅包含一个函数原型:
#ifndef interface__interface__h
#define interface__interface__h
#include <Rcpp.h>
#include <utility>
namespace interface {
std::pair<int, int> bla();
} // interface
#endif // interface__interface__h
和以前一样,Makevars
和 Makevars.win
只包含 PKG_CPPFLAGS = -I../inst/include
(以及您可能需要设置的其他标志)。 bla.cpp
非常简单,只包含适当的实现:
#include "interface.h"
namespace interface {
std::pair<int, int> bla()
{ return std::make_pair(1, 1); }
} // interface
如前所述,客户端包将需要 link 他们的代码 interface
以便实际 使用 bla()
-- 并且通过 linking 我并不是说 只是 添加 interface
到 DESCRIPTION
文件中的 LinkingTo
字段,具有讽刺意味的是与编译中的 linking 阶段无关。未能做到这一点——例如only including the header interface.h
-- 将导致 R CMD INSTALL
停止,因为它在尝试加载时找不到合适的符号客户端包。对于用户来说,这可能是一个非常令人沮丧的错误。幸运的是,您可以通过提供类似于以下函数的函数来帮助简化事情,该函数生成 interface
共享库的位置:
# libpath.R
.libpath <- function() {
cat(sprintf(
"%s/interface/libs/interface%s",
installed.packages()["interface","LibPath"][1],
.Platform$dynlib.ext
))
}
我以 .
开头命名它,这样它就不会被导出(默认情况下),因此不需要记录。如果你打算让人们在他们自己的包中使用你的 C++ 接口,你应该导出函数并适当地记录它,以便他们了解如何使用它。 returns 的路径完全取决于调用它的特定 R 安装(necessaily),但在我的 Linux 机器上它看起来像这样:
interface:::.libpath()
# /home/nathan/R/x86_64-pc-linux-gnu-library/3.4/interface/libs/interface.so
继续使用恰当命名的 client
包,
# nathan@nathan-deb:/tmp$ tree client/
# client/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
# ├── client.cpp
# ├── Makevars
# ├── Makevars.win
DESCRIPTION
文件再次需要
LinkingTo: Rcpp,
interface
以便正确定位 header 文件。源文件 client.cpp
如下所示:
// [[Rcpp::depends(interface)]]
#include <Rcpp.h>
#include <interface.h>
// [[Rcpp::export]]
void call_bla()
{
std::pair<int, int> x = interface::bla();
std::printf(
"x.first = %d\nx.second = %d\n",
x.first, x.second
);
}
这与 hclient
包中的源文件没有什么不同。 Makevars
和 Makevars.win
变得有趣,现在包含
PKG_LIBS += `${R_HOME}/bin/Rscript -e "cat(interface:::.libpath())"`
这里我们使用 interface
包中定义的辅助函数来确保 linker 可以使用适当的符号。从 R 构建并测试它,
client::call_bla()
# x.first = 1
# x.second = 1
我想为 return std::pair
使用 Rcpp::interface
的包中的函数提供 C++ 接口。但是,编译器会抛出一大堆错误,从以下内容开始:
.../Rcpp/include/Rcpp/internal/Exporter.h:31:31: error: no matching
function for call to ‘std::pair<int, int>::pair(SEXPREC*&)’
Exporter( SEXP x ) : t(x){}
这是一个简单的例子:
#include <Rcpp.h>
#include <utility>
// [[Rcpp::interfaces(cpp)]]
// [[Rcpp::export]]
std::pair<int, int> bla()
{
return std::make_pair(1,1);
}
此示例函数生成的代码如下所示:
inline std::pair<int, int> bla() {
typedef SEXP(*Ptr_bla)();
static Ptr_bla p_bla = NULL;
if (p_bla == NULL) {
validateSignature("std::pair<int, int>(*bla)()");
p_bla = (Ptr_bla)R_GetCCallable("testinclude", "testinclude_bla");
}
RObject rcpp_result_gen;
{
RNGScope RCPP_rngScope_gen;
rcpp_result_gen = p_bla();
}
if (rcpp_result_gen.inherits("interrupted-error"))
throw Rcpp::internal::InterruptedException();
if (rcpp_result_gen.inherits("try-error"))
throw Rcpp::exception(as<std::string>(rcpp_result_gen).c_str());
return Rcpp::as<std::pair<int, int> >(rcpp_result_gen);
}
这是一个错误还是哪里出了问题?
However, the compiler throws a shitload of errors, starting with:
.../Rcpp/include/Rcpp/internal/Exporter.h:31:31: error: no matching function for call to ‘std::pair<int, int>::pair(SEXPREC*&)’ Exporter( SEXP x ) : t(x){}
正如德克所指出的,这个错误(通常是引用 Exporter.h 或 wrap.h 的任何错误) 是通过使用 // [[Rcpp::export]]
属性触发的,该属性(尝试)生成将 std::pair<int, int>
转换为 R 知道如何处理的东西所需的适当样板代码(即某种类型的 SEXP
).
根据您的评论
But I don't want to return it back to R at all [...]
你的情况很好,因为这意味着你不必经历编写处理 std::pair<int, int>
的转换器函数的麻烦——只需删除 // [[Rcpp::export]]
声明,然后将处理上述错误消息。
进入问题的实质,
I simply want to use some C++ functions in another package
我可以为您推荐两种方法,顺便说一下,它们都没有使用 // [[Rcpp::interfaces]]
属性。
简单的方法
我假设您提供的示例是您实际用例的简化,但如果可能的话,尽您所能提供一个 header-only 接口 .虽然这种方法存在潜在的缺点(例如讨论 in this question),但它将大大简化您尝试做的事情,并且 IMO,这远远超过额外几分钟编译时间的成本。如果您打算提供严格模板 类 和/或函数的界面,那么生活是美好的,因为无论如何这将是您唯一的选择。
为了演示,请考虑接口包的以下目录结构:
# nathan@nathan-deb:/tmp$ tree hinterface/
# hinterface/
# ├── DESCRIPTION
# ├── inst
# │ └── include
# │ ├── hinterface
# │ │ └── hinterface.hpp
# │ └── hinterface.h
# ├── NAMESPACE
# ├── R
# └── src
# ├── hinterface.cpp
# ├── Makevars
# └── Makevars.win
您首先创建目录 inst/
和 inst/include/
,因为这将导致 R 将 header 文件 hinterface.h
复制到 hinterface
库中当软件包安装在用户机器上时的目录。此外,我还创建了 inst/include/hinterface/
和 hinterface.hpp
,其中包含实现:
#ifndef hinterface__hinterface__hpp
#define hinterface__hinterface__hpp
#include <Rcpp.h>
#include <utility>
namespace hinterface {
inline std::pair<int, int> bla()
{ return std::make_pair(1, 1); }
} // hinterface
#endif // hinterface__hinterface__hpp
这不是绝对必要的,但这是一个合理的约定,尤其是当您有很多 header 文件时。向上移动一级,hinterface.h
文件——客户实际上将其包含在他们的源代码中——包含以下内容:
#ifndef hinterface__hinterface__h
#define hinterface__hinterface__h
#include "hinterface/hinterface.hpp"
// possibly other
// header files
// to include
#endif // hinterface__hinterface__h
在src/
目录下,创建Makevars
和Makevars.win
,每个包含
PKG_CPPFLAGS = -I../inst/include
以及您可能需要设置的任何其他必要的编译器选项。最后,我添加了一个虚拟源文件只是为了使包能够构建,但如果您实际上要导出一个或多个 C++ 函数,则没有必要这样做:
#include "hinterface.h"
void noop() { return; }
在包 hclient
中,它将从 hinterface
包调用 bla
,事情更简单:
# nathan@nathan-deb:/tmp$ tree hclient/
# hclient/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
# ├── hclient.cpp
用户需要做的所有事情(假设包是通过 Rcpp::Rcpp.package.skeleton
从 R 生成的)将 hinterface
添加到 DESCRIPTION
文件中的 LinkingTo
字段,
LinkingTo: Rcpp,
hinterface
在其源文件中添加对 // [[Rcpp::depends(hinterface)]]
属性的调用,并包含 hinterface.h
:
// hclient.cpp
// [[Rcpp::depends(hinterface)]]
#include <Rcpp.h>
#include <hinterface.h>
// [[Rcpp::export]]
void call_bla()
{
std::pair<int, int> x = hinterface::bla();
std::printf(
"x.first = %d\nx.second = %d\n",
x.first, x.second
);
}
构建这个包,我们可以看到它通过从 R 调用它按预期工作:
hclient::call_bla()
# x.first = 1
# x.second = 1
艰难的道路
在这种方法中,因为你真的只会在你的 header 文件中提供一个接口(因此客户端包中的代码将需要 link 到实施),你将需要跳过障碍来安抚 link 人,这从来都不是一个有趣的时刻。此外,它给客户端包带来了比以前更多的负担,尽管您可以在某种程度上减轻这种负担,如后文所示。
事不宜迟,interface
布局如下:
# nathan@nathan-deb:/tmp$ tree interface/
# interface/
# ├── DESCRIPTION
# ├── inst
# │ └── include
# │ └── interface.h
# ├── NAMESPACE
# ├── R
# │ └── libpath.R
# └── src
# ├── bla.cpp
# ├── Makevars
# └── Makevars.win
由于我们不再在 *.hpp
或 *.h
文件中实现 bla
,接口 header、interface.h
将仅包含一个函数原型:
#ifndef interface__interface__h
#define interface__interface__h
#include <Rcpp.h>
#include <utility>
namespace interface {
std::pair<int, int> bla();
} // interface
#endif // interface__interface__h
和以前一样,Makevars
和 Makevars.win
只包含 PKG_CPPFLAGS = -I../inst/include
(以及您可能需要设置的其他标志)。 bla.cpp
非常简单,只包含适当的实现:
#include "interface.h"
namespace interface {
std::pair<int, int> bla()
{ return std::make_pair(1, 1); }
} // interface
如前所述,客户端包将需要 link 他们的代码 interface
以便实际 使用 bla()
-- 并且通过 linking 我并不是说 只是 添加 interface
到 DESCRIPTION
文件中的 LinkingTo
字段,具有讽刺意味的是与编译中的 linking 阶段无关。未能做到这一点——例如only including the header interface.h
-- 将导致 R CMD INSTALL
停止,因为它在尝试加载时找不到合适的符号客户端包。对于用户来说,这可能是一个非常令人沮丧的错误。幸运的是,您可以通过提供类似于以下函数的函数来帮助简化事情,该函数生成 interface
共享库的位置:
# libpath.R
.libpath <- function() {
cat(sprintf(
"%s/interface/libs/interface%s",
installed.packages()["interface","LibPath"][1],
.Platform$dynlib.ext
))
}
我以 .
开头命名它,这样它就不会被导出(默认情况下),因此不需要记录。如果你打算让人们在他们自己的包中使用你的 C++ 接口,你应该导出函数并适当地记录它,以便他们了解如何使用它。 returns 的路径完全取决于调用它的特定 R 安装(necessaily),但在我的 Linux 机器上它看起来像这样:
interface:::.libpath()
# /home/nathan/R/x86_64-pc-linux-gnu-library/3.4/interface/libs/interface.so
继续使用恰当命名的 client
包,
# nathan@nathan-deb:/tmp$ tree client/
# client/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
# ├── client.cpp
# ├── Makevars
# ├── Makevars.win
DESCRIPTION
文件再次需要
LinkingTo: Rcpp,
interface
以便正确定位 header 文件。源文件 client.cpp
如下所示:
// [[Rcpp::depends(interface)]]
#include <Rcpp.h>
#include <interface.h>
// [[Rcpp::export]]
void call_bla()
{
std::pair<int, int> x = interface::bla();
std::printf(
"x.first = %d\nx.second = %d\n",
x.first, x.second
);
}
这与 hclient
包中的源文件没有什么不同。 Makevars
和 Makevars.win
变得有趣,现在包含
PKG_LIBS += `${R_HOME}/bin/Rscript -e "cat(interface:::.libpath())"`
这里我们使用 interface
包中定义的辅助函数来确保 linker 可以使用适当的符号。从 R 构建并测试它,
client::call_bla()
# x.first = 1
# x.second = 1