views:

188

answers:

11

I'm programming in C here, for Windows and various Unix platforms. I have a set of structs that have common fields, but also fields that are different. For example:

typedef struct {
    char street[10];
    char city[10];
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    char street[10];
    char city[10];
    char zip[10];
    char country[10];
} ADDR_B;

They obviously aren't that simple, and I actually have 6 different structs, but that's the basic idea.

What I would like, is a function in which I could pass a pointer to one of these and be able to update the common fields. Something like:

static void updateFields( void *myStruct ) {
    strcpy( myStruct->street, streetFromSomethingElse );
    strcpy( myStruct->city, cityFromSomethingElse );
}

OBVIOUSLY, what I wrote there doesn't work due to the void pointer and a cast of some kind is in order, but there isn't really a good way to cast that (is there?). I'm not opposed to an additional argument that somehow specifies the type of the struct.

For reasons that aren't relevant, I CANNOT change these structs or combine them or do anything else with them. I HAVE to use them as they are.

As always, thanks in advance for the help.

EDIT: As I added in a comment below, the common fields are NOT guaranteed to be all at the beginning or all at the end or anything else. They are just guaranteed to exist.

+2  A: 

As long as the portion you wish to update is a common prefix on each structure, you can define a new structure that lists these prefix fields, and cast your parameter to this. Assuming the layout of the prefix struct you create is identical (i.e. no strange compiler padding issues), the updates will work on all structures with that prefix.

Edit: As sztomi rightly points you, as long as the relative offsets of the fields are the same, then you can access non-prefix elements. However, I still prefer creating a common structure to act as a "template" for this function; it guards against changes in field names in whatever other structure you might choose.

Adam Wright
I would add that you don't actually need a common prefix, only that the offset of the mutual fields, that you want to change, are the same. And you don't need a common 'base'-struct, you can select one of them, and cast to it.
Tamás Szelei
That's a great idea, however, they DON'T have the same offset. That was just a coincidental in my example.
Morinar
+1  A: 

This is the time when I plug C++ because this is an obvious use for polymorphism. Or templates. Or function overloading.

However, if all of the common fields are first, you should be able to just cast the pointer to an ADDR_A and treat it as such:

static void updateFields( void *myStruct ) {
    ADDR_A *conv = myStruct;
    strcpy( conv->street, streetFromSomethingElse );
    strcpy( conv->city, cityFromSomethingElse );
}
rlbond
Ha! You plug C++ as if I had actually CHOSEN to work with C. :-p
Morinar
@Morinar: You've been finding reasons why every proposed reason won't work. They seem to be good reasons, but I'm coming to the conclusion that you're just doomed to struggle with this until you can break some of the constraints.
David Thornley
Also, notice that C++ would have problems with this, given the constraint that the layouts are different. You'd need an abstract base class with a bunch of pure virtual getters and setters, all of which would be instantiated separately in each subclass. Joy.
David Thornley
+2  A: 

You could use a macro - if the padding Adam suggests does not work

#define UPDATE_FIELDS( myStruct, aStreet, aCity ) \
  strcpy( myStruct->street, aStreet ); \
  strcpy( myStruct->city, aCity );

but the c++ plug is better;)

Mark
This is also a good idea, but there are something like 20 common fields and many of them require some additional processing to compute. I suppose it would work, but would qualify as the "Macro from Hell."
Morinar
A: 

If you do not have the type of the struct being passed (like your void* suggests), basically you are doomed. They are but a bunch of memory with a beginning address.

What you can do is have several functions like the update_fields you suggested and somehow call the right one depending on the type of structure (that is easy and can be quite hidden in a macro to avoid syntaxic noise).

Doing so you get code as below:

#include <stdio.h>
#include <string.h>

typedef struct {
    char street[10];
    char city[10];
    char lat[10];
    char lon[10];
} ADDR_A;

typedef struct {
    char street[10];
    char city[10];
    char zip[10];
    char country[10];
} ADDR_B;

#define UPDATEFIELDS(type, value) updateFields_##type(value)

static void updateFields_ADDR_A(ADDR_A *myStruct ) {
        strcpy(myStruct->street, "abc" );
        strcpy(myStruct->city, "def" );
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
        strcpy(myStruct->street, "abc" );
        strcpy(myStruct->city, "def" );
    }

