Search code examples
c++rrcpp

Coerce a vector to another type in Rcpp


In R, you can coerce between vector types using as.X, e.g. as.character(1) or as.integer(1). However I can't work out if there is a native, elegant way to do the same in Rcpp.

Firstly, the C API does have an AS_CHARACTER macro which I tried to use, but Rcpp doesn't seem to include Rdefines.h where it's defined:

> as_character = Rcpp::cppFunction("CharacterVector as_character(RObject x){ return AS_CHARACTER(x); }")
file113af2570bd06.cpp: In function ‘Rcpp::CharacterVector as_character(Rcpp::RObject)’:
file113af2570bd06.cpp:6:49: error: ‘AS_CHARACTER’ was not declared in this scope
    6 | CharacterVector as_character(RObject x){ return AS_CHARACTER(x); }
      |                                                 ^~~~~~~~~~~~
make: *** [/usr/lib/R/etc/Makeconf:178: file113af2570bd06.o] Error 1
g++ -std=gnu++14 -I"/usr/share/R/include" -DNDEBUG   -I"/home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include" -I"/tmp/RtmppCAKV8/sourceCpp-x86_64-pc-linux-gnu-1.0.10"    -fpic  -g -O2 -ffile-prefix-map=/build/r-base-LhKvHL/r-base-4.2.3=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2  -c file113af2570bd06.cpp -o file113af2570bd06.o
Error in sourceCpp(code = code, env = env, rebuild = rebuild, cacheDir = cacheDir,  : 
  Error 1 occurred building shared library.

If I try to use CharacterVector, it also fails to compile:

> as_character = Rcpp::cppFunction(
+ "CharacterVector as_character(RObject x){ return CharacterVector::create(x); }"
+ )
In file included from /home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp/Vector.h:50,
                 from /home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp.h:40,
                 from file113af65cda53c.cpp:1:
/home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp/vector/converter.h: In instantiation of ‘static SEXPREC* Rcpp::internal::string_element_converter<RTYPE>::get(const T&) [with T = Rcpp::RObject_Impl<Rcpp::PreserveStorage>; int RTYPE = 16; SEXP = SEXPREC*]’:
/home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp/generated/Vector__create.h:84:34:   required from ‘static Rcpp::Vector<RTYPE, StoragePolicy> Rcpp::Vector<RTYPE, StoragePolicy>::create__dispatch(Rcpp::traits::false_type, const T1&) [with T1 = Rcpp::RObject_Impl<Rcpp::PreserveStorage>; int RTYPE = 16; StoragePolicy = Rcpp::PreserveStorage; Rcpp::traits::false_type = Rcpp::traits::integral_constant<bool, false>]’
/home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp/generated/Vector__create.h:71:26:   required from ‘static Rcpp::Vector<RTYPE, StoragePolicy> Rcpp::Vector<RTYPE, StoragePolicy>::create(const T1&) [with T1 = Rcpp::RObject_Impl<Rcpp::PreserveStorage>; int RTYPE = 16; StoragePolicy = Rcpp::PreserveStorage]’
file113af65cda53c.cpp:6:72:   required from here
/home/migwell/R/x86_64-pc-linux-gnu-library/4.2/Rcpp/include/Rcpp/vector/converter.h:49:37: error: no matching function for call to ‘std::__cxx11::basic_string<char>::basic_string(const Rcpp::RObject_Impl<Rcpp::PreserveStorage>&)’
   49 |                         std::string out(input) ;
      |   

On the other hand, it does allow Rf_coerceVector which does work, but it seem unlikely this is the best way, because I'm fairly deep into pure C if I'm using STRSXP and Rf_ functions:

> as_character = Rcpp::cppFunction(
+ "CharacterVector as_character(RObject x){ return Rf_coerceVector(x, STRSXP); }"
+ )
> as_character(1)
[1] "1"

What is the most natural/simple way to do this in Rcpp?

I'm using:

  • R 4.2.3
  • Rcpp 1.0.10

Solution

  • If you take a second and closer look at the existing Rcpp documentation as provided by the ten vignettes, including two introductory ones and one focussed entirely on the conversions you ask about here, then you may encouter the two central helper functions wrap() to return any option to a SEXP, and as<T>() which (as a templated function) converts from a SEXP to the template type.

    > cppFunction("CharacterVector cnvrt(NumericVector x) \  # indented for display
                       { return as<CharacterVector>(x); }")  # else really one line
    > cnvrt(1.23)
    [1] "1.23"
    > 
    

    The thing that happens and is confusing is that the compiler most often automagically injects an existing conversion from or to SEXP, but it sometimes errors, or conflicts with other templated meta programming code (which is complicated, sadly). For example overloading the return statement often bites us.

    So I have some time-honoured rules:

    • if in doubt, separate the out the assignment (and trust the compiler to re-optimise)
    • for conversions, maybe first explicitly go from your source object to SEXP and then from SEXP to your destination object
    • in other words "make it work first, then maybe make it fancy"