简体   繁体   English

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

[英]C++ interface with Rcpp::interfaces not working for a function returning std::pair

I would like to provide a C++ interface for a function within a package which return std::pair using Rcpp::interface . 我想为使用Rcpp::interface返回std::pair的包中的函数提供一个C ++ Rcpp::interface 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){}

Here is a simple example: 这是一个简单的示例:

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

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

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

The generated code for this example function looks like: 此示例函数生成的代码如下:

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);
}

Is this a bug or what is going wrong here? 这是一个错误还是出了什么问题?

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

As pointed out by Dirk, this error (and generally any error citing Exporter.h or wrap.h ) was triggered by the use of the // [[Rcpp::export]] attribute, which (attempts) to generate the appropriate boilerplate code needed to turn the std::pair<int, int> into something that R knows how to handle (ie, some type of SEXP ). 如Dirk所指出的,此错误(以及通常引用Exporter.hwrap.h的任何错误)是通过使用// [[Rcpp::export]]属性触发的(尝试生成适当的样板)将std::pair<int, int>转换成R知道如何处理的代码(即某种类型的SEXP )。

Based on your comment 根据您的评论

But I don't want to return it back to R at all [...] 但是我根本不想将其返回给R [...]

you are in a good situation because this means you won't have to go through the trouble of writing a converter function that handles std::pair<int, int> -- simply remove the // [[Rcpp::export]] declaration, and that will take care of the above error message. 您处在良好的状态,因为这意味着您不必麻烦编写处理std::pair<int, int>的转换器函数-只需删除// [[Rcpp::export]]声明,它将处理上述错误消息。


On to the meat of the problem, 关于问题的实质,

I simply want to use some C++ functions in another package 我只是想在另一个包中使用一些C ++函数

I can propose two approaches for you, and incidentally, neither of them use the // [[Rcpp::interfaces]] attribute. 我可以为您提供两种方法,顺便说一句, 它们都不使用// [[Rcpp::interfaces]]属性。


The Easy Way 简单的方法

I assume the example you provided is a simplification of your actual use case, but if at all possible, do everything in your power to provide a header-only interface . 我假设您提供的示例是您实际使用情况的简化,但是,如果有可能, 请尽一切可能提供仅标头的接口 While there are potential drawbacks to this approach (eg discussed in this question ), it will greatly simplify what you are trying to do, and IMO, this far outweighs the cost of an extra couple of minutes of compilation time. 尽管这种方法有潜在的弊端(例如, 在本问题中进行了讨论),但它将大大简化您要尝试执行的操作,而IMO,这远远超过了额外几分钟的编译时间。 If you were planning on providing an interface of strictly template classes and / or functions then life is good, because this would be your only option anyways. 如果您打算提供严格的模板类和/或函数的接口,那么生活就很好了,因为无论如何这是您唯一的选择。

To demonstrate, consider the following directory structure for the interface package: 为了演示,请考虑以下接口包的目录结构:

# nathan@nathan-deb:/tmp$ tree hinterface/
# hinterface/
# ├── DESCRIPTION
# ├── inst
# │   └── include
# │       ├── hinterface
# │       │   └── hinterface.hpp
# │       └── hinterface.h
# ├── NAMESPACE
# ├── R
# └── src
#     ├── hinterface.cpp
#     ├── Makevars
#     └── Makevars.win

You begin by creating the directories inst/ and inst/include/ , as this will cause R to copy the header file hinterface.h into the hinterface library directory when the package is installed on a user's machine. 首先创建目录inst/inst/include/ ,因为这会导致R在将包安装在用户计算机上时将头文件hinterface.h复制到hinterface库目录中。 Additionally, I've created inst/include/hinterface/ , and hinterface.hpp , which contains the implementation: 此外,我创建了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

This is not strictly necessary, but it is a reasonable convention to follow, especially if you have many header files. 这不是严格必要的,但是遵循这是一个合理的约定,尤其是在您有许多头文件的情况下。 Moving back up one level, the hinterface.h file -- which clients will actually include in their source code -- contains this: 返回上一级, hinterface.h文件(客户端实际上将在其源代码中包括)包含以下内容:

#ifndef hinterface__hinterface__h
#define hinterface__hinterface__h

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

#endif // hinterface__hinterface__h

In the src/ directory, create a Makevars and Makevars.win , each containing src/目录中,创建一个MakevarsMakevars.win ,每个包含

PKG_CPPFLAGS = -I../inst/include

and any other necessary compiler options you may need to set. 以及您可能需要设置的任何其他必要的编译器选项。 Finally, I've added a dummy source file just to enable the package to build, but if you were actually exporting one or more C++ functions this would not be necessary: 最后,我添加了一个虚拟源文件以使程序包能够生成,但是如果您实际上是在导出一个或多个C ++函数,则没有必要:

#include "hinterface.h"

void noop() { return; }

In the package hclient , which will be calling bla from the hinterface package, things are even simpler: hclient包中( hclienthinterface包中调用bla ,事情变得更加简单:

# nathan@nathan-deb:/tmp$ tree hclient/
# hclient/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
#     ├── hclient.cpp

All a user needs to do (assuming the packages was generated from R via Rcpp::Rcpp.package.skeleton ) add hinterface to the LinkingTo field in the DESCRIPTION file, 用户需要做的所有事情(假设软件包是通过Rcpp::Rcpp.package.skeleton从R生成的),将hinterface添加到DESCRIPTION文件中的LinkingTo字段中,

LinkingTo: Rcpp,
    hinterface

add a call to the // [[Rcpp::depends(hinterface)]] attribute in their source file, and include hinterface.h : 在其源文件中添加对// [[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
    );
}

