https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114731

--- Comment #12 from Alejandro Colomar <alx at kernel dot org> ---
Hi Martin,

On Tue, Apr 16, 2024 at 05:35:03AM +0000, uecker at gcc dot gnu.org wrote:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97100
> 
> --- Comment #7 from uecker at gcc dot gnu.org ---
> 
> The fix suppresses certain warnings which are guarded by a flag, but it is not
> always clear whether a specific warning should be suppressed or not in dead
> code. 

_Generic(3)'s branches are by design using different types, to call
appropriate overload implementations.  These will necessarily result in
incompatible types somewhere.  If you're lucky and your overload
implementations are functions _and_ you call them all with the same
argument list, you can move the argument list out of the _Generic
expression, so as to avoid the warnings (because _Generic() will have
already been evaluated at the time the argument list is part of the
function call and the compiler has the information to diagnose.

If either you pass a different argument list (not my case) to each
overload, or your overloads must be implemented via macros (my case),
then you must put the argument list inside the _Generic() expression,
which will necessarily result in incompatible types (otherwise you
wouldn't be using _Generic() at all, probably).

> 
> You could also always add a cast.

I designed these macros precisely for type safety.  That is, to have the
compiler diagnose type mismatches.  For example, the call

        err = a2i(time_t, &t, "42", endp, base, min, max);

is a type-safe version of BSD's

        t = strtoi("42", endp, base, min, max);

Because nothing guarantees that intmax_t will be appropriate for parsing
a time_t.  My macro will call a wraper function that makes sure that we
call the right one, strtoi(3) or strtou(3), depending on the signedness
of time_t.  It goes even further and makes sure that &t is actually a
time_t, by wrapping strtoi(3) in a call that accepts a long*.

If I do cast, then I lose all that type safety, and need to increase
the complexity by tinkering with GNU extesions such as ({}) and
__builtin_types_compatible_p(), and I also need to insert a
_Static_assert(3).  All of this is error prone, and requires that the
program is not DRY, so I would really like to avoid it.

It's nice, though, that it's possible to workaround it with a cast +
GNU extensions + some extra complexity.  At least I have a fallback.
But I don't think I'm content with keeping it like that.  See the
program below to see what needs to be done to make it work safely.

You mentioned in the #97100 thread that a C compiler must diagnose all
constraint violations.  Is the following then a violation of the
standard?

        $ cat constraint.c 
        void f(int *);

        int main(void)
        {
                unsigned u;
                f(&u);
        }
        $ cc constraint.c -S
        $ 

I don't see the compiler should be allowed not emit a diagnostic there,
and it's not ok to not emit a diagnostic in a dead branch of _Generic(),
independently of me having asked the compiler to warn about
-Wincompatible-pointer-types.  Dead branches of _Generic() are not just
any dead code; it's dead code that we know will result in incompatible
types, by design.  A warning about them there is bogus.

Please present a case where not warning about incompatible types in a
dead _Generic() branch would be bad.

> Fundamentally, the program is that _Generic is not ideally designed for this
> use case. One could consider an extension

Why not?  What's the design use-case for _Generic() if it's not
overloading a macro for different types, and then call the appropriate
overload function or macro?  Even if you open-code the branches in the
_Generic() expressions, you're doing the same thing: writing code for
handling different types, which will almost necessarily result in
incompatible types in all branches but one.

Have a lovely day!
Alex

Reply via email to