与Rcpp :: interfaces的C++接口不适用于返回std :: pair的函数

NoB*_*own 1 c++ rcpp

我想为一个返回std::pair使用的包中的函数提供一个C++接口Rcpp::interface.但是,编译器会抛出大量错误,从以下开始:

.../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){}
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子:

#include <Rcpp.h>
#include <utility>

// [[Rcpp::interfaces(cpp)]]

// [[Rcpp::export]]
std::pair<int, int> bla()
{
  return std::make_pair(1,1);
}
Run Code Online (Sandbox Code Playgroud)

此示例函数的生成代码如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

这是一个错误还是这里出了什么问题?

nru*_*ell 5

但是,编译器会抛出大量错误,从以下开始:

.../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){}
Run Code Online (Sandbox Code Playgroud)

正如Dirk所指出的,这个错误(通常是引用Exporter.hwrap.h的任何错误)都是通过使用// [[Rcpp::export]]属性触发的,该属性(尝试)生成需要的相应样板代码,以便将其std::pair<int, int>转换为R知道的内容如何处理(即某种类型SEXP).

根据您的评论

但我不想把它全部还给R [...]

你处于一个良好的状态,因为这意味着你不必经历编写处理转换器函数的麻烦std::pair<int, int>- 只需删除// [[Rcpp::export]]声明,并将处理上述错误消息.


关于问题的关键,

我只是想在另一个包中使用一些C++函数

我可以为你提出两种方法,顺便说一句,他们都没有使用该// [[Rcpp::interfaces]]属性.


简单的方法

我假设您提供的示例是对实际用例的简化,但如果可能的话,尽一切力量提供仅限标题的界面.虽然这种方法存在潜在的缺点(例如在这个问题中讨论过),但它会大大简化你要做的事情,而IMO,这远远超过了额外几分钟编译时间的成本.如果您计划提供严格模板类和/或功能的界面,那么生活是美好的,因为这将是您唯一的选择.

要演示,请考虑接口包的以下目录结构:

# nathan@nathan-deb:/tmp$ tree hinterface/
# hinterface/
# ??? DESCRIPTION
# ??? inst
# ?   ??? include
# ?       ??? hinterface
# ?       ?   ??? hinterface.hpp
# ?       ??? hinterface.h
# ??? NAMESPACE
# ??? R
# ??? src
#     ??? hinterface.cpp
#     ??? Makevars
#     ??? Makevars.win
Run Code Online (Sandbox Code Playgroud)

首先,创建目录inst/inst/include/,因为这将导致R键头文件复制hinterface.hhinterface当安装在用户的机器上的包库目录.另外,我已创建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
Run Code Online (Sandbox Code Playgroud)

这不是绝对必要的,但它是一个合理的约定,特别是如果你有许多头文件.向后移动一级,hinterface.h文件 - 客户端实际将包含在其源代码中 - 包含以下内容:

#ifndef hinterface__hinterface__h
#define hinterface__hinterface__h

#include "hinterface/hinterface.hpp"
// possibly other
// header files
// to include

#endif // hinterface__hinterface__h
Run Code Online (Sandbox Code Playgroud)

src/目录中,创建一个MakevarsMakevars.win,每个包含

PKG_CPPFLAGS = -I../inst/include
Run Code Online (Sandbox Code Playgroud)

以及您可能需要设置的任何其他必要的编译器选项.最后,我添加了一个虚拟源文件,只是为了使包能够构建,但是如果你实际上是在导出一个或多个C++函数,那么就没有必要:

#include "hinterface.h"

void noop() { return; }
Run Code Online (Sandbox Code Playgroud)

在将从包中hclient调用blahinterface包中,事情甚至更简单:

# nathan@nathan-deb:/tmp$ tree hclient/
# hclient/
# ??? DESCRIPTION
# ??? NAMESPACE
# ??? R
# ??? src
#     ??? hclient.cpp
Run Code Online (Sandbox Code Playgroud)

所有用户需要做的(假设包是从R via生成的Rcpp::Rcpp.package.skeleton)添加hinterface到文件中的LinkingTo字段DESCRIPTION,

LinkingTo: Rcpp,
    hinterface
Run Code Online (Sandbox Code Playgroud)

// [[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
    );
}
Run Code Online (Sandbox Code Playgroud)

构建这个包,我们可以看到它通过从R调用它按预期工作:

hclient::call_bla()
# x.first = 1
# x.second = 1
Run Code Online (Sandbox Code Playgroud)

困难的方式

在这种方法中,因为你真的只在头文件中提供一个接口(因此客户端包中的代码需要链接到实现),你需要跳过箍来安抚链接器,这从来都不是一个好玩的时间.此外,它会给客户端程序包带来比以前更多的负担,尽管您可以在某种程度上减轻这种负担,如稍后所示.

没有进一步的麻烦,interface布局如下:

# nathan@nathan-deb:/tmp$ tree interface/
# interface/
# ??? DESCRIPTION
# ??? inst
# ?   ??? include
# ?       ??? interface.h
# ??? NAMESPACE
# ??? R
# ?   ??? libpath.R
# ??? src
#     ??? bla.cpp
#     ??? Makevars
#     ??? Makevars.win
Run Code Online (Sandbox Code Playgroud)

由于我们不再bla在一个*.hpp*.h文件中实现,所以接口头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
Run Code Online (Sandbox Code Playgroud)

和以前一样,MakevarsMakevars.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
Run Code Online (Sandbox Code Playgroud)

如上提到的,客户端软件包需要将它们的代码链接interface到实际使用 bla() - 并且通过链接到我并不仅仅意味着添加interface到文件中的LinkingTo字段DESCRIPTION,这与编译中的链接阶段没有任何关系.如果不这样做 - 例如包括标题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
    ))
}
Run Code Online (Sandbox Code Playgroud)

我将其命名为a,.以便它不会被导出(默认情况下),因此不需要记录.如果您打算让人们在自己的包中使用您的C++接口,您应该导出该函数并对其进行适当的记录,以便他们了解如何使用它.它返回的路径将完全取决于从(必要)调用的特定R安装,但在我的Linux机器上它看起来像这样:

interface:::.libpath()
# /home/nathan/R/x86_64-pc-linux-gnu-library/3.4/interface/libs/interface.so
Run Code Online (Sandbox Code Playgroud)

转到适当命名的client包,

# nathan@nathan-deb:/tmp$ tree client/
# client/
# ??? DESCRIPTION
# ??? NAMESPACE
# ??? R
# ??? src
#     ??? client.cpp
#     ??? Makevars
#     ??? Makevars.win
Run Code Online (Sandbox Code Playgroud)

DESCRIPTION文件再次需要

LinkingTo: Rcpp,
        interface
Run Code Online (Sandbox Code Playgroud)

以便头文件正确定位.源文件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
    );
}
Run Code Online (Sandbox Code Playgroud)

这与hclient包中的源文件没有什么不同.事情变得有趣的是,在MakevarsMakevars.win,现在包含

PKG_LIBS += `${R_HOME}/bin/Rscript -e "cat(interface:::.libpath())"`
Run Code Online (Sandbox Code Playgroud)

这里我们使用interface包中定义的辅助函数来确保链接器可以使用适当的符号.构建它并从R中测试它,

client::call_bla()
# x.first = 1
# x.second = 1
Run Code Online (Sandbox Code Playgroud)