Skip to content

Replace ATTRIB in three places #1429

Description

@eddelbuettel

@ltierney just now on r-devel:

As part of clarifying and tightening the R API for packages the
non-API entry points ATTRIB and SET_ATTRIB will be removed from
installed header files and, where possible, hidden from package
access. Both expose internal structure that might need to be changed
and also make it too easy to inadvertently break consistency
assumptions about the values of some attributes. Using these
functions is will now result in check NOTEs. These check NOTEs will
soon be replaced by WARNINGs; this will probably happen by the end of
January. The 'Working with attributes' section in the 'Writing R
Extensions' list some alternatives. Additional functions for working
with attributes, without exposing internal attributes structure, could
be added if they are needed.

We have three spots we can likely replace

edd@paul:~/git/rcpp(master)$ ag ATTRIB inst/include/ src/
inst/include/Rcpp/proxy/AttributeProxy.h
82:        SEXP attrs = ATTRIB( static_cast<const CLASS&>(*this).get__());
91:        SEXP attrs = ATTRIB(static_cast<const CLASS&>(*this).get__());

inst/include/Rcpp/DataFrame.h
71:            SEXP att = ATTRIB( Parent::get__() )  ;
edd@paul:~/git/rcpp(master)$ 

Activity

  1. eddelbuettel commented on Dec 20, 2025

    @eddelbuettel
    MemberAuthor

    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"
    > 
  2. eddelbuettel commented on Dec 21, 2025

    @eddelbuettel
    MemberAuthor

    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:

    // 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);
    }

  3. ltierney commented on Dec 21, 2025

    @ltierney
  4. eddelbuettel commented on Dec 21, 2025

    @eddelbuettel
    MemberAuthor

    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.

  5. eddelbuettel commented on Dec 21, 2025

    @eddelbuettel
    MemberAuthor

    dim(() for a data.frame object dispatches to internal function shortRowNames() which is implemented via another internal function do_shortRowNames() which accesses attributes in ways we cannot, and implements similar logic. What I just sketched is similar (following the existing function).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions