• Article by Simon Tatham about Flaw in _Generic

    From Kaz Kylheku@864-117-4973@kylheku.com to comp.std.c on Sun Jul 30 21:39:53 2023
    From Newsgroup: comp.std.c

    https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/c11-generic/

    In spite of _Generic being a compile-time type switch, the unused
    clauses all have to type check with a given argument.

    E.g. any of the cases maps an argument x to (x)->memb, then
    the argument cannot be a "char *".

    _Generic should only require that the clauses parse, using
    the minimal amount of type information to that aim,.

    (If the clauses don't parse, they cannot be identified,
    so the construct cannot work. When sscanning the association
    list, when the implementation encounters a nonmatching type,
    it has to parse the associatead expression in order to find
    the next clause in the association list.)

    I would havce designed this with required parentheses:

    _Generic(expr, t1: (e1), t2: (e2), ... :default (edfl));

    Only the matching expression would be fully parsed; the non-matching
    ones would be regarded as token sequences in which parentheses and
    braces have to balance.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Kaz Kylheku@864-117-4973@kylheku.com to comp.std.c on Mon Jul 31 17:40:26 2023
    From Newsgroup: comp.std.c

    On 2023-07-30, Kaz Kylheku <864-117-4973@kylheku.com> wrote:
    https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/c11-generic/

    In spite of _Generic being a compile-time type switch, the unused
    clauses all have to type check with a given argument.

    E.g. any of the cases maps an argument x to (x)->memb, then
    the argument cannot be a "char *".

    _Generic should only require that the clauses parse, using
    the minimal amount of type information to that aim,.

    (If the clauses don't parse, they cannot be identified,
    so the construct cannot work. When sscanning the association
    list, when the implementation encounters a nonmatching type,
    it has to parse the associatead expression in order to find
    the next clause in the association list.)

    I would havce designed this with required parentheses:

    _Generic(expr, t1: (e1), t2: (e2), ... :default (edfl));

    Only the matching expression would be fully parsed; the non-matching
    ones would be regarded as token sequences in which parentheses and
    braces have to balance.

    Another idea is to parse and type check each of the en, but inside each
    one, pretend that expr has the type of tn.

    We invent a generated symbol __g0025 and transform
    the construct like this. Here I'm using GCC brace expression
    syntax ({ ... }):

    _Generic(expr,
    t1 : ({t1 __g0025 = expr; ee1; }),
    t2 : ({t2 __g0025 = expr; ee2; }),
    // ...)

    Here ee2 denotes e1, but with __g0025 substituted for
    expr (so that expr is evaluated only once).

    Under this transformation, the ony type error we have
    in the dead clauses occurs in the initialization of
    __g0025 which isn't compatible with tn on the left.

    The implementation can arrange to suppress that
    diagnostic, and then just parse and type-check ee2, which has a
    correctly typed __g0025 in scope.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Martin Uecker@ma.uecker@gmail.com to comp.std.c on Tue Aug 1 23:35:39 2023
    From Newsgroup: comp.std.c

    On Monday, July 31, 2023 at 7:40:29 PM UTC+2, Kaz Kylheku wrote:
    On 2023-07-30, Kaz Kylheku wrote:
    https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/c11-generic/

    In spite of _Generic being a compile-time type switch, the unused
    clauses all have to type check with a given argument.

    E.g. any of the cases maps an argument x to (x)->memb, then
    the argument cannot be a "char *".

    _Generic should only require that the clauses parse, using
    the minimal amount of type information to that aim,.

    (If the clauses don't parse, they cannot be identified,
    so the construct cannot work. When sscanning the association
    list, when the implementation encounters a nonmatching type,
    it has to parse the associatead expression in order to find
    the next clause in the association list.)

    I would havce designed this with required parentheses:

    _Generic(expr, t1: (e1), t2: (e2), ... :default (edfl));

    Only the matching expression would be fully parsed; the non-matching
    ones would be regarded as token sequences in which parentheses and
    braces have to balance.
    Another idea is to parse and type check each of the en, but inside each
    one, pretend that expr has the type of tn.

    We invent a generated symbol __g0025 and transform
    the construct like this. Here I'm using GCC brace expression
    syntax ({ ... }):

    _Generic(expr,
    t1 : ({t1 __g0025 = expr; ee1; }),
    t2 : ({t2 __g0025 = expr; ee2; }),
    // ...)

    Here ee2 denotes e1, but with __g0025 substituted for
    expr (so that expr is evaluated only once).

    Under this transformation, the ony type error we have
    in the dead clauses occurs in the initialization of
    __g0025 which isn't compatible with tn on the left.

    The implementation can arrange to suppress that
    diagnostic, and then just parse and type-check ee2, which has a
    correctly typed __g0025 in scope.
    I usually do this why a pointer to void and back to the right type
    for the branch
    https://godbolt.org/z/4fr37shGn
    ("usually" = rarely, because I avoid this type of generic
    programming because it causes more problems than it
    solved)
    Note that GCC emitting warnings for the non-active branch
    is a known problem. A design problem of _Generic is
    that the default branch does not have to be last, so during
    parsing it may not be known which branch is active (it
    could still suppress warnings when the default branch is
    last though).
    Another way this could have been solved is to give a new name
    to access the value of the controlling expression inside the
    branch which then gets the right type:
    _Generic(x, int y: y + 1, default: 0);
    where y = x for the active branch.
    Martin
    --- Synchronet 3.20a-Linux NewsLink 1.114