type-safe parametric polymorphism
Among the many different new proposals for C23 that WG14 obtained are two sensible gems by Alex Gilding
The primary might be what you’d count on if you understand the C++ characteristic and the way it began (I believe in C++11), particularly a risk to specify constants for any sort (if used to declare an object) or to have easy perform calls that may be compelled to be evaluated at compile time. There are nonetheless some tough edges to chop (storage class, linkage) however principally, if Wg14 weren’t so predictably unpredictable, this ought to be a homerun.
The opposite one is on an entire totally different degree, eye opening and really, very promissing. Its major concept is so as to add annotations to APIs to keep away from breaking ABIs and code duplication on the similar time.
Let’s take a primary instance what the options which might be proposed can do. For the rest of this submit I can be assuming that we’ve a C23 compiler that implements the options.
void * memcpy(void * prohibit s1, const void *prohibit s2, size_t n);
This perform makes use of the catch-all sort void
to point that the pointers that it receives as arguments might level to any bytes, so long as for each there are no less than n
bytes accessible, the 2 byte arrays don’t overlap, and so long as the thing pointed-to by s1
is mutable.
Now in 99% of the circumstances the sorts of the objects which might be pointed-to are most likely the identical and the byte values which might be copied from s2
into s1
will mix to a wise object within the goal. Issues happen if the pointed to things have totally different dimension or in the event that they even have totally different sort. Even when float
has 32 bit the next code is inaccurate, as a result of it overwrites an object with floating level sort by a illustration of an integer sort, and the ensuing illustration won’t be legitimate.
uint32_t a = 4288912;
float x;
memcpy(&x, &a, sizeof(x)); // semantic error, do not do that!
printf("an unsigned as a float: %g", x); // might entice
The treatment utilizing options from the above papers seems to be as follows
constexpr void [[bind_var(A)]]* (*cpy_typed)(void [[bind_var(A)]]* prohibit s1, const void [[bind_var(A)]]*prohibit s2, size_t n) = memcpy;
...
uint32_t a;
float x = 45.9;
cpy_typed(&a, &x, sizeof(a)); // Contraint violation, prognosis required!
So what had been undefined habits above, now turns into a tough error (constraint violation as we name it in C), however the perform that’s used beneath, memcpy
, continues to be precisely the identical.
The trick is to annotate the void*
pointers of the orginal interface with attributes a brand new C23 characteristic (borrowed from C++) that enables to annotate precice spots of the syntax. Right here it’s 3 times the identical attribute [[bind_var(A)]]
, indicating that the code that makes use of this interface is predicted to go in two pointers which have the identical base sort named A
(which ever that’s) and likewise to return a pointer to an object of that very same base sort A
.
Now all this specification is placed on a perform pointer cpy_typed
that’s constexpr
and initialized to memcpy
(scroll to the tip of the road to see this.)
No additional code is generated, however sort security is improved!
In C23 with type-generic lambdas, if that’s accepted, you might do one thing like this
#outline cpy_typed(S1, S2, N)
[](auto* s1, typeof(*s1) const* s2, size_t n) {
return (typeof(*s1)*)memcpy(s1, s2, n); }(S1, S2, N);
}(S1, S2, N)
Utilizing such a lambda would supply the identical sort checks, however on the potential value of effectivity and code dimension. We might be relying on the optimization capacities of the compiler, the decision to memcpy
would typically have an extra indirection and changing the lambda to a perform pointer might indicate the technology of an extra stub every time.
The brand new characteristic turns into much more fascinating after we look into capabilities that obtain a callback to carry out a activity that depends upon such a perform. Once more, let’s have a look at a perform of the C library
void qsort(void *base, size_t nmemb, size_t dimension, int (*compar)(const void *, const void *));
int comp_double(void const* x, void const* y) {
double const* X = x; double const* Y;
return (*X < *Y) ? -1 : (*X == *Y ? 0 : +1);
}
...
double dArr[5] = { 4.0, 2.2, 5.1, 6.3, 9.4, };
unsigned uArr[5] = { 4, 2, 5, 6, 9, };
qsort(dArr, 5, sizeof dArr[0], comp_double); // okay
qsort(uArr, 5, sizeof uArr[0], comp_double); // undefined
If you’re not used to this type of syntax, it is a perform that types the array to which base
factors. That array is meant to be composed of nmemb
components of dimension dimension
and compar
is a pointer to a comparability perform that receives two pointers, compares the objects and returns values much less, equal or larger then 0
if the primary is smaller, equal or larger. The underlying sort for base
and the kind order that compar
describes might be arbitrary.
Now, once more C simply places the duty that each one of that is constant in your (the programmers) shoulders. For now there is no such thing as a mechanism in C that would present even the best consistency checks. However we acquire one perform, one single exterior image within the C library that’s able to doing a generic type. Often it’s comparatively environment friendly and small.
How this modifications with the proposed papers? First this could possibly be augmented in an analogous approach by including attributes
constexpr void (*sort_typed)(void [[bind_var(A)]] *base, size_t nmemb, size_t dimension, int (*compar)(const void [[bind_var(A)]]*, const void [[bind_var(A)]]*)) = qsort;
So now it is a perform pointer that factors to the qsort
perform, however that ensures that it might solely be known as with constant sorts.
int comp_double(void const [[bind_type(double)]]* x, void const [[bind_type(double)]]* y) {
double const* X = x; double const* Y;
return (*X < *Y) ? -1 : (*X == *Y ? 0 : +1);
}
...
double dArr[5] = { 4.0, 2.2, 5.1, 6.3, 9.4, };
unsigned uArr[5] = { 4, 2, 5, 6, 9, };
sort_typed(dArr, 5, sizeof dArr[0], comp_double); // okay
sort_typed(uArr, 5, sizeof uArr[0], comp_double); // laborious error
So now the compiler is aware of {that a} perform is predicted that receives pointer the identical pointer goal sort than dArr
and uArr
. For dArr
this base sort is double
so every little thing works out tremendous. For uArr
it will expect unsigned
and so it is ready to alert the programmer that the particular name is inconsistent.
Right here we’ve the second attribute syntax [[bind_type(double)]]
that’s launched by the paper. It ensures that comp_double
can solely be known as with pointers coming from double
. For the perform definition it additionally ensures that the parameters can solely be silently transformed to tips that could double
and nothing else.
So as an alternative of blowing up the C library by an unbounded variety of capabilities (as for instance templates
in C++ or type-specific capabilities and type-generic interfaces to them in C) Alex’ trick helps us to take care of precisely the one perform that has been within the C library because the epoch (no ABI change), and to enhance the capability of the compiler to detect inconsistencies (a change within the API).