Date: Wed, 12 Nov 2025 17:39:48 +0100
On 11/12/25 16:51, Aaron Ballman via Liaison wrote:
> This is why I'm asking SG22 to step in; this seems like a needless incompatibility between C and C++.
>
> void func(int i, ...) {
> va_list list;
>
> va_start(list, blah); // #1
> va_start(list, __COUNTER__); // #2
> }
>
> #1 is required to be diagnosed in C17 and C++23, is required to be accepted in C++26. C23 is unclear IMO. The standard says the argument is not evaluated. So if the implementation expands to `sizeof(blah)` for some reason, that's allowed by the standard wording for va_start because `blah` is not evaluated?
Obviously. In sizeof(blah), "blah" is not evaluated,
but name lookup must succeed.
> I think the intent for C23 is clear in that this code is expected to be semantically well-formed regardless of what it expands to,
What is the context in which "semantically well-formed" is determined?
> and there's a recommendation to diagnose if any additional arguments are passed.
There are three levels of ignored:
(1) preprocessing tokens are ignored (__COUNTER__ is not incremented):
#define va_start(list, ...) __builtin_va_start(list)
(2) tokens are ignored (__COUNTER__ is incremented, no phase 7 name lookup):
#define va_start(list, ...) __builtin_va_start(list, __VA_ARGS__ ???)
(3) tokens are not evaluated (__COUNTER__ is incremented, phase 7 name lookup must succeed):
#define va_start(list, ...) ( sizeof((__VA_ARGS__, 0)) , __builtin_va_start(list) )
(4) tokens are evaluated as an expression:
#define va_start(list, ...) ( (__VA_ARGS__, 0), __builtin_va_start(list) )
It's easy to to (1) and probably easy to do (3) (even though the "sizeof"
might not be exactly the right machinery), but I don't know how to do (2)
with current technology, absent a new macro-like built-in.
Can someone speak to the intent in C23 ?
I thought (1) was intended to be a valid implementation in C23,
with QoI diagnostics for lookup errors on the second argument.
(Yes, __COUNTER__ would not be incremented.)
If (1) is not a valid implementation, we need to be a lot more
precise what we expect in terms of macro expansion and phase 7 name lookup.
> #2 is required to be diagnosed in C17 and C++23, is required to be accepted in C23 and C++26, where __COUNTER__ is expected to increment in C and expected to not increment in C++.
>
> In both cases, I think this boils down to whether the committees expect something like:
>
> #define va_start(list, ...) __builtin_va_start(list, __VA_ARGS__)
>
> where the optional arguments passed to the macro are expanded and must be valid for a call to a builtin function. I think this is how WG14 expected things to work and so #1 would be rejected due to use of an undeclared identifier, similar to what would happen with `1 || blah` in an expression (`blah` is not evaluated, but it still has to be semantically valid). And #2 would increment __COUNTER__ because the macro is expanded for the call to the builtin.
>
> Or whether the committees expect something more like:
>
> #define va_start(list, ...) __builtin_va_start(list)
>
> where the optional arguments passed to the macro are not expanded and therefore don't have to be valid beyond what the preprocessor requires. I think this is how WG21 expected things to work, so #1 would be accepted and #2 would not increment __COUNTER__.
I don't know if WG21 "expects" anything; https://cplusplus.github.io/LWG/issue4388
is phrased very much like an alignment with C23 that was overlooked when
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3348r4.pdf
rebased the C library parts of C++ onto C23.
Jens
> This is why I'm asking SG22 to step in; this seems like a needless incompatibility between C and C++.
>
> void func(int i, ...) {
> va_list list;
>
> va_start(list, blah); // #1
> va_start(list, __COUNTER__); // #2
> }
>
> #1 is required to be diagnosed in C17 and C++23, is required to be accepted in C++26. C23 is unclear IMO. The standard says the argument is not evaluated. So if the implementation expands to `sizeof(blah)` for some reason, that's allowed by the standard wording for va_start because `blah` is not evaluated?
Obviously. In sizeof(blah), "blah" is not evaluated,
but name lookup must succeed.
> I think the intent for C23 is clear in that this code is expected to be semantically well-formed regardless of what it expands to,
What is the context in which "semantically well-formed" is determined?
> and there's a recommendation to diagnose if any additional arguments are passed.
There are three levels of ignored:
(1) preprocessing tokens are ignored (__COUNTER__ is not incremented):
#define va_start(list, ...) __builtin_va_start(list)
(2) tokens are ignored (__COUNTER__ is incremented, no phase 7 name lookup):
#define va_start(list, ...) __builtin_va_start(list, __VA_ARGS__ ???)
(3) tokens are not evaluated (__COUNTER__ is incremented, phase 7 name lookup must succeed):
#define va_start(list, ...) ( sizeof((__VA_ARGS__, 0)) , __builtin_va_start(list) )
(4) tokens are evaluated as an expression:
#define va_start(list, ...) ( (__VA_ARGS__, 0), __builtin_va_start(list) )
It's easy to to (1) and probably easy to do (3) (even though the "sizeof"
might not be exactly the right machinery), but I don't know how to do (2)
with current technology, absent a new macro-like built-in.
Can someone speak to the intent in C23 ?
I thought (1) was intended to be a valid implementation in C23,
with QoI diagnostics for lookup errors on the second argument.
(Yes, __COUNTER__ would not be incremented.)
If (1) is not a valid implementation, we need to be a lot more
precise what we expect in terms of macro expansion and phase 7 name lookup.
> #2 is required to be diagnosed in C17 and C++23, is required to be accepted in C23 and C++26, where __COUNTER__ is expected to increment in C and expected to not increment in C++.
>
> In both cases, I think this boils down to whether the committees expect something like:
>
> #define va_start(list, ...) __builtin_va_start(list, __VA_ARGS__)
>
> where the optional arguments passed to the macro are expanded and must be valid for a call to a builtin function. I think this is how WG14 expected things to work and so #1 would be rejected due to use of an undeclared identifier, similar to what would happen with `1 || blah` in an expression (`blah` is not evaluated, but it still has to be semantically valid). And #2 would increment __COUNTER__ because the macro is expanded for the call to the builtin.
>
> Or whether the committees expect something more like:
>
> #define va_start(list, ...) __builtin_va_start(list)
>
> where the optional arguments passed to the macro are not expanded and therefore don't have to be valid beyond what the preprocessor requires. I think this is how WG21 expected things to work, so #1 would be accepted and #2 would not increment __COUNTER__.
I don't know if WG21 "expects" anything; https://cplusplus.github.io/LWG/issue4388
is phrased very much like an alignment with C23 that was overlooked when
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3348r4.pdf
rebased the C library parts of C++ onto C23.
Jens
Received on 2025-11-12 16:39:56
