I have function taking a variable and NULL terminated list of pointer arguments using the elipsis. I know about variable length template argument lists. It is about legacy code. Will the following two calls lead to undefined behaviour because the terminator is interpreted as Serializable* by va_arg? What are the differences between the two calls?
void serialize(Serializable* first, ...) {
va_list vl;
va_start(vl, first);
while(1)
{
Serializable* arg = va_arg(vl, Serializable*);
if(arg == NULL)
break;
/* serialize arg here */
}
}
serialize(obj1, obj2, obj3, NULL);
serialize(obj1, obj2, obj3, nullptr);
No, I don't think so.
Quoting cppreference.com on va_arg
:
If the type of the next argument in
ap
(after promotions) is not compatible withT
, the behavior is undefined, unless:
- one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types; or
- one type is pointer to
void
and the other is a pointer to a character type (char
,signed char
, orunsigned char
).
(This very closely matches the actual C11 wording; remember, va_arg
is defined by C, not C++.)
Now, C11's definition of "compatible types" is summed up by another cppreference, which teaches us that for your NULL
to have a type compatible with Serializable*
, the pointee-type of NULL
would have to be compatible with Serializable
.
Now, NULL
has an implementation-defined type so you can't know what it is, but it's certainly not going to be one that's compatible with Serializable
, unless that is simply a type alias for void
or int
and you get lucky.
With the nullptr
you get void*
, but then again see above.