Default values in a C Struct
Asked Answered
R

11

93

I have a data structure like this:

struct foo {
    int id;
    int route;
    int backup_route;
    int current_route;
}

and a function called update() that is used to request changes in it.

update(42, dont_care, dont_care, new_route);

this is really long and if I add something to the structure I have to add a 'dont_care' to EVERY call to update( ... ).

I am thinking about passing it a struct instead but filling in the struct with 'dont_care' beforehand is even more tedious than just spelling it out in the function call. Can I create the struct somewhere with default values of dont care and just set the fields I care about after I declare it as a local variable?

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

What is the most elegant way to pass just the information I wish to express to the update function?

and I want everything else to default to -1 (the secret code for 'dont care')

Rodin answered 14/4, 2009 at 20:13 Comment(0)
H
161

While macros and/or functions (as already suggested) will work (and might have other positive effects (i.e. debug hooks)), they are more complex than needed. The simplest and possibly most elegant solution is to just define a constant that you use for variable initialisation:

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
    dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);

This code has virtually no mental overhead of understanding the indirection, and it is very clear which fields in bar you set explicitly while (safely) ignoring those you do not set.

Haversack answered 14/4, 2009 at 23:15 Comment(4)
Another bonus of this approach is that it does not rely on an C99 features to work.Asbury
When I changed to this 500 lines "fell out" of the project. Wish I could up-vote twice on this one!Rodin
PTHREAD_MUTEX_INITIALIZER uses this as well.Strafe
I have added an answer below relying on X-Macros for being flexible on the number of elements present in the struct.Mylor
R
17

You can change your secret special value to 0, and exploit C's default structure-member semantics

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

will then pass 0 as members of bar unspecified in the initializer.

Or you can create a macro that will do the default initialization for you:

#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }

struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);
Rena answered 14/4, 2009 at 20:38 Comment(4)
Does FOO_INIT work? The compiler should complain, I think, if you initialize the same member twice.Agulhas
I've tried it with gcc and it didn't complain. Also, I haven't found anything against it in the standard, in fact, there's one example where the overwriting is specifically mentioned.Rena
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf: 6.7.8.19: The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;Oculus
If this is true, could you not define DEFAULT_FOO, which has all the initializers, and then do struct foo bar = { DEFAULT_FOO, .id=10 }; ?Glennisglennon
G
7

<stdarg.h> allows you to define variadic functions (which accept an indefinite number of arguments, like printf()). I would define a function which took an arbitrary number of pairs of arguments, one which specifies the property to be updated, and one which specifies the value. Use an enum or a string to specify the name of the property.

Guberniya answered 14/4, 2009 at 20:22 Comment(1)
Hi @Matt , how to map enum variables to the struct field? Hardcode it? Then this function will be only applicable to specific struct.Sinecure
H
5

Perhaps consider using a preprocessor macro definition instead:

#define UPDATE_ID(instance, id)  ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route)  ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route)  ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route)  ({ (instance)->current_route = (route); })

If your instance of (struct foo) is global, then you don't need the parameter for that of course. But I'm assuming you probably have more than one instance. Using the ({ ... }) block is a GNU-ism that that applies to GCC; it is a nice (safe) way to keep lines together as a block. If you later need to add more to the macros, such as range validation checking, you won't have to worry about breaking things like if/else statements and so forth.

This is what I would do, based upon the requirements you indicated. Situations like this are one of the reasons that I started using python a lot; handling default parameters and such becomes a lot simpler than it ever is with C. (I guess that's a python plug, sorry ;-)

Hellraiser answered 14/4, 2009 at 21:10 Comment(0)
H
4

How about something like:

struct foo bar;
update(init_id(42, init_dont_care(&bar)));

with:

struct foo* init_dont_care(struct foo* bar) {
  bar->id = dont_care;
  bar->route = dont_care;
  bar->backup_route = dont_care;
  bar->current_route = dont_care;
  return bar;
}

and:

struct foo* init_id(int id, struct foo* bar) {
  bar->id = id;
  return bar;
}

and correspondingly:

struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);

In C++, a similar pattern has a name which I don't remember just now.

EDIT: It's called the Named Parameter Idiom.

Humankind answered 14/4, 2009 at 20:21 Comment(3)
I believe its called a constructor.Modestamodeste
No, I meant the thing that you can do: update(bar.init_id(42).init_route(5).init_backup_route(66));Humankind
It seems to be called "chained Smalltalk-style initialization", at least here: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…Humankind
L
4

One pattern gobject uses is a variadic function, and enumerated values for each property. The interface looks something like:

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

Writing a varargs function is easy -- see http://www.eskimo.com/~scs/cclass/int/sx11b.html. Just match up key -> value pairs and set the appropriate structure attributes.

