Is there a nice way to combine designated initializers from C99, with the result of a malloc
?
The following seems to have needless duplication:
typedef struct {
int a, b, c;
} Type;
Type *t = malloc(sizeof *t);
*t = (Type) {
.a = 2,
.b = 3,
.c = 5,
};
Can the use of Type
, and *t
be removed from the above code?
With C++20, we get a handy way of initializing data members. The new feature is called designated initializers and might be familiar to C programmers.
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
Since you asked ;) there is one tool in C to avoid explicit duplication of code, macros. That said I don't see a way not to repeat at least the name of the type. But in C++ they can't either, so C is at least as good :)
The easiest I see is
#define DESIGNATE_NEW(T, ...) \
memcpy(malloc(sizeof(T)), \
&(T const){ __VA_ARGS__ }, \
sizeof(T))
which would give
Type *t = DESIGNATE_NEW(Type,
.a = 2,
.b = 3,
.c = 5,
);
this has several advantages.
0
for float types or pointers.NB: Observe the const
in the macro, this allows several instances of the compound literal to be folded, if the compiler decides this to be relevant. Also there are means to have a variant where the list of designators is optional, see P99 below.
The disadvantage is the memcpy
and I would be happier with an assignment. Second there is no check for failure of malloc
before using the result, but one could probably come across with some weirdness to have the code exit nicely.
In P99 I go a slightly different way. There we always have an initialization function for a type, something like
inline
Type* Type_init(Type* t, int a, int b, int c) {
if (t) {
*t = (Type const){ .a = a, .b = b, .c = c };
}
return t;
}
which by macro magic can be made to provide default arguments for a
, b
and c
if they are omitted. Then you can simply use something like
Type *t = P99_NEW(Type, 1, 2, 3);
in your application code. This is better, since it avoids dereferrencing the pointer when the call to malloc
failed. On the other hand this reintroduces an order to the initializers, so not perfect either.
You can with a variadic macro. I'm not going to claim that this is a good idea, but it works:
#include <stdlib.h>
#include <stdio.h>
#define CREATE(type, ptr, ...) \
type *ptr = malloc(sizeof *ptr); \
if (ptr) *ptr = (type){__VA_ARGS__}
int main(void)
{
typedef struct {
int a, b, c;
} Type;
CREATE(Type, t, .a = 2, .b = 3, .c = 5);
printf("t->a = %d, t->b = %d, t->c = %d\n", t->a, t->b, t->c);
return 0;
}
Note that I wasn't able to use the usual do { ... } while (0)
macro definition trick (it would create a new scope, and t
wouldn't be visible), so you'd have to be careful about the context in which you use this.
Personally, I think I'm happier with the needless duplication.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With