Document Number: N2145
Submitter: Martin Sebor
Submission Date: April 21, 2017
Subject: Properties Of Generic Functions

Summary

Consider the following as a motivating example:

struct Shorts { short a, b; };

typedef _Atomic struct Shorts AtomicShorts;

void store_shorts (AtomicShorts *p, short a, short b)
{
  atomic_store (p, (struct Shorts){ a, b });
}

Is this valid C code that should be accepted by implementations with support for atomics?

Unlike §7.25, Type-generic math, which defines a number of type-generic macros, §7.17 Atomics, refers to the functions defined in the section as generic functions.

While §7.25 explains the process of determining the actual function call corresponding to an invocation of a type-generic macro, no similar explanation is provided in §7.17.

Moreover, beyond its uses in §7.17, C11 doesn't define the term generic function.

As a result, other than each generic functions' ability to be invoked with arguments of many different types, it's unclear what other special properties or restrictions might apply to generic functions that do not apply to ordinary functions. For example:

  1. Can one declare or take the address of a generic function?
  2. What are the effects of removing the defintion (with #undef) of a generic function implemented as a macro?
  3. Can compound literals be passed as arguments to such functions?

I expect it's safe to say that the answer to (1) was never intended to be yes, and so C just needs to make those restrictions explicit. This could be handled by replacing the term generic function with type-generic macro throughout §7.17. Since the term type-generic macro isn't precisely defined in C11 either its properties should probably be formalized and the term moved into §3. Terms, definitions, and symbols.

Similarly, I'm sure that the atomic generic functions were expected to be implemented as type-generic macros, and that removing their definitions was never meant to be valid. C11 simply lacks the restriction on strictly conforming programs preventing them from removing the macros.

However, the answer to (3) is less clear cut. It seems reasonable to expect the code example above to be accepted but it doesn't work when atomic_store is an ordinary macro that takes two arguments because the second argument in the invocation contains a comma (see also C11 DR 485). If C doesn't want to disallow this use case (or require callers to parenthesize compound literal arguments) it would, at a minimum, need to make the atomic generic macros variadic. Unfortunately, while that would work in some cases, it wouldn't be enough to make all such cases work. The variadic approach breaks down for the atomic functions that take more than one non-atomic argument. For instance, in a variadic implementation of atomic_store_explicit, there is no way to extract the last argument:

atomic_store_explicit (p, (struct S){ a, b }, memory_order_weak);

To let the function accept compound literal arguments atomic_store_explicit and other atomic functions, would need to be implemented by some compiler "magic," essentially as a C++ function templates.

It is interesting to note that although known implementations (Clang, GCC, and Oracle Studio) make use of _Generic to abstract away some aspects of the atomics interface, underneath the macro API they effectively do just that: namely, provide compiler intrinsic functions that accept arguments of any type, just like C++ functions templates do.

In fact, as it turns out, there is no other way to implement the atomic functions but by relying on some extra-linguistic compiler "magic." That is because the functions, by virtue of taking some arguments by value, must accept rvalue arguments of arbitrary types, and C provides no mechanism to handle those in a generic way. The rvalue aspect is important because an rvalue cannot have its address taken, and the language doesn't provide a mechanism to copy an rvalue of an arbitrary type into a temporary lvalue whose address could in turn be passed to an ordinary (i.e., non-generic) function. Known implementations overcome this limitation by implementing the atomic generic functions either as special type-generic intrinsics, or by making use of extensions like GCC's Typeof to determine the type of an argument and define a copy of it as an lvalue, along with the Statement Expression extension to make it possible to define such a copy in contexts where expressions are required, or both. As a result, a possible implementation of atomic_store_explicit might look something like this:

#define atomic_store_explicit(PTR, VAL, MO)                        \
__extension__                                                      \
({                                                                 \
   __auto_type __atomic_store_ptr = (PTR);                         \
   __typeof__ (*__atomic_store_ptr) __atomic_store_tmp = (VAL);    \
   __atomic_store (__atomic_store_ptr, &__atomic_store_tmp, (MO)); \
})

Above, __atomic_store is the underlying type-generic intrinsic function defined by the compiler. This example (taken from the GCC 7 implementation of <stdatomic.h>) demonstrates a technique that relies on a combination of the approaches discussed above. While simpler solutions exist, they all make use at least one of the extra-linguistic aspects discussed above to achieve type-genericity.

Suggested Technical Corrigendum

Since in all known implementations the generic atomic functions rely on some extra-linguistic compiler support, we propose to change C2X to codify existing practice by adding the following definition to §3. Terms, definitions, and symbols, immediately after 3.11:

3.12

generic function

special kind of a function that takes at least one argument of multiple distinct types. A generic function has no linkage, cannot be declared by a program, cannot have its address taken, and may but need not be implemented as a possibly type-generic macro taking a variable argument list). The type of an instance of a generic function depends on the type of the arguments with which it is invoked.

In addition, modify §7.17.1, paragraph 1 as follows:

The header <stdatomic.h> defines several macros and declares several types and generic functions for performing atomic operations on data shared between threads. The behavior of a program that removes (with #undef) the definition of a generic function implemented as a macro is undefined.

Finally, attach a new footnote to §7.17.1, paragraph 5, with the following text:

New Footnote) Generic functions specified to take an argument of type C or A may be passed a compound literal or a pointer to one of the corresponding type, wherever such an argument would be valid for an ordinary function with the corresponding type.