![](/img/trans.png)
[英]Is there a standard C++ function object for taking apart a std::pair?
[英]C++ interface with Rcpp::interfaces not working for a function returning std::pair
我想為使用Rcpp::interface
返回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){}
這是一個簡單的示例:
#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);
}
這是一個錯誤還是出了什么問題?
但是,編譯器會引發大量錯誤,從以下內容開始:
.../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){}
如Dirk所指出的,此錯誤(以及通常引用Exporter.h或wrap.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
首先創建目錄inst/
和inst/include/
,因為這會導致R在將包安裝在用戶計算機上時將頭文件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
這不是嚴格必要的,但是遵循這是一個合理的約定,尤其是在您有許多頭文件的情況下。 返回上一級, 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
包中( 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
在這種方法中,由於您將只在頭文件中提供一個接口(因此,客戶端程序包中的代碼將需要鏈接到實現),因此您將需要跳個圈來安撫鏈接器,這從來都不是一件有趣的事情時間。 此外,盡管您可以在某種程度上減輕此負擔,但它會比以前增加更多的負擔,如稍后所示。
事不宜遲, 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
,因此接口頭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
如所暗示的那樣,客戶端程序包將需要將其代碼鏈接至interface
才能真正使用 bla()
,並且通過鏈接至我並不是說僅向DESCRIPTION
文件中的LinkingTo
字段添加interface
,具有諷刺意味的是,在編譯中執行鏈接階段。 否則,例如僅包含標頭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 ++接口,則應導出該函數並適當地記錄下來,以便他們了解如何使用它。 它返回的路徑將完全取決於(必要時)從其調用的特定R安裝,但是在我的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
以便正確定位頭文件。 源文件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
包中定義的幫助器功能來確保鏈接器可以使用適當的符號。 構建它並從R中進行測試,
client::call_bla()
# x.first = 1
# x.second = 1
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.