Chapter 18 RObject

The RObject type is a type that can be assigned to any type of object defined in Rcpp. If you do not know what type is passed to the variable at run time, you can use RObject.

18.1 Member functions

RObject has the following member functions. These member functions also exist in all other API classes (such as NumericVector) in Rcpp.

18.1.1 inherits(str)

Returns true if this object inherits the class specified by the string str.

18.1.2 slot(name)

Accesses the slot specified by the character string name if this object is S4.

18.1.3 hasSlot(name)

Returns true if there is a slot specified by the character string name.

18.1.4 attr(name)

Accesses the attribute specified by the string name.

18.1.5 attributeNames()

Return the names of all the attributes of this object as std::vector<std::string>.

18.1.6 hasAttribute(name)

Returns true if this object has an attribute with the name specified by the string name.

18.1.7 isNULL()

Returns true if this object is NULL.

18.1.8 sexp_type()

Returns SXPTYPE of this object as int. See the R internals for a list of all SEXPTYPE defined in R.

18.1.9 isObject()

Returns true if this object has a “class” attribute.

18.1.10 isS4()

Return true if this object is a S4 object.

18.2 Determining type of object assigned to RObject

One useful use of RObject is to determine the type of the object. To determine which type the value assigned to RObject, use the is<T>() function or member function isS4() isNULL().

However, matrices and factor vectors can not be determined by only using the function is<T>() because they are vectors with values are set to specific attribute. To determine them, use Rf_isMatrix() function or the Rf_isFactor() function.

The code example below shows how to determine the type using RObject.

// [[Rcpp::export]]
void rcpp_type(RObject x){
    if(is<NumericVector>(x)){
        if(Rf_isMatrix(x)) Rcout << "NumericMatrix\n";
        else Rcout << "NumericVector\n";       
    }
    else if(is<IntegerVector>(x)){
        if(Rf_isFactor(x)) Rcout << "factor\n";
        else Rcout << "IntegerVector\n";
    }
    else if(is<CharacterVector>(x))
        Rcout << "CharacterVector\n";
    else if(is<LogicalVector>(x))
        Rcout << "LogicalVector\n";
    else if(is<DataFrame>(x))
        Rcout << "DataFrame\n";
    else if(is<List>(x))
        Rcout << "List\n";
    else if(x.isS4())
        Rcout << "S4\n";
    else if(x.isNULL())
        Rcout << "NULL\n";
    else
        Rcout << "unknown\n";
}

Use as<T>() to convert RObject to another Rcpp type after determining the type.

// Converting `RObject` to `NumericVector`
RObject x;
NumericVector v = as<NumericVector>(x);

18.3 Using RObject to write functions that receive various datatypes (function templates)

C++ allows us to avoid code duplication by writing templated functions that are not limited to specific datatypes.

Note: This section does not cover C++ templates in any detail - it only shows how to implement them in Rcpp. It also assumes your compiler can use C++11 syntax. If not, please refer to this Rcpp Gallery example by Nathan Russell which covers the process in much more detail (and which provided the source material for this section).

Say we have a very simple C++ templated function that prints “Hello” and then returns its input.

template<typename T>
T say_hello(T x) {
    std::cout << "Hello!" << std::endl;
    return x;
}

The T in this function is a placeholder for any other type. You can use any identifier you like, but T is conventional. At compile time the compiler analyses our code and creates new functions with T replaced with the relevant datatypes, so we don’t have to write versions of add_one() for int, float, double etc.

In Rcpp the process is almost identical, but we have to jump through a couple of extra hoops.

A first attempt at an Rcpp version of the function might look very similar:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
template<typename T>
T say_hello(T x) {
    Rcout << "Hello!" << std::endl;
    return x;
}

However, this will not compile. To quote Nathan Russell’s article:

Sadly this does not work: magical as Rcpp attributes may be, there are limits to what they can do, and at least for the time being, translating C++ template functions into something compatible with R’s C API is out of the question.

Fortunately Rcpp provides two macros, RCPP_RETURN_VECTOR and RCPP_RETURN_MATRIX that make it very simple to implement a templated function.

template<int RTYPE>
Vector<RTYPE> printer(Vector<RTYPE> x) {
    Rcout << "Hello!" << std::endl;
    return x;
}

// [[Rcpp::export]]
RObject say_hello(RObject x) {
    RCPP_RETURN_VECTOR(printer, x);
}

The exported say_hello() function now works as expected in R.

say_hello("a")
## Hello!
##  [1] "a"

say_hello(1:10)
## Hello!
##  [1]  1  2  3  4  5  6  7  8  9 10

say_hello(FALSE)
## Hello!
##  [1] FALSE

Note that the template specification looks slightly different: template<int RTYPE> rather than template<typename T> in the earlier example. This is called a non-type parameter, and is used because the type of an RObject is stored as an integer. Again, Nathan’s article provides more details on this. As with the use of T in the first example, the use of RTYPE as an alias for the type is entirely arbitrary.

Also note that printer() and say_hello() have different names because otherwise the call to RCPP_RETURN_VECTOR is ambiguous and the code will not compile. If you would prefer both functions to have the same name, you can use namespaces as shown below.

namespace internal {

    template<int RTYPE>
    Vector<RTYPE> say_hello(Vector<RTYPE> x) {
        Rcout << "Hello!" << std::endl;
        return x;
    }
    
}  // end of "internal" namespace

// [[Rcpp::export]]
RObject say_hello(RObject x) {
    RCPP_RETURN_VECTOR(internal::say_hello, x);
}