2

Provided that cpp11 does not provide any "sugar", we need to use attributes.

I am trying to set colnames in a C++ function, as in the next MWE

#include "cpp11.hpp"
using namespace cpp11;

// THIS WORKS
[[cpp11::register]]
doubles cpp_names(writable::doubles X) {
    X.attr("names") = {"a","b"};
    return X;
}

// THIS WON'T
[[cpp11::register]]
doubles_matrix<> cpp_colnames(writable::doubles_matrix<> X) {
    X.attr("dimnames") = list(NULL, {"A","B"});
    return X;
}

How can I pass a list with two vectors, one NULL and the other ("a","b"), that can be correctly converted to a SEXP?

In Rcpp, one would do colnames(X) = {"a","b"}.

My approach can be wrong.

coatless
  • 20,011
  • 13
  • 69
  • 84
pachadotdev
  • 3,345
  • 6
  • 33
  • 60
  • 1
    Create a matrix with row- and colnames and run `attributes()` on it: you see that it is just a vector (with a `dim` attribute) along with a `dimnames` attribute that is a list of two character vectors for row and colnames. `Rcpp` is user-friendly here and gives you an accessor helper, if you want to use something else you may have to write such a helper. – Dirk Eddelbuettel May 11 '22 at 00:52
  • 1
    Oh, and as a follow-up, you question title is not correct. Nobody uses `colnames` from R, it really is just a matter of setting the attribute(s) at the C API level. Which can be done in C but is tedious -- it is much easier with proper abstractions. Lastly, you may get this question closed on you as it doesn't really conform to the format StackOverflow prefers. – Dirk Eddelbuettel May 11 '22 at 00:56
  • 3
    Attribute access with `cpp11` is documented at https://cpp11.r-lib.org/articles/cpp11.html#attributes. – mpadge May 11 '22 at 07:52

1 Answers1

1

There is possibly a bug with the matrix class s.t. attributes cannot be set. One workaround is to 'upcast' it to a sexp and set the attributes via that interface.

Two other notes about your MWE:

  1. Initialising an item in a list with NULL throws an error; instead use R_NilValue which is R's 'NULL' expression.
  2. The MWE copies the original object and modifies the copy (see cpp11's copy-on-write semantics "Copy-on-write semantics" in the cpp11 "Motivations" vignette); this may not be your intention with the function.

If you do not wish to modify the column names of the original object, then the following code is a correct implementation of the MWE:

#include "cpp11.hpp"

using namespace cpp11;

[[cpp11::register]]
sexp cpp_colnames(writable::doubles_matrix<> X) {
    sexp X_sexp(X.data());
    X_sexp.attr("dimnames") = writable::list(
        { R_NilValue, writable::strings({"A", "B"}) }
    );
    return X_sexp;
}
// > X <- matrix(as.numeric(1:4), nrow=2)
// > cpp_colnames(X)
//      A B
// [1,] 1 3
// [2,] 2 4
// > X
//      [,1] [,2]
// [1,]    1    3
// [2,]    2    4

Otherwise, if you do want to modify the original object (see "How do I modify a vector in place?" in the cpp11 FAQ) then:

#include "cpp11.hpp"

using namespace cpp11;

[[cpp11::register]]
void cpp_colnames(sexp X_sexp) {
    X_sexp.attr("dimnames") = writable::list(
        { R_NilValue, writable::strings({"A", "B"}) }
    );
}
// > X <- matrix(as.numeric(1:4), nrow=2))
// > cpp_colnames(X)
// > X
//      A B
// [1,] 1 3
// [2,] 2 4
stephematician
  • 844
  • 6
  • 17