Home > Mobile >  Specialising Rcpp::as() for std::array
Specialising Rcpp::as() for std::array

Time:12-25

One of my projects uses C 11 std::array for fixed-sized array types, and so I'm trying to specialise Rcpp::as() for convenient conversion from SEXP to these types. Since this requires partial specialisation, I've followed the Exporter route as outlined in Rcpp-extending:

#include <RcppCommon.h>
#include <array>

namespace Rcpp {
namespace traits {

// Partial specialisation to allow as<array<T,D>>(...)
template <typename ElementType, int Dimensionality>
class Exporter<std::array<ElementType,Dimensionality>>
{
private:
    std::array<ElementType,Dimensionality> value;

public:
    Exporter (SEXP x)
    {
        std::vector<ElementType> vec = as<std::vector<ElementType>>(x);
        if (vec.size() != Dimensionality)
            throw Rcpp::exception("Array does not have the expected number of elements");
        for (int i=0; i<Dimensionality; i  )
            value[i] = vec[i];
    }
    
    std::array<ElementType,Dimensionality> get () { return value; }
};

} // traits namespace
} // Rcppnamespace

#include <Rcpp.h>

Other code within the package can then do, for example,

std::array<double,3> arr = Rcpp::as<std::array<double,3>>(vec);

I've bundled this up into a minimal package for example purposes; the "real" application is here.

The problem is that this approach compiles and works fine for me locally (macOS, clang), but gcc doesn't like it. The output from a GitHub CI action on the minimal package is below.

* installing *source* package ‘Rcpp.asArray’ ...
** using staged installation
** libs
g   -std=gnu  11 -I"/opt/R/4.2.2/lib/R/include" -DNDEBUG  -I'/home/runner/work/_temp/Library/Rcpp/include' -I/usr/local/include   -fpic  -g -O2  -c main.cpp -o main.o
In file included from /home/runner/work/_temp/Library/Rcpp/include/Rcpp/as.h:25,
                 from /home/runner/work/_temp/Library/Rcpp/include/RcppCommon.h:169,
                 from array.h:4,
                 from main.cpp:1:
/home/runner/work/_temp/Library/Rcpp/include/Rcpp/internal/Exporter.h: In instantiation of ‘Rcpp::traits::Exporter<T>::Exporter(SEXP) [with T = std::array<double, 3>; SEXP = SEXPREC*]’:
/home/runner/work/_temp/Library/Rcpp/include/Rcpp/as.h:87:41:   required from ‘T Rcpp::internal::as(SEXP, Rcpp::traits::r_type_generic_tag) [with T = std::array<double, 3>; SEXP = SEXPREC*]’
/home/runner/work/_temp/Library/Rcpp/include/Rcpp/as.h:152:31:   required from ‘T Rcpp::as(SEXP) [with T = std::array<double, 3>; SEXP = SEXPREC*]’
main.cpp:8:62:   required from here
/home/runner/work/_temp/Library/Rcpp/include/Rcpp/internal/Exporter.h:31:42: error: no matching function for call to ‘std::array<double, 3>::array(SEXPREC*&)’
   31 |                     Exporter( SEXP x ) : t(x){}
      |                                          ^~~~
In file included from /usr/include/c  /11/tuple:39,
                 from /usr/include/c  /11/bits/hashtable_policy.h:34,
                 from /usr/include/c  /11/bits/hashtable.h:35,
                 from /usr/include/c  /11/unordered_map:46,
                 from /home/runner/work/_temp/Library/Rcpp/include/Rcpp/platform/compiler.h:153,
                 from /home/runner/work/_temp/Library/Rcpp/include/Rcpp/r/headers.h:62,
                 from /home/runner/work/_temp/Library/Rcpp/include/RcppCommon.h:30,
                 from array.h:4,
                 from main.cpp:1:
/usr/include/c  /11/array:95:12: note: candidate: ‘std::array<double, 3>::array()’
   95 |     struct array
      |            ^~~~~
/usr/include/c  /11/array:95:12: note:   candidate expects 0 arguments, 1 provided
/usr/include/c  /11/array:95:12: note: candidate: ‘constexpr std::array<double, 3>::array(const std::array<double, 3>&)’
/usr/include/c  /11/array:95:12: note:   no known conversion for argument 1 from ‘SEXP’ {aka ‘SEXPREC*’} to ‘const std::array<double, 3>&’
/usr/include/c  /11/array:95:12: note: candidate: ‘constexpr std::array<double, 3>::array(std::array<double, 3>&&)’
/usr/include/c  /11/array:95:12: note:   no known conversion for argument 1 from ‘SEXP’ {aka ‘SEXPREC*’} to ‘std::array<double, 3>&&’
make: *** [/opt/R/4.2.2/lib/R/etc/Makeconf:178: main.o] Error 1
ERROR: compilation failed for package ‘Rcpp.asArray’
* removing ‘/home/runner/work/Rcpp.asArray/Rcpp.asArray/Rcpp.asArray.Rcheck/Rcpp.asArray’

I don't know the internals of Rcpp well enough to be sure, but it looks like the default implementation of Exporter is being used in preference to the custom one, and failing because there is no std::array constructor for SEXP, which would involve intrusive modification.

Can anyone suggest a way to resolve this, please?

CodePudding user response:

As best as I can tell your approach was missing a few pieces. The best guide is still the trusted example by @coatless in the Rcpp Gallery

Following that I reduces your header (with a bit of renaming -- sorry, personal style crept in) to (skipping the declaration in step 1) to

namespace Rcpp {
  namespace traits {

    template <typename T, std::size_t D> class Exporter<std::array<T,D>> {
      typedef typename std::array<T,D> ATD;

      // Convert the type to a valid rtype.
      const static int RTYPE = Rcpp::traits::r_sexptype_traits<T>::rtype;
      Rcpp::Vector<RTYPE> vec;

    public:
      Exporter(SEXP x): vec(x) {
        if (TYPEOF(x) != RTYPE) Rcpp::stop("Wrong R type for array");
      }
      ATD get() {
        ATD x;
        if (vec.size() != D) Rcpp::stop("Array does not have the exp nb of elements");
        std::memcpy(x.begin(), vec.begin(), D*sizeof(T));
        return x;
      }
    };
  }
}

I changed / simplified your usage example to using Rcpp Attribute because, hey, its 2022 and we had them for close a decade so .Call() is no longer de rigeur:

// [[Rcpp::export]]
void foo() {
  Rcpp::NumericVector v = Rcpp::NumericVector::create(1,2,3);
  Rcpp::print(v);
  std::array<double,3> a = Rcpp::as<std::array<double,3>>(v);
  Rcpp::Rcout << "Hi "
              << a[0] << " "
              << a[1] << " "
              << a[2] << std::endl;
}

And that then works as expected (here from the shell with littler loading your package:

edd@rob:~/git/rcppasarray(main)$ r -lRcpp.asArray -e'Rcpp.asArray:::foo()'
[1] 1 2 3
Hi 1 2 3
edd@rob:~/git/rcppasarray(main)$ 

It may need an explicit resizing step -- I haven't worked too much with std::array to be sure. I'll clean up a bit more and send you a PR.

  • Related