C++ Logo

std-proposals

Advanced search

Re: [std-proposals] scoped enums and varargs

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Fri, 23 Feb 2024 21:59:20 +0000
On Fri, 23 Feb 2024 at 21:52, enh <enh_at_[hidden]> wrote:
>
> On Fri, Feb 23, 2024 at 1:45 PM Jonathan Wakely <cxx_at_[hidden]> wrote:
> >
> > On Fri, 23 Feb 2024 at 21:32, enh via Std-Proposals
> > <std-proposals_at_[hidden]> wrote:
> > >
> > > (https://isocpp.org/std/submit-issue led me to believe there isn't a
> > > public bug database, and i should send a mail here.)
> > >
> > > clang recently started warning about using scoped enums as arguments
> > > to varargs functions without a cast. this caused a lot of warning spam
> > > in Android, and a lot of confusion as to why there's even a warning
> > > here.
> >
> > Is this about varargs functions in general, or printf?
>
> printf() is the source of all the warnings i've seen.

I assume that's because the warning is saying "passing this to printf
is undefined", which is correct. Passing a scoped enum to an arbitrary
varargs function is not undefined.

>
> > I think it's intentional they don't promote when passed via varargs.
> > Why should you get implicit conversions from scoped enumeration types
> > when they don't implicitly convert under any other circumstances?
>
> because varargs seems like a special case...

As my example below shows, it's not really. You can pass it via
varargs just fine without requiring promotion.

>
> > So they get passed as their original type. For a general varargs
> > function you write yourself, there's no problem passing a scoped enum
> > as long as you extract it with that type:
> >
> > #include <stdarg.h>
> >
> > enum class E { E1 };
> > void f(int i ...) {
> > va_list v;
> > va_start(v, i);
> > E e = va_arg(v, E);
> > }
> >
> > int main()
> > {
> > f(0, E::E1);
> > }
> >
> > But for printf, you get undefined behaviour, because printf doesn't
> > know how to extract that type and it expects an int, a double, a
> > pointer, or whatever.
>
> ...for this reason. in particular, it means you can't log a scoped
> enum's integer value for debugging without a cast that duplicates its
> underlying type (or arbitrarily picks a larger one).
>
> certainly everyone who wrote this code was expecting the usual
> promotions would apply (they've "correctly" used the appropriate
> format specifiers for if-i-passed-the-underlying-type-to-printf() in
> the cases i looked at).

That doesn't make it correct though, because they didn't actually pass
the underlying type. Reading a value of type E as if it was actually a
std::underlying_t<E> is an aliasing violation, equivalent to
reintepret_cast<int*>(&x).

Using std::to_underlying(e) gives you the right type, but was only
added in C++23. A cast should be used pre-C++23.

Received on 2024-02-23 22:00:36