int main(){
    ADDR_A a;
    ADDR_B b;

    updateFields_ADDR_A(&a);
    updateFields_ADDR_B(&b);
    UPDATEFIELDS(ADDR_A, &a);

    printf("%s\n", a.city);
    printf("%s\n", b.city);
}

Notice that you obviously should not write actual field change code in updateFields_XXX functions but just use that code as the wrapper to get the right offset to inner fields.

I you also want to have code locality you can use macro again,

#define COMMONBODY strcpy(myStruct->street, "abc" );\
        strcpy(myStruct->city, "def" );


static void updateFields_ADDR_A(ADDR_A *myStruct ) {
    COMMON_BODY
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
    COMMON_BODY
    }

or even put common code in a separate file and include it two times (quite unusual, but working file)

static void updateFields_ADDR_A(ADDR_A *myStruct ) {
    #include "commonbody.c"
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
    #include "commonbody.c"
    }

It is also nearly possible (but not quite as long as I know) to do it using typeof operator except the name mangling part.

If your structure are of different size we could even use only one updateFields function (with void*) that takes in the size of the structure as second parameter and use it to automagically cast to the right type. It's quite dirty but possible [please comment if someone wants to see that solution developped].

You can also use an union. Common fields will be accessible using any of the union member if there is a common prefix (but I understand it is not so). You have to understand that defining such union does not change anything to the existing data structure. That's basically defining a type either ADDR_A or ADDR_B than can be referenced using a pointer.

typedef union {
    ADDR_A a;
    ADDR_B b;
} ADDR_A_or_B;

If there is common prefix you can set these variable using any accessor field:

static void updateFields(ADDR_A_or_B *myStruct ) {
    strcpy( myStruct->a.street, someStreeValueFromSomewhereElse);
    strcpy( myStruct->a.city, someCityValueFromSomewhereElse);
}

You just need to cast when calling:

ADDR_A a;
updateFields((ADDR_A_or_B *)&a);

ADDR_B b;
updateFields((ADDR_A_or_B *)&b);
kriss
I think he uses void* to emulate polimorphism, and he know that the actual type is either `ADDR_A` or `ADDR_B`.
Tamás Szelei
Yes, if so the common prefix trick suggested is just fine. My point was just that void * remove all explicit information on type and even bypass compiler type checking. But you gave me an idea that I will add to my answer as nobody yet did it : define an union type.
kriss
+4  A: 

The rule of demeter - only exposing the minimum of structure - may apply.

static void updateStreetAndCity ( char *street, char *city ) {
    strcpy( street, streetFromSomethingElse );
    strcpy( city, cityFromSomethingElse );
}

Which has a slight overhead that you have to call it with two pointers rather than one, but it fits the bill of one function which will work for all your struct types.

If your structs are of different sizes, you can use macros to give static polymorphism, as is done in C99's tgmath.h, but that's quite a bit of work.

Pete Kirkham
Definitely a solid idea, unfortunately the "real" application would be something like 20+ pointers rather than one. May still be something I consider though.
Morinar
+4  A: 
#define ASSIGN(type,object,member,value) do { \
    ((type *)object)->member = value; \
    } while (0)

And now you can do stuff like:

#include <stdio.h>

struct foo {
    int x;
    char y;
};

struct bar {
    char y;
    int x;
};

int main () {
    struct foo foo;
    struct bar bar;
    ASSIGN(struct foo, &foo, x, 100);
    ASSIGN(struct bar, &bar, y, 'x');
    // ...
}

Of course, you could always extend that to incorporate a function call to do the operation so supporting memcpy or strcpy or similar would be trivial.

ezpz
Oooh, that seems like it could definitely be a winner. Perhaps that's what Mark was getting at, but the way you have laid things out here has definitely turned the light bulb on for me.
Morinar
A: 

If you can't alter the structures in any way, I think the idea of passing in the type is a good one. Make a bunch of functions that do one thing to an address, and have it take the type of address.

int updateStreetAndCity(void *addr, int type);
double getLatitude(void *addr, int type);

etc. You could have an enum, or several macro definitions to make the types. Then in each function, based on the type you passed in, call another function specific to that type to do the work for you (to avoid a monster switch statement which does a thousand different things).

Carson Myers
+1  A: 

