-
-
Notifications
You must be signed in to change notification settings - Fork 214
header only Rcpp
Rcpp is now "header only". Well, not really, but for the purpose of this document that is an accurate enough description.
This means client packages no longer need Makevars
and Makevars.win
, nor need to deal with what we used to call the Rcpp user library. No such thing anymore, we just don't build it anymore. No more need to use Rcpp:::LdFlags
. The only thing that is needed from now is:
LinkingTo: Rcpp
There also appears to be a need for an explicit import statement in the file NAMESPACE
. Just using import(Rcpp)
is unfortunately not sufficient, but importing a function (actually: any function) works:
importFrom(Rcpp, evalCpp)
as Rcpp:::CxxFlags
had been unnecessary for a while.
The first step of this conversion was to move a lot of code to headers. This was achieved thanks to inlining and generalization through templates.
This is pretty simple. Make a function inline, move it to the headers. Done.
If you look into the details, you'll see that classes from the Rcpp
API are now instantiations of template classes instead of classes. For example, RObject
looks like this:
template < template <class> class StoragePolicy > class RObject_Impl :
public StoragePolicy<RObject_Impl<StoragePolicy> >,
public SlotProxyPolicy<RObject_Impl<StoragePolicy> >,
public AttributeProxyPolicy<RObject_Impl<StoragePolicy> >,
public RObjectMethods< RObject_Impl<StoragePolicy> >
{
...
}
typedef RObject_Impl<PreserveStorage> RObject ;
So RObject
is the instantiation of RObject_Impl
where the storage is controlled by the PreserveStorage
template. This is policy design class. If you're curious about this, you can look into the
Modern C++ design book.
For most of use it changes nothing. You'll still be able to use RObject
or any Rcpp
API class as before.
What the storage policy controls is protection of the underlying SEXP
from the garbage collector. The PreserveStorage
template is used as default. It protects the SEXP
the same way we did before, i.e. with R_PreserveObject
/R_ReleaseObject
.
But we could imagine to create a LocalStorage
template suitable for StoragePolicy
that would use the PROTECT/UNPROTECT
stack instead, or another template doing no protection at all. It sometimes makes sense when we know an object is already protected. For example symbols don't need protection since they're cached.
Anyway, so classes from the Rcpp
API are now instantiations of policy based templates.
Sometimes, we could not move things to templates or to inline functions, because we need the function to exist only once, right inside Rcpp. For this we've used R's mechanism for registering functions. The mechanism is described in writing R extensions.
The idea is that an actual function is compiled in the shared library of Rcpp
and when Rcpp
is loaded, the function is registered by using the R_RegisterCCallable
void registerFunctions(){
using namespace Rcpp ;
using namespace Rcpp::internal ;
#define RCPP_REGISTER(__FUN__) R_RegisterCCallable( "Rcpp", #__FUN__ , (DL_FUNC)__FUN__ );
RCPP_REGISTER(rcpp_get_stack_trace)
...
}
Then in Rcpp headers, we do condition compiling depending on whether we are compiling Rcpp or something else:
#if defined(COMPILING_RCPP)
// just a declaration. will be defined in one of Rcpp's .cpp files
SEXP rcpp_get_stack_trace() ;
#else
#define GET_CALLABLE(__FUN__) (Fun) R_GetCCallable( "Rcpp", __FUN__ )
// defining it right here
inline SEXP rcpp_get_stack_trace(){
// the actual function pointer type
typedef SEXP (*Fun)(void) ;
// retrieve the function with R_GetCCallable
static Fun fun = GET_CALLABLE("rcpp_get_stack_trace") ;
// call it
return fun() ;
}
#endif
In the future, this boiler plate code might be generated automatically by an Rcpp::register
attribute, but for now it is hand crafted.