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 (i.e., some type of SEXP
).
Based on your comment
But I don't want to return it back to R at all [...]
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.
On to the meat of the problem,
I simply want to use some C++ functions in another package
I can propose two approaches for you, and incidentally, neither of them use the // [[Rcpp::interfaces]]
attribute.
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 (e.g. 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. 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. Additionally, I've created inst/include/hinterface/
, and hinterface.hpp
, which contains the implementation:
#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:
#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
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:
#include "hinterface.h"
void noop() { return; }
In the package hclient
, which will be calling bla
from the hinterface
package, things are even simpler:
# 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,
LinkingTo: Rcpp,
hinterface
add a call to the // [[Rcpp::depends(hinterface)]]
attribute in their source file, and include 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:
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:
# 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:
#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). bla.cpp
is pretty straight forward, and just contains the appropriate implementation:
#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. Failure to do this -- e.g. 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. 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:
# 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. 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:
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,
# nathan@nathan-deb:/tmp$ tree client/
# client/
# ├── DESCRIPTION
# ├── NAMESPACE
# ├── R
# └── src
# ├── client.cpp
# ├── Makevars
# ├── Makevars.win
The DESCRIPTION
file again needs
LinkingTo: Rcpp,
interface
so that the header file is located correctly. The source file, client.cpp
, looks like this:
// [[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. Where things get interesting is in Makevars
and Makevars.win
, which now contain
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. Building this and testing it out from R,
client::call_bla()
# x.first = 1
# x.second = 1