Building this package, we can see that it works as expected by calling it from R: 构建此程序包,我们可以通过从R调用它来看到它按预期工作:

hclient::call_bla()
# x.first = 1
# x.second = 1

The Hard Way 艰难的道路

In this approach, since you will truly only be providing an interface in your header files (and thus code in client packages will need to link to the implementation), you will need to jump through hoops to appease the linker, which is never a fun time. 在这种方法中,由于您将只在头文件中提供一个接口(因此,客户端程序包中的代码将需要链接到实现),因此您将需要跳个圈来安抚链接器,这从来都不是一件有趣的事情时间。 Futhermore, it places more of a burden on the client package than before, although you can mitigate this to some degree as shown later. 此外,尽管您可以在某种程度上减轻此负担,但它会比以前增加更多的负担,如稍后所示。

Without further ado, interface is laid out like this: 事不宜迟, interface的布局如下:

# nathan@nathan-deb:/tmp$ tree interface/
# interface/
# ├── DESCRIPTION
# ├── inst
# │   └── include
# │       └── interface.h
# ├── NAMESPACE
# ├── R
# │   └── libpath.R
# └── src
#     ├── bla.cpp
#     ├── Makevars
#     └── Makevars.win

Since we are no longer implementing bla in a *.hpp or *.h file, the interface header, interface.h , will only contain a function prototype: 由于我们不再在*.hpp*.h文件中实现bla ,因此接口头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

As before, Makevars and Makevars.win simply contain PKG_CPPFLAGS = -I../inst/include (and other flags you may need to set). 和以前一样, MakevarsMakevars.win仅包含PKG_CPPFLAGS = -I../inst/include (以及其他可能需要设置的标志)。 bla.cpp is pretty straight forward, and just contains the appropriate implementation: bla.cpp非常简单,仅包含适当的实现:

#include "interface.h"

namespace interface {

std::pair<int, int> bla()
{ return std::make_pair(1, 1); }

} // interface

As alluded to, client packages will need to link their code to interface in order to actually use bla() -- and by linking to I don't mean just adding interface to the LinkingTo field in the DESCRIPTION file, which ironically has nothing to do with the linking phase in compilation. 如所暗示的那样,客户端程序包将需要将其代码链接至interface才能真正使用 bla() ,并且通过链接至我并不是说DESCRIPTION文件中的LinkingTo字段添加interface ,具有讽刺意味的是,在编译中执行链接阶段。 Failure to do this -- eg only including the header interface.h -- will cause R CMD INSTALL to halt, as it will fail to find the appropriate symbols when it attempts to load the client package. 否则,例如包含标头interface.h ,将导致R CMD INSTALL暂停,因为它将在尝试加载客户端程序包时找不到合适的符号。 This can be a very frustrating error for users to deal with. 对于用户来说,这可能是一个非常令人沮丧的错误。 Fortunately, you can help make things easier by providing something like the following function, which generates the location of the interface shared library: 幸运的是,您可以通过提供类似于以下功能的东西来简化事情,该函数生成interface共享库的位置:

# libpath.R
.libpath <- function() {
    cat(sprintf(
        "%s/interface/libs/interface%s",
        installed.packages()["interface","LibPath"][1],
        .Platform$dynlib.ext
    ))
}

I named this beginning with a . 我以开头的名字. so that it would not be exported (by default) and thus not require being documented. 这样就不会导出(默认情况下),因此不需要进行记录。 If you intended for people to uses your C++ interface in their own packages, you should export the function and document it appropriately so that they understand how to use it. 如果您打算让人们在自己的程序包中使用C ++接口,则应导出该函数并适当地记录下来,以便他们了解如何使用它。 The path it returns will depending entirely on the specific R installation it is called from (necessarily), but on my Linux machine it looks like this: 它返回的路径将完全取决于(必要时)从其调用的特定R安装,但是在我的Linux机器上,它看起来像这样:

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

Moving on to the aptly named client package, 转到适当命名的client程序包,

# nathan@nathan-deb:/tmp$ tree client/
# client/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
#     ├── client.cpp
#     ├── Makevars
#     ├── Makevars.win

The DESCRIPTION file again needs 再次需要DESCRIPTION文件

LinkingTo: Rcpp,
        interface

so that the header file is located correctly. 以便正确定位头文件。 The source file, client.cpp , looks like this: 源文件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
    );
}

This is no different than the source file in the hclient package. 这与hclient软件包中的源文件没有什么不同。 Where things get interesting is in Makevars and Makevars.win , which now contain 有趣的地方是MakevarsMakevars.win ,现在包含

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

Here we are using the helper function defined in the interface package to make sure the appropriate symbols are available to the linker. 在这里,我们使用在interface包中定义的帮助器功能来确保链接器可以使用适当的符号。 Building this and testing it out from R, 构建它并从R中进行测试,

client::call_bla()
# x.first = 1
# x.second = 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM