Search code examples
c++rrcpprcpp11

Is there a way to check the arity of Rcpp::Function?


I need to check the arity of a function in an Rcpp block at run time. What I would like to do is something akin to the following:

double loglikelihood(Rcpp::List data, Rcpp::List params, SEXP i, Rcpp::RObject custom_function) {
    Rcpp::Function f = Rcpp::as<Rcpp::Function>(custom_function);
    double res = 0.0;
    if (arity(f) == 3) {
        res = Rcpp::as<double>(f(data, param, i));
    } else if (arity(f) == 2) {
        res = Rcpp::as<double>(f(data, param));
    }
    return res;
}

However, the limited documentation I've seen for Rcpp does not seem to contain a function for checking the arity of an Rcpp::Function. Is there any way to do this?


Solution

  • So I solved this using a workaround that is a little bit clunky but after giving it some serious thought, this is the least clunky of three methods I tried implementing.

    The method I ended up going with is checking the arity of the function on the R side using methods::formalArgs, wrapping the (function, arity) pair in a list and passing that to the Rcpp function like so:

    double loglikelihood(Rcpp::List data, Rcpp::List params, 
                         SEXP i, Rcpp::RObject custom_function) {
        Rcpp::List l = Rcpp::as<Rcpp::List>(custom_function);
        Rcpp::Function f = Rcpp::as<Rcpp::Function>(l[0]);
        int arity = l[1];
    
        double res = 0.0;
        if (arity == 3) {
            res = Rcpp::as<double>(f(data, param, i));
        } else if (arity == 2) {
            res = Rcpp::as<double>(f(data, param));
        }
        return res;
    }
    

    As I mentioned, this is a bit clunky and it changes the signature of the funciton, which is not ideal. Another way of doing this would be to use the forgiveness-rather-than-permission approach and doing the control flow in a try-catch block, like so:

    double loglikelihood(Rcpp::List data, Rcpp::List params, 
                         SEXP i, Rcpp::RObject custom_function) {
    
        Rcpp::Function f = Rcpp::as<Rcpp::Function>(custom_function);
    
        double res = 0.0;
        try {
            res = Rcpp::as<double>(f(data, param, i));
        } catch (const std::exception &e) {
            res = Rcpp::as<double>(f(data, param));
        }
        return res;
    }
    

    This approach is less clunky, but the problem with it is that it also catches other exceptions that might arise within f and silences them so they aren't passed to the user. It is possible that there are more fine-grained exceptions defined in Rcpp that would be able to catch the specific error of passing too many parameters, but if so I haven't found it.

    Lastly, we could pass methods::formalArgs to loglikelihood and query the arity just before we need to use it, but I think this approach is the clunkiest of the three, because it requires us to pass formalArgs around a lot.