Name n3805, alx-0073r1 - named arguments after varying arguments in macros Principles - Enable secure programming Category Preprocessor Authors Alejandro Colomar Cc: Kamila Szewczyk History r0 (2026-01-17): - Initial draft. r1 (2026-01-18; n3805): - Rename parameter-list => macro-parameter-list. parameter-list is already in use by functions. - wfix Description It is useful to have wrappers that perform an operation on a certain parameter of an API (often the first or last), and otherwise transparently call the wrapped API. It usually looks like this: #define FOO(a, b, c) foo(bar(a), b, c) The preprocessor is usually good for these, because it offers a simplicity that functions can't match. Simplicity means it is easier to review for correctness, and thus result in less bugs. On the other hand, the preprocessor doesn't have any type safety, and one can make accidents more easily than with functions. Those accidents usually result in silent bugs. In the case of the FOO() macro above, assuming that the arguments b and c to the function foo() are of a similar type (e.g., both are integers), then one could accidentally type the macro as #define FOO(a, b, c) foo(bar(a), c, b) and get a bug. Interestingly, a function wouldn't be better: void FOO(int a, int b, int c) { foo(bar(a), c, b); } The accident is the same in this case, and we get some unnecessary overhead of defining the function. In the case of this macro, it can be done better as a macro by using varying arguments, which makes sure that accidents such as this one can't happen: #define FOO(a, ...) foo(bar(a), __VA_ARGS__) However, sometimes one wants to do the same with the last argument to an API: #define ASD(a, b, c) asd(a, b, bar(c)) In this case, the preprocessor doesn't have the ability to take all the preceding arguments as a block. This should be easy to implement, though. It could be easily expressed as #define ASD(..., c) asd(__VA_ARGS__, bar(c)) There's no fundamental reason why this can't be implemented easily in the preprocessor. Proposed wording Based on N3685. 6.10.1 Preprocessing directives :: General @@ Syntax, p1 control-line: # include pp-tokens new-line # embed pp-tokens new-line - # define identifier replacement-list new-line - # define identifier lparen identifier-list(opt) ) replacement-list new-line - # define identifier lparen ... ) replacement-list new-line - # define identifier lparen identifier-list , ... ) replacement-list new-line + define-directive - # undef identifier new-line + undef-directive ... -lparen: - a ( character not immediately preceded by white space - -replacement-list: - pp-tokens(opt) ... -identifier-list: - identifier - identifier-list , identifier 6.10.5.1 Macro replacement :: General ## Add 'Syntax' before 'Constraints' @@ Syntax, new p after title +define-directive: + # define identifier replacement-list new-line + # define identifier lparen macro-parameter-list ) replacement-list new-line + +undef-directive: + # undef identifier new-line + +lparen: + a ( character not immediately preceded by white space + +replacement-list: + pp-tokens(opt) + +macro-parameter-list: + identifier-list(opt) + ... + identifier-list , ... + identifier-list , ... , identifier-list + ... , identifier-list + +identifier-list: + identifier + identifier-list , identifier @@ Constraints, p4 -If the identifier-list +If the macro-parameter-list in the macro definition -does not end with an ellipsis, -does not contain an ellipsis, the number of arguments (including those arguments consisting of no preprocessing tokens) in an invocation of a function-like macro shall equal the number of parameters in the macro definition. Otherwise, there shall be at least as many arguments in the invocation as there are parameters in the macro definition (excluding the ...). There shall exist a ) preprocessing token that terminates the invocation. @@ Semantics, p10 A preprocessing directive of the form - # define identifier lparen identifier-list(opt) ) replacement-list new-line - # define identifier lparen ... ) replacement-list new-line - # define identifier lparen identifier-list , ... ) replacement-list new-line + # define identifier lparen macro-parameter-list ) replacement-list new-line ... @@ p12 If there is a ... -in the identifier-list +in the macro-parameter-list in the macro definition, -then the trailing arguments (if any), +then the extra arguments (if any), including any separating comma preprocessing tokens, are merged to form a single item: the varying arguments. +Any commas +that separate extra arguments from named arguments +are not merged into the varying arguments. The number of arguments so combined is such that, following merger, the number of arguments is one more than the number of parameters in the macro definition -(excluding the ...), +(excluding the ...). -except that if there are as many arguments as named parameters, -the macro invocation behaves -as if a comma token has been appended to the argument list -such that varying arguments are formed that contain no pp-tokens.