Repository navigation
Replace ATTRIB in three places #1429
Description
Activity
WRE is a little minimal on all of this, see 6.22.6 Working with Attributes.
Below is a first minimal version of retrieving all attribute names from a given SEXP.
#include <Rcpp/Lighter> // [[Rcpp::export]] std::vector<std::string> getnamesPrevious(SEXP s) { std::vector<std::string> v ; SEXP attrs = ATTRIB( s ); //static_cast<const CLASS&>(*this).get__()); while( attrs != R_NilValue ){ v.push_back( std::string(CHAR(PRINTNAME(TAG(attrs)))) ) ; attrs = CDR( attrs ) ; } return v; } extern "C" SEXP walk(SEXP tag, SEXP attr, void* data) { std::vector<std::string>* vecptr = static_cast<std::vector<std::string>*>(data); std::string s{Rcpp::as<std::string>(Rf_asChar(tag))}; vecptr->push_back(s); return NULL; } /* Map a function FUN over an object's attributes. FUN should return NULL if it wants the iteration to continue. A non-NULL return value from FUN terminates the iteration and is returned as the value of the R_mapAttrib call. */ // SEXP R_mapAttrib(SEXP x, SEXP (*FUN)(SEXP, SEXP, void *), void *data); // [[Rcpp::export]] std::vector<std::string> getnamesNew(SEXP s) { std::vector<std::string> v; R_mapAttrib(s, walk, (void *) &v); return v; } /*** R suppressMessages(library(xts)) x <- xts(1:4, Sys.Date()+0:3) # as an xts has three attributes getnamesPrevious(x) getnamesNew(x) */
Seems to work with (very current) R-devel:
> Rcpp::sourceCpp("/tmp/r/walkattributes.cpp") > suppressMessages(library(xts)) > x <- xts(1:4, Sys.Date()+0:3) > getnamesPrevious(x) [1] "dim" "index" "class" > getnamesNew(x) [1] "dim" "index" "class" >
This extends also to testing if a given string is among the attributes.
But I am less sure I know how to avoid the third use:
Rcpp/inst/include/Rcpp/DataFrame.h
Lines 63 to 84 in 9778196
// By definition, the number of rows in a data.frame is contained // in its row.names attribute. If it has row names of the form 1:n, // they will be stored as {NA_INTEGER, -<nrow>}. Unfortunately, // getAttrib(df, R_RowNamesSymbol) will force an expansion of that // compact form thereby allocating a huge vector when we just want // the row.names. Hence this workaround. inline int nrow() const { SEXP rn = R_NilValue ; SEXP att = ATTRIB( Parent::get__() ) ; while( att != R_NilValue ){ if( TAG(att) == R_RowNamesSymbol ) { rn = CAR(att) ; break ; } att = CDR(att) ; } if (Rf_isNull(rn)) return 0; if (TYPEOF(rn) == INTSXP && LENGTH(rn) == 2 && INTEGER(rn)[0] == NA_INTEGER) return std::abs(INTEGER(rn)[1]); return LENGTH(rn); } - On Sun, 21 Dec 2025, Dirk Eddelbuettel wrote: [673121?s=20&v=4] eddelbuettel left a comment (RcppCore/Rcpp#1429) This extends also to testing if a given string is among the attributes.I will probably add R_getAttribNames and R_getAttribCount to cover some common uses. We could also add R_existsAttrib, or (Rf_)existsAttribute as well.But I am less sure I know how to avoid the third use: https://github.com/RcppCore/Rcpp/blob/977819676139ac15d1408024e93c71c97297c f66/inst/include/Rcpp/DataFrame.h#L63-L84Is there a reason you don't use the dim attribute for this, as the R-level nrow() does? The comment is no longer accurate as the returned expanded row.names now uses a compact integer sequence. [This compact row names representation is the main reason I am not sure the current R_mapAttrib is quite what we want. There is also the peculiar handling of names on one-dimensional arrays and pairlist/language objects...]…— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you werementioned.[AA55UVGQUYBAAU5KFDIMWQT4C2ZBVA5CNFSM6AAAAACPT6SYIOWGG33NNVSW45C7 OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTW3I4QWC.gif] Message ID: ***@***.***>-- Luke Tierney Ralph E. Wareham Professor of Mathematical Sciences University of Iowa Phone: 319-335-3386 Department of Statistics and Fax: 319-335-3017 Actuarial Science 241 Schaeffer Hall email: ***@***.*** Iowa City, IA 52242 WWW: http://www.stat.uiowa.edu
I just worked out a solution using
R_mapAttrib()that is close to what we currently do. It covers the compact way, and falls back to LENGTH if need be. To minimise impact I was just following the old logic.But I also scratched my chin and wondered why we do that in the first place. It is a pretty old routine too. Looking at
dim()sounds like a viable alternative.dim(()for adata.frameobject dispatches to internal functionshortRowNames()which is implemented via another internal functiondo_shortRowNames()which accesses attributes in ways we cannot, and implements similar logic. What I just sketched is similar (following the existing function).
@ltierney just now on r-devel:
We have three spots we can likely replace