Jens Gustedt's Blog

May 19, 2012

type generic functions taking pointers or arrays

Filed under: C11, language, P99 — Jens Gustedt @ 14:26

Every C programmer knows about the implicit conversion an array object A undergoes in most contexts: it is converted to a pointer to its first element, as if the programmer had written `&A[0]`. The technical term of the standard for that is “lvalue conversion”, and the corresponding section describes what precisely is going on when an object (an lvalue) is evaluated for its contents (the rvalue).

One particular place where this conversion takes place is when an array is passed as an argument to a function. So in that case all additional information about the array, in particular its length, is not made available to the function. One could always pass the size of the array (sizeof A) as additional argument to the function, but this then would always require to pass this information also in case the function is called with what its interface suggest, just a pointer. In this post I will describe a new possibility that is provided by the _Generic keyword in C11 that allows us to create type generic interfaces that knows how to distinguish between the case being called with an array or with a pointer as an argument.

Type generic expressions that are constructed with _Generic have the particularity that none of their subexpressions undergo lvalue conversion. Let us have a look at an example:

_Generic(A, double*: A[0], default: _Generic(&A[0], double*: A))

This has four different expressions A, A[0], &A[0] and A. All of them are valid syntactical expressions if A corresponds to a pointer or to an array. (To me more precise a register array would not be allowed, though). So in these cases the above code provides a valid type generic expression. For any other type of object, e.g an double variable or expression or for an object of struct type it would be a syntax error.

Now, this expression does an extra check, namely for the base type of A: it is only valid if the base type is double. Any other type, even float or double const, would be an error. To summarize the properties of this expression

  • it accepts pointers to double or arrays of double as “argument” A
  • it rejects all other types of arguments
  • it returns an lvalue expression consisting either of a single double object or of a double array

Now let us add some () to force the evaluation order and put this in a macro

#define DOUBLE_LENGTH(A) (sizeof _Generic((A), double*: (A)[0], default: _Generic(&(A)[0], double*: (A))))/sizeof(double)

The prefix sizeof will give us with the size of the lvalue that we discussed above, and the whole macro evaluation will

  • return 1 when called with a double*
  • return the length of the array when called with a double[]
  • issue a syntax error otherwise

Now if we have a function

double fmax(size_t n, double a[n])

we could wrap this in a macro

#define fmax(A) fmax(DOUBLE_LENGTH(A), (A))

that could be equally called with a pointer to double (supposing that this corresponds to an array of length 1) or an array of double.

With P99 we even could make the wrapper more convenient to provide the length parameter only when need, that is when the macro is called with a single argument instead of two. (If you like to program with P99, this is a good exercise.)

If you want to check for more than one type, type generic expressions as given above become a bit clumsy. P99 now has four interfaces that deal with this. They all accept an expression as a first argument and then a list of types that are to be checked for the base type of the pointer or array.

  • P99_OVALUE gives the object as in the example above.
  • P99_AVALUE returns the argument array or an array of one element at the same address as the pointer target.
  • P99_OBJSIZE returns the size of the object.
  • P99_OBJLEN returns the length of the array or one if the argument was a pointer.

With that, the above definition could be written as

#define DOUBLE_LENGTH(A) P99_OBJLEN(A, double)

and if we would like to accept qualified pointers or arrays as well

#define DOUBLE_QUALIFIED_LENGTH(A) P99_OBJLEN(A, double, double const, double volatile, double const volatile)
Advertisements

