TL;DR
Consider using the "trick" implemented in plot.default and elsewhere. It is tried and tested.
foo <- function(x, y, ...) {
localSum <- function(..., value) sum(...)
localGrep <- function(..., na.rm) grep(...)
list(sum = localSum(x, ...), grep = localGrep("abc", y, ...))
}
X <- c(1:5, NA, 6:10)
Y <- "xyzabcxyz"
foo(X, Y, na.rm = TRUE, value = TRUE)
## $sum
## [1] 55
##
## $grep
## [1] "xyzabcxyz"
##
I am a bit shocked that the "trick" used by plot.default hasn't come up here yet. (Maybe it is mentioned elsewhere?)
Recall that plot.default must process the optional arguments of the internal plot.xy (for points and lines) as well as those of plot.window, box, axis, and title. These are all handled through one ... argument.
Importantly, when we pass (say) lwd = 6 to plot.default to widen lines in the plot region, plot.default must ensure that this argument is not seen by box and axis, which use lwd to set the width of the plot region border and axis lines. Hence:
> plot.default(0:1, 0:1, type = "l", lwd = 6)

This feature of plot.default is implemented using "local" versions of the subfunctions plot.window, box, axis, and title. The local versions are just wrappers designed to filter out arguments in ... that the subfunctions cannot be allowed to see (in this case col, bg, pch, cex, lty, and lwd).
> plot.default
function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL,
log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL,
ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL,
panel.last = NULL, asp = NA, xgap.axis = NA, ygap.axis = NA,
...)
{
localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...)
localBox <- function(..., col, bg, pch, cex, lty, lwd) box(...)
localWindow <- function(..., col, bg, pch, cex, lty, lwd) plot.window(...)
localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...)
xlabel <- if (!missing(x))
deparse1(substitute(x))
ylabel <- if (!missing(y))
deparse1(substitute(y))
xy <- xy.coords(x, y, xlabel, ylabel, log)
xlab <- if (is.null(xlab))
xy$xlab
else xlab
ylab <- if (is.null(ylab))
xy$ylab
else ylab
xlim <- if (is.null(xlim))
range(xy$x[is.finite(xy$x)])
else xlim
ylim <- if (is.null(ylim))
range(xy$y[is.finite(xy$y)])
else ylim
dev.hold()
on.exit(dev.flush())
plot.new()
localWindow(xlim, ylim, log, asp, ...)
panel.first
plot.xy(xy, type, ...)
panel.last
if (axes) {
localAxis(if (is.null(y))
xy$x
else x, side = 1, gap.axis = xgap.axis, ...)
localAxis(if (is.null(y))
x
else y, side = 2, gap.axis = ygap.axis, ...)
}
if (frame.plot)
localBox(...)
if (ann)
localTitle(main = main, sub = sub, xlab = xlab, ylab = ylab,
...)
invisible()
}
<bytecode: 0x10b813010>
<environment: namespace:graphics>
Let's take localTitle as an example:
localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...)
plot.default passes all of the arguments in ... to localTitle when it calls
localTitle(main = main, sub = sub, xlab = xlab, ylab = ylab, ...)
but title only ever sees those arguments not named col, bg, pch, cex, lty, or lwd, since those are included as formal arguments of localTitle.
Notably, this approach to processing ... has minimal overhead as it takes full advantage of lazy evaluation. None of the expressions in ... are evaluated until they are used by the subfunctions.