Cows42
Cows42

Reputation: 307

C using enum to shorten code

Basically I'm trying to remove duplicates in my code by using enum and a new function to help.

Say I have a structure with following:

typedef struct user {
    bool empty;
    int lineNumber;
    char *errMessage;
    char *username;
    char *password;
    char *uid;
    char *gid;
    char *gecos;
    char *dir;
    char *shell;
} user;

and I make an enum like the following:

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

What I'm trying to do is send a parameter userValue enumParam in a function and by that choose which member in the structure I want to access. Example of the helper function:

void parseHelper(userValue enumParam) {
    user *newStruct;
    newStruct -> enumParam = "hello";
}

Example of usage:

parseHelper(password)

Expected: newStruct->password should point to "hello"

But this isn't helping because I receive the following error:

'user {aka struct user}' has no member named 'val'

Upvotes: 1

Views: 144

Answers (5)

Brian Zammit
Brian Zammit

Reputation: 179

The way I would go about this is using function pointers. It is not a very clean solution but it should work..

Basically in this solution the enums MUST be in the same order that the char pointers are defined in within the structure. (I would prefer setting them manually to the index in the struct like so:

typedef enum {
    username = 0, 
    password = 1,
    uid = 2,
    gid = 3,
    gecos = 4,
    dir = 5,
    shell = 6
} userValue;

Now the enum can be used as an index from the first char* represented by the enum in the structure (the username). This can be done by getting a pointer to the char* and increment it using pointer arithmetic.

Full code example (Used debugger to confirm correct execution):

#include <string.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    char *username;
    char *password;
    char *uid;
    char *gid;
    char *gecos;
    char *dir;
    char *shell;
} user;

typedef enum { 
    username = 0,
    password = 1,
    uid = 2,
    gid = 3,
    gecos = 4,
    dir = 5,
    shell = 6
} userValue;

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    char** selected = &newStruct.username;
    selected = (char**)(selected + ((int)enumParam));
    *selected = "hello";
}

int main()
{
    parseHelper(username);
    parseHelper(password);
    parseHelper(uid);
    parseHelper(gid);
    parseHelper(gecos);
    parseHelper(dir);
    parseHelper(shell);
    return 0;
}

-Edit Maybe a more readable way of achieving the same result is to cast the first char* (in this case the username) to an array of char*, and use the standard array syntax to move to the desired offset in the

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    // Other fields is an array of char * which starts at username...
    char** otherFields = &newStruct.username;
    otherFields[(int)enumParam] = "hello";
}

-- Option 2

If you are able to modify the structure definition, and nowhere else do you want to specifically access the strings by their name (for example user->username), you can declare an array of char pointers in your struct like so:

typedef struct user
{
    bool empty;
    int lineNumber;
    char *otherFields[7];
    // otherFields[0] - username
    // otherFields[1] - password
    // otherFields[2] - uid
    // otherFields[3] - gid
    // otherFields[4] - gecos
    // otherFields[5] - dir
    // otherFields[6] - shell
} user;

and then fill in as necessary using the index to array:

void parseHelper(userValue enumParam)
{
    user newStruct;
    memset(&newStruct, 0, sizeof(user));

    newStruct.otherFields[enumParam] = "hello";
}

Under the hood, this solution is identical to the first one. However, this has the added benefit of simplifying the use of the enum to access the desired char*, while loosing the benefit of using the string name to access a particular string in the rest of the code.

Upvotes: 1

pablo1977
pablo1977

Reputation: 4433

I have a proposal for you.

First, add a last item to your enum list (userValue_max):

typedef enum {username, password, uid, gid, gecos, dir, shell, userValue_max} userValue;  

Now, define your struct in a slightly different way, containing an array of strings:

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    char *field[userValue_max];
} user;  

Now write your code in this way:

void parseHelper(userValue enumParam) {
    user *newStruct = malloc(sizeof(user)); // Allocating memory here!!
    newStruct->field[enumParam] = "hello";
}  

