15

I'd like to pass a variable number of arguments from a function to C/C++, but would like to leave the arguments unevaluated and at the same time don't want to do any computations in R (aside from calling the C/C++ function), i.e. I don't want to call substitute in my R function. One option for this that I thought I could use is .External and doing smth like this:

R_fn = function(...) .External("cpp_fn", ...)

...
# and in C code:
SEXP cpp_fn (SEXP arglist) {
}

However .External is evaluating arguments in ..., so if I try something like

rm(x, y) # just making sure these don't exist

R_fn(x*y)

I get an error because R is trying to evaluate x*y before sending it to the function.

To contrast, the following works in R:

f = function(...) g(...)
g = function(x, ...) print(substitute(x))

f(x*y*z)
# x * y * z

What other options do I have? Clearly it's possible to do as R itself does it for a number of functions, e.g. substitute itself, but I don't understand how to do it. I added the rcpp tag because my eventual usage of this is going to be in Rcpp.

Romain Francois
  • 17,432
  • 3
  • 51
  • 77
eddi
  • 49,088
  • 6
  • 104
  • 155
  • 1
    I wouldn't have thought there was an *easy* way to do this without `substitute` given that's what it is designed for? But then my knowledge of R-innards is rather beginner. Is there an operational reason you can't use `substitute`? It conveniently solves the problem as in the following example: `require( inline ); cdr <- cfunction(c(x = "ANY"), 'return(CDR(x));'); cdr( substitute( x * y ) );cdr( x * y )` – Simon O'Hanlon Oct 30 '13 at 16:41
  • @SimonO101 thanks, but I don't want to use `substitute` because I want to absolutely minimize the time spent doing various R dispatches, and those calls end up costing a lot for my purposes – eddi Oct 30 '13 at 16:46
  • Ok. I suggest having a look at how R handles `$` whose arguments are never evaluated and also `[[` (both in `src/main/subset.c`). It specifically states: `/* The [[ subset operator. It needs to be fast. */`. Maybe there are some pointers there. – Simon O'Hanlon Oct 30 '13 at 16:50
  • I capture unevaluated expression in R (https://github.com/hadley/pryr/blob/master/R/inspect.r) and then use that to look up var in C++ (https://github.com/hadley/pryr/blob/master/src/inspect.cpp) – hadley Oct 30 '13 at 16:59
  • Oh and a nod to @hadley for the `cdr` function above from [**Advanced R**](http://adv-r.had.co.nz/C-interface.html#pairlists-and-symbols) – Simon O'Hanlon Oct 30 '13 at 17:01
  • I'd vote to remove the Rcpp tag. Everything here happens outside of the `.Call()`. – Dirk Eddelbuettel Oct 30 '13 at 17:02
  • @SimonO101 the thing is that all of those functions use `.Primitive` to interface with C and I don't think that's available for me (is it?) – eddi Oct 30 '13 at 17:04
  • thanks @hadley, I realize I can `substitute` first, but I very much would like to avoid doing extra computations in R – eddi Oct 30 '13 at 17:04
  • @eddi To the best of my knowledge, there are currently no other options. It's not that much work. – hadley Oct 30 '13 at 17:07
  • @hadley but R functions do it - `substitute`, `||`, `&&`, etc all pass their arguments unevaluated to C - what's so special about them? (this is maybe another way of saying I don't understand how `.Primitive` works); this may not seem like a lot of work, but when your C++ function calls back your own R function in a loop, it adds up to quite a bit – eddi Oct 30 '13 at 17:15
  • They are special because they are primitive/internal functions and can do stuff that you can't do with .Call/.External. – hadley Oct 30 '13 at 18:31
  • @DirkEddelbuettel btw what Rcpp type does the result of `substitute` correspond to? Or is there no universal answer to that? – eddi Oct 30 '13 at 20:03
  • I don't think so, and I now removed the `rcpp` tag. – Dirk Eddelbuettel Oct 30 '13 at 22:22
  • 1
    @eddi It's either an atomic vector (REALSXP/INTSXP/LGLSXP/etc) of length 1, a name (SYMSXP) or a call (LANGSXP). See http://adv-r.had.co.nz/Expressions.html – hadley Oct 30 '13 at 22:25

2 Answers2

5

One possibility is to do what match.call does (thanks to Ricardo Saporta for pointing me in that direction). This requires copy-pasting a few definitions from R source code that I won't do here, but the basic idea is to get the calling function from R_GlobalContext and then extract the function arguments from there. The rough sketch is as follows:

R_fn = function(...) .Call("cpp_fn")

// and in C++ code
Language cpp_fn() {
  SEXP sysp = ((RCNTXT*)R_GlobalContext)->sysparent;
  RCNTXT *cptr = (RCNTXT*)R_GlobalContext;

  while (cptr != NULL) {
    if (cptr->callflag & CTXT_FUNCTION && cptr->cloenv == sysp)
      break;
    cptr = cptr->nextcontext;
  }
  cptr = cptr->nextcontext; // because this is called from .Call and not from R_fn

  // and now cptr->promargs has the unevaluated arguments to do as one pleases
  // e.g.
  Language firstArg(R_PromiseExpr(CAR(cptr->promargs)));

  return firstArg;
}
eddi
  • 49,088
  • 6
  • 104
  • 155
  • +1 cool stuff. Can you explain why you have to do `sysp = ((RCNTXT*)R_GlobalContext)->sysparent` rather than just `sysp = R_GlobalContext->sysparent`? And what kind of performance increase did you see using this method over substitute for your needs? – Simon O'Hanlon Nov 01 '13 at 09:28
  • @SimonO101 because `R_GlobalContext` is exported as a `void *`, and that's where copy-pasting comes in, as `RCNTXT` is not exported, so you have to copy paste that struct. I haven't put this to real life use yet as I'm exploring various options, if/when I do, it'll be in `data.table`. – eddi Nov 01 '13 at 14:47
  • Thanks for additional explanation eddi. This is great stuff. I'll be keeping an eye on the `dt` sources (which is already a brilliant package anyway). – Simon O'Hanlon Nov 01 '13 at 14:49
  • 2
    Using non exported c api is asking for trouble. – hadley Nov 03 '13 at 13:56
  • Eddi, did you end up using this? I have a very **[similar question as you](http://stackoverflow.com/questions/25936484/r-contexts-environments-and-call-stacks-with-call)**, but was hoping there was a more "legal" way to do something like this. – BrodieG Sep 19 '14 at 15:31
  • @BrodieG I didn't but I don't see any reason to consider this illegal - as long as the pointer continues to be exported, you can always change your code to fit the struct if it changes in the future – eddi Sep 19 '14 at 15:42
  • What did you end up copying over? I was trying to grab stuff from `Defn.h` but couldn't get my code to compile and couldn't seem to find all the required definitions (got stuck around defining `JMP_BUF`). – BrodieG Sep 19 '14 at 20:16
  • @BrodieG I don't remember what else I had to copy besides `RCNTXT` - presumably also the `JMP_BUF` definitions from above (in that same file) and the corresponding includes (search for `R_USE_SIGNALS` in that file will cover the above points) – eddi Sep 19 '14 at 20:44
0

I have had some succcess with the .External2 interface and looking up the unevaluated promises in the R wrapper's environment. Not thoroughly tested, but saved me a few hundred nanoseconds with swapping out the R level substitute() and match.call().

In R:

subs <- function(expr, ...) .External2(c_subs)

In C:

#include <Rinternals.h>

SEXP c_subs(SEXP call, SEXP op, SEXP args, SEXP rho) {
    static SEXP R_ExprSymbol = NULL;
    if (R_ExprSymbol == NULL)
        R_ExprSymbol = Rf_install("expr");
    SEXP expr = Rf_findVar(R_ExprSymbol, rho); // Promise
    SEXP dots = Rf_findVar(R_DotsSymbol, rho); // Pairlist of promises
    return Rf_list2(PREXPR(expr), PREXPR(CAR(dots)));
}
Mikko Marttila
  • 10,972
  • 18
  • 31