Longitude answered 14/4, 2009 at 20:25 Comment(0)
E
4

Since it looks like that you only need this structure for the update() function, don't use a structure for this at all, it will only obfuscate your intention behind that construct. You should maybe rethink why you are changing and updating those fields and define separate functions or macros for this "little" changes.

e.g.


#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)

Or even better write a function for every change case. As you already noticed you don't change every property at the same time, so make it possible to change only one property at a time. This doesn't only improve the readability, but also helps you handling the different cases, e.g. you don't have to check for all the "dont_care" because you know that only the current route is changed.

Elysha answered 15/4, 2009 at 1:12 Comment(2)
some cases will change 3 of them in the same call.Rodin
You can have this sequence: set_current_rout(id, route); set_route(id, route); apply_change(id); or similar. What I think is that you can have one function call for every setter. And do the real calculation (or what ever) at a later point.Elysha
L
2

I'm rusty with structs, so I'm probably missing a few keywords here. But why not start with a global structure with the defaults initialized, copy it to your local variable, then modify it?

An initializer like:

void init_struct( structType * s )
{
   memcopy(s,&defaultValues,sizeof(structType));
}

Then when you want to use it:

structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function
Luck answered 14/4, 2009 at 20:38 Comment(0)
M
2

You could address the problem with an X-Macro

You would change your struct definition into:

#define LIST_OF_foo_MEMBERS \
    X(int,id) \
    X(int,route) \
    X(int,backup_route) \
    X(int,current_route)


#define X(type,name) type name;
struct foo {
    LIST_OF_foo_MEMBERS 
};
#undef X

And then you would be able to easily define a flexible function that sets all fields to dont_care.

#define X(type,name) in->name = dont_care;    
void setFooToDontCare(struct foo* in) {
    LIST_OF_foo_MEMBERS 
}
#undef X

Following the discussion here, one could also define a default value in this way:

#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X

Which translates into:

const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};

And use it as in hlovdal answer, with the advantage that here maintenance is easier, i.e. changing the number of struct members will automatically update foo_DONT_CARE. Note that the last "spurious" comma is acceptable.

I first learned the concept of X-Macros when I had to address this problem.

It is extremely flexible to new fields being added to the struct. If you have different data types, you could define different dont_care values depending on the data type: from here, you could take inspiration from the function used to print the values in the second example.

If you are ok with an all int struct, then you could omit the data type from LIST_OF_foo_MEMBERS and simply change the X function of the struct definition into #define X(name) int name;

Mylor answered 12/4, 2017 at 13:43 Comment(1)
This technique exploits that the C preprocessor uses late binding and this can be very useful when you want one single place to define something (for instance states) and then be 100% sure that everywhere when used items are always in the same order, new items are automatically added and deleted items removed. I am not too fond of using short names like X and typically append _ENTRY to the list name, e.g. #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....Haversack
R
1

The most elegant way would be to update the struct fields directly, without having to use the update() function - but maybe there are good reasons for using it that don't come across in the question.

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

Or you can, like Pukku suggested, create separate access functions for each field of the struct.

Otherwise the best solution I can think of is treating a value of '0' in a struct field as a 'do not update' flag - so you just create a funciton to return a zeroed out struct, and then use this to update.

struct foo empty_foo(void)
{
    struct foo bar;
    bzero(&bar, sizeof (struct bar));
    return bar;    
}

struct foo bar = empty_foo();
bar.id=42;
bar.current_route = new_route;
update(&bar);

However, this might not be very feasible, if 0 is a valid value for fields in the struct.

Regretful answered 14/4, 2009 at 20:22 Comment(2)
A network is a good reason =) But if -1 is the 'dont care' value, then just restucture the empty_foo() function, and use that approach?Regretful
sorry, I clicked the downvote by mistake and only noticed later!!! I cant find any way to undo that now unless there is an edit...Siret
U
0

I've encountered this pattern before. What I do is just separate the "caring" from the value. You do pay a bit of a cost for storing the cares, but using one bit fields will keep the cost down. You now have the full range of your values back and you can define whether you care or not. This also works very well with designated initializers.

struct foo_has
{
    bool id:1;
    bool route:1;
    bool backup_route:1;
    bool current_route:1;
}

struct foo
{
    struct foo_has has;
    int id;
    int route;
    int backup_route;
    int current_route;
}

not too much more typing than what you are looking for. All of the other "has" fields are going to default to false here.

struct foo bar = {.has.id = true, .has.current_route = true, .id = 42, .current_route = new_route };
update(&bar);
Ursas answered 30/5, 2023 at 17:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.