Upvotes: 2

Jonathan Leffler
Jonathan Leffler

Reputation: 753675

You can't do what you're trying to do as directly as you show because enumeration constants are names for integers, not names of structure members. You can't directly write a reference to a structure member using any sort of variable to identify the member name — you must explicitly name a member (so it is a hard-wired offset into the structure).

However, assuming you have C11 support in your compiler for anonymous union and structure members, and assuming that you can redefine the structure type (it isn't wholly fixed and immovable, pre-ordained by some exterior force, such as a tutor or a standard), you can get fairly close to what you want with code like this:

#include <stdio.h>
#include <stdbool.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    union
    {
        struct
        {
            char *username;
            char *password;
            char *uid;
            char *gid;
            char *gecos;
            char *dir;
            char *shell;
        };
        char *field[7];
    };
} user;

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

static void parseHelper(user *u, userValue enumParam, char *value)
{
    u->field[enumParam] = value;
}

int main(void)
{
    user u;
    u.empty = false;
    u.lineNumber = 1;
    u.errMessage = "no error";
    parseHelper(&u, password, "secret");
    parseHelper(&u, username, "me");
    parseHelper(&u, uid, "0");
    parseHelper(&u, gid, "1");
    parseHelper(&u, gecos, "Yours Truly");
    parseHelper(&u, dir, "/home/me");
    parseHelper(&u, shell, "/bin/sea");

    printf("%s:%s:%s:%s:%s:%s:%s\n", u.username,
           u.password, u.uid, u.gid, u.gecos, u.dir, u.shell);
    return 0;
}

When run, it produces the output:

me:secret:0:1:Yours Truly:/home/me:/bin/sea

There are several factors that make this work.

  1. Structure member names are in separate name spaces, so the enumeration element names don't interfere with the structure member names.
  2. All the fields that you're trying to treat are the same type (char *).
  3. When you have an anonymous union in a structure, you can access the elements of the union by name.
  4. When you have an anonymous structure within a union, you can access the elements within the structure by name.

If you're stuck with C90 or C99, you can achieve a similar effect, but it is more verbose:

#include <stdio.h>
#include <stdbool.h>

typedef struct user
{
    bool empty;
    int lineNumber;
    char *errMessage;
    union
    {
        struct
        {
            char *username;
            char *password;
            char *uid;
            char *gid;
            char *gecos;
            char *dir;
            char *shell;
        } f;
        char *field[7];
    } u;
} user;

typedef enum {username, password, uid, gid, gecos, dir, shell} userValue;

static void parseHelper(user *u, userValue enumParam, char *value)
{
    u->u.field[enumParam] = value;
}

int main(void)
{
    user u;
    u.empty = false;
    u.lineNumber = 1;
    u.errMessage = "no error";
    parseHelper(&u, password, "secret");
    parseHelper(&u, username, "me");
    parseHelper(&u, uid, "0");
    parseHelper(&u, gid, "1");
    parseHelper(&u, gecos, "Yours Truly");
    parseHelper(&u, dir, "/home/me");
    parseHelper(&u, shell, "/bin/sea");

    printf("%s:%s:%s:%s:%s:%s:%s\n", u.u.f.username, u.u.f.password,
           u.u.f.uid, u.u.f.gid, u.u.f.gecos, u.u.f.dir, u.u.f.shell);
    return 0;
}

This produces the same output, of course.

Upvotes: 0

Alex Morrison
Alex Morrison

Reputation: 405

-> operator is for accessing member variable of a pointer to a structure. Important thing is the names of these member variable can't vary. You should call them by whichever name you have given to them in the declaration.

Here you are trying to access member variable of name 'val', but there is no such member variable. Hence the error.

In this function you will have to put the possible values of enum under conditions to match possible names of structure.

Upvotes: 0

0___________
0___________

Reputation: 67476

No my friend.

Enums are just integers, they are no string substitutions :)

Upvotes: 3

Related Questions