Considering that the structs don't have at least a similar layout, there are limited ways to accomplish this, neither are short or elegant:

  1. Use a hash table instead of structs to store your data. Doing so will make it possible to access fields with their name as the key in the hashtable, regardless of the actual type. This requires a lot of trouble in C, and only if you need to access say, a hundred fields, I wouldn't recommend it.

  2. Stick to strong typing and update the fields distinctly. Yeah, it's what you don't want, but for a moderate amount of fields this might be ok.

  3. Use a macro as others suggested. However I think it's best to avoid macros whenever possible. It isn't worth it.

It is not easy nor good to hack something like this together in C (there is no duck typing). This is a typical example where a dynamic language would be more comfortable (maybe you could call python from your code? :) )

Tamás Szelei
I'm going to accept this as it most accurately sums up the various options. My actual solution is going to be a mix of distinct field update with some macro help. As David Thornley pointed out in another comment, the ridiculous constraints I seem to always be under really stonewall my ability to use most of these solutions. I do want to thank everyone for the feedback... if nothing else, I've definitely learned a lot.
Morinar
A: 

One option not yet mentioned is to create descriptors for the fields, using the offsetof() macro from <stddef.h>. You can then pass appropriate descriptors to the modification functions.

typedef struct string_descriptor
{
    size_t offset;
    size_t length;
} string_descriptor;

In C99, you could then do things like:

int update_string(void *structure, const string_descriptor d, const char *new_val)
{
    char *buffer = (char *)structure + d.offset;
    size_t len = strlen(new_val);
    size_t nbytes = MIN(len, d.length);
    // Optionally validate lengths, failing if the new value won't fit, etc
    memcpy(buffer, new_val, nbytes);
    buffer[nbytes] = '\0';
    return(0);  // Success
}

void some_function(void)
{
    ADDR_A a;
    ADDR_B b;
    static const string_descriptor a_des =
        { .offset = offsetof(ADDR_A, street), .length = sizeof(a.street) };
    static const string_descriptor b_des =
        { .offset = offsetof(ADDR_B, street), .length = sizeof(b.street) };

    update_string(&a, a_des, "Regent St."); // Check return status for error!
    update_string(&b, b_des, "Oxford St."); // Check return status for error!
}

Clearly, for a single type, this looks clumsy, but with careful generalization, you can use multiple descriptors and the descriptors can be bigger (more complex) but passed by reference, and need only be defined once, etc. With appropriate descriptors, the same update_string() function could be used to update any of the fields in either of the two structures. The tricky bit is the length initializer sizeof(b.street); I think that requires a variable of the correct type around to provide the right information, though I'd be delighted to take a (Standard C99) revision to remove that restriction. Non-minimal generic descriptors can contain a type indicator and member name and other information that might be relevant - I stuck to a very simple structure despite temptations to elaborate. You can use function pointers to supply different validations for different character strings - the validation for latitude and longitude is very different from the validation for a street name.

You can also write functions that take arrays of descriptors and do multiple updates. For example, you could have an array of descriptors for the fields in ADDR_A and another for ADDR_B and then pass the new values for the fields in an array of new strings, etc.

The onus is on you to get the descriptors correct and to use the correct descriptors.


See also: What is the purpose and return type of the __builtin_offsetof operator?

Jonathan Leffler
+1  A: 

Just an untested idea:

struct ADDR_X_offset {
  int street;
  int city;
} ADDR_A_offset = { offsetof(ADDR_A,street), offsetof(ADDR_A,city) },
  ADDR_B_offset = { offsetof(ADDR_B,street), offsetof(ADDR_B,city) };

static void updateFields( void *myStruct, struct ADDR_X_offset *offset ) {

    strcpy( myStruct+offset->street, streetFromSomethingElse );

    strcpy( myStruct+offset->city,   cityFromSomethingElse );

}
sambowry
I would use pointers instead of offsets, personally. I like the idea, however. +1
strager
A: 
typedef struct {
    char street[10];
    char city[10];
} ADDR_BASE;

typedef struct {
    struct ADDR_BASE addr;
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    struct ADDR_BASE addr;
    char zip[10];
    char country[10];
} ADDR_B;

this way you can pass ADDR_BASE* to your function.

codymanix
The question states the structs cannot be modified.
strager