11 Comments

  1. Hi, nice article (even though I don’t have a use for this trick right now) and nice blog in general! I knew P99 from before but I didn’t really notice you had a blog.

    Just a bit of nitpicking: I think you’re missing a default type case in your _Generic invocation (or some kind of type name before the nested _Generic). Also, I think there should be a closing parenthesis at the end of your macro.

    Comment by Nhat Minh Lê — May 21, 2012 @ 09:02

  2. Are you really sure that the controlling expression of _Generic does not undergo lvalue conversion? N1569 6.3.2.1p2 gives a list of contexts in which lvalue conversion does not take place, and generic selection is not mentioned. (And in any case, conversion of array to pointer is not the same as lvalue conversion; the former is covered in 6.3.2.1p3, and again generic selection is not mentioned.) 6.5.1.1p4 applies only to the result expression.

    That said, it is not difficult to work around; just use &(A) instead of (A) as the controlling expression, and adjust the type(s) appropriately, like this:

    sizeof _Generic(&(A), double**: (A)[0], /* the rest stays the same */ default: _Generic(&(A)[0], double*: (A)))
    

    The only downside to that solution I can think of is that it will fail on pointers declared with register storage class.

    Comment by Marcin Grzegorczyk — May 31, 2012 @ 20:53

    • The sematics section for generic expressions says in 6.5.1.1p3:

      The controlling expression of a generic selection is not evaluated.

      An expression that is not evaluated cannot undergo conversion, can’t it?

      Also, the only compiler that I know that has _Generic, clang, does it like I describe 🙂

      That said, you are right that 6.3.2.1p3 (or perhaps 6.3.2.1 in general) is in urgent need of being updated, e.g `_Alignas` is also missing there, I think.

      Comment by Jens Gustedt — June 1, 2012 @ 06:30

      • An expression that is not evaluated cannot undergo conversion, can’t it?

        Apparently it can, because otherwise sizeof(p[0]) would be a constraint violation…

        I think the issue is worth a Defect Report. One can find arguments both for and against having the controlling expression undergo array to pointer conversion (there was a short discussion about it on comp.std.c), although personally I now believe the arguments against the conversion are better. (It allows more flexibility, and if one does need the conversion, using (A)+0 will do the trick. By the way, I still think the controlling expression should undergo lvalue conversion, because otherwise the example given in 6.5.1.1p5 would not work on lvalues with qualified types.)

        Also by the way, I don’t think _Alignas is really missing from 6.3.2.1p3, since its argument has to be either an integer constant expression (so it does not matter if an array there gets converted to a pointer or not, it’s still a constraint violation) or a type name (which is not an expression, so 6.3.2.1 does not apply).

        Comment by Marcin Grzegorczyk — June 2, 2012 @ 11:13

        • You are right, all of this is ambiguous. I think from the formulation of 6.5.1.1p2 we may deduce that complete array types (as long as they are not VLA) are among the types that are allowed in a generic association. This makes only sense to me, if the controlling expression can have an array type. I think the following would be an intended use case for generic expressions

          _Generic((A),
            int[1]: func1,
            int[2]: func2,
            default: func0)(A)
          

          For the same reasons I don’t think that the intent is to do lvalue conversion. It says

          No two generic associations in the same generic selection shall specify
          compatible types.

          and this is the only constraint that relates the different types of the different associations. So two associations may well have the same base type with different sets of qualifiers.

          _Generic((x),
            int: fun,
            int const: func,
            int _Atomic: funa)(&(x))
          

          If we’d have lvalue conversion, only the first association in this list would trigger.

          But then indeed examples as the one given in the standard should be rewritten as

          #define cbrt(X) _Generic((X)+0, \
                    long double: cbrtl,   \
                    default: cbrt,        \
                    float: cbrtf          \
                    )(X)
          

          to force an lvalue conversion. But for this example this trick to force lvalue conversion only works because the expression is expected to be of arithmetic type. For general types one would have to use something like (1 ? (X) : (X)).

          Comment by Jens Gustedt — June 2, 2012 @ 14:12

      • There is no ambiguity of any kind; P4 clearly says: “The type and value of a generic selection are identical to those of its result expression.” Identical means no conversion of any kind.

        Comment by David — June 13, 2013 @ 22:56

        • David, once the selection is made, there is indeed no ambiguity about the type of the result. But this para clearly only talks about the result expression, not of the expression that is used for the selection. After discussion this with members of the C committee, even there doesn’t seem to be a clear idea on how this is to be interpreted. The only thing that is clear that this was designed to do some kind of function/macro overloading in the spirit of tgmath. That this also can be used much more widely was not in focus when this had been decided.

          Comment by Jens Gustedt — June 14, 2013 @ 06:40

      • Well, Is “printf(“=> %d\n”, _Generic(“foo”, char [4]: 2));” Legal?

        Actually, after re-reading that sentence it makes no sense at all.

        If the type of the result expression is, for example a float, as in cbrt(4.5f), “float: cbrtf” is a violation since ‘float(*)(float)’ is clearly not identical to ‘float’.

        $6.5.1 Primary expressions clearly says “A generic selection is a primary expression. Its type and value depend on the selected generic association, as detailed in the following subclause.” And of course a re-qoute of paragraph 4, “The type and value of a generic selection are identical to those of its result expression.”.

        Comment by David — June 14, 2013 @ 23:56

        • I am really not sure what you are after. Whether or not _Generic(“foo”, char [4]: 2)) boils down to the question if an array type is a selection that can trigger. As you can see from the discussion above I would say yes, but not everybody seems to agree. If the array type triggers for "foo" as a selection then all of that is valid. If it undergoes array to pointer conversion, the this _Generic is missing a case for char* and so it would be a constraint violation.

          As for your example with cbrtf, I am lost. What has the type that triggers a certain association to do with the type of the result expression? These are clearly of two different types.

          Comment by Jens Gustedt — June 15, 2013 @ 07:44

      • I think I’ve simply confused myself a little there. But you are right; I cannot see anything that explicitly forbids the controlling expression from undergoing a conversion if a compatible type-name could be matched.

        Comment by David — June 15, 2013 @ 15:10


RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Create a free website or blog at WordPress.com.