[Rcpp-devel] overloaded methods in modules
Romain Francois
romain at r-enthusiasts.com
Thu Nov 18 22:25:18 CET 2010
Hello,
Up to now, a class exposed by a module could only have one method of a
given name. I've now lifted that restriction by applying the same sort
of trickery as in this thread:
http://article.gmane.org/gmane.comp.lang.r.rcpp/929
Consider this simple C++ class :
class Randomizer {
public:
Randomizer(){}
NumericVector get( int n ){
RNGScope scope ;
return runif( n, 0.0, 1.0 );
}
NumericVector get( int n, double min ){
RNGScope scope ;
return runif( n, min, 1.0 );
}
NumericVector get( int n, double min, double max ){
RNGScope scope ;
return runif( n, min, max );
}
} ;
We'd like to be able to use it from R like we would in C++:
r <- new( Randomizer )
r$get(5)
r$get(5, .5)
r$get(5, 0, 10)
It is a bit more work than with the constructor because we need to help
the compiler to disambiguate between the three "get".
This is one way to do it:
RCPP_MODULE(mod){
// helping the compiler disambiguate things
NumericVector (Randomizer::*get_1)(int) = &Randomizer::get ;
NumericVector (Randomizer::*get_2)(int,double) = &Randomizer::get ;
NumericVector (Randomizer::*get_3)(int,double,double) =
&Randomizer::get ;
class_<Randomizer>( "Randomizer" )
.default_constructor()
.method( "get" , get_1 )
.method( "get" , get_2 )
.method( "get" , get_3 )
;
}
Another way is :
RCPP_MODULE(mod){
class_<Randomizer>( "Randomizer" )
.default_constructor()
.method( "get" , ( NumericVector (Randomizer::*)(int) )(
&Randomizer::get) )
.method( "get" , ( NumericVector (Randomizer::*)(int,double) )(
&Randomizer::get) )
.method( "get" , ( NumericVector (Randomizer::*)(int,double,double) )(
&Randomizer::get) )
;
}
We can probably get smarter about this. If someone has an idea, please
come forward.
The examples above are for the most trivial form of overloading, where
the decision is only based on the number of arguments. But we can also
dispatch based on the arguments themselves. This is the same idea as for
the constructor and is achieved by passing an extra argument to .method
that constrols whether a given ethod is valid for the supplied arguments.
I'm running out of good examples, but here is one that shows dispatch
based on the arguments:
The C++ class:
class Randomizer {
public:
Randomizer(){}
NumericVector get( int n ){
RNGScope scope ;
return runif( n, 0.0, 1.0 );
}
List get( IntegerVector n ){
RNGScope scope ;
int size = n.size() ;
List res( size) ;
for( int i=0; i<size; i++){
res[i] = runif(n[i] , 0.0, 1.0 ) ;
}
return res ;
}
} ;
From R, we will call this as such:
r$get(5L)
r$get(c(5,10))
and we want to use the first get when the vector is of length one, the
other one otherwise.
For this we need this little function (it needs to have this exact
signature) :
bool get_int_valid(SEXP* args, int nargs){
if( nargs != 1 ) return false ;
if( TYPEOF(args[0]) != INTSXP ) return false ;
return ( LENGTH(args[0]) == 1 ) ;
}
whose job is to decide if the first version is ok. If not, the second is
tried, and so on. methods are stored in a std::vector so it is
guaranteed that they are scanned in the order they are declared (the
.method calls).
So, we would then expose like this:
RCPP_MODULE(mod){
class_<Randomizer>( "Randomizer" )
.default_constructor()
.method( "get" , ( NumericVector (Randomizer::*)(int) )(
&Randomizer::get) , &get_int_valid )
.method( "get" , ( List (Randomizer::*)(IntegerVector) )(
&Randomizer::get) )
;
}
As I said, it is more work, but most often only the first form of
dispatch is needed.
This is a first shot at it, so it might not be as good as it can be, it
deserves testing, ...
Romain
--
Romain Francois
Professional R Enthusiast
+33(0) 6 28 91 30 30
http://romainfrancois.blog.free.fr
|- http://bit.ly/9VOd3l : ZAT! 2010
|- http://bit.ly/c6DzuX : Impressionnism with R
`- http://bit.ly/czHPM7 : Rcpp Google tech talk on youtube
More information about the Rcpp-devel
mailing list