sleske
sleske

Reputation: 83635

A struct with a member that sometimes needs to be`const` and sometimes not

I am working on a program that uses a generic struct in many places to shuttle around related values. This struct contains a field char* s.

Many functions modify s; however, sometimes the struct is used to pass information to functions which will only read it. In these cases often the string used to initialize s is a const char*. However, assigning it to s causes a compiler warning.

Though technically correct, this warning feels wrong, as the function does not modify s. Is there a way around this warning, apart from just casting away the const? Is there some way for a function to promise it will treat a struct member as const?

Example:

#include <stdio.h>

struct mystruct{
  int i;
  char* s;
};

void i_only_read(const struct mystruct *m){
  printf("mystruct: i=%d, s=%s\n", m->i, m->s);
}

int main(int argc, char **argv){
  const char* cstr = "Hello";
  struct mystruct m;
  m.i=99;
  /* gcc warning: assignment discards ‘const’ qualifier
   * from pointer target type
   */
  m.s=cstr;
  i_only_read(&m);
}

Notes

  1. I cannot change the struct's declaration to const char* s, because most functions taking pointers to the struct do modify s.
  2. I guess I could have two structs, one with char* s and one with const char* s, but this seems very ugly (creates redundancy, needs conversion functions between the two structs).
  3. If anyone's interested the program is Navit, the struct is struct attr. I created a simple example for this question.

Upvotes: 3

Views: 797

Answers (4)

sleske
sleske

Reputation: 83635

Building on paddy's and jxh's ideas, I have come up with a solution that looks practical:

  • create a "const version" of the struct, with a const char* member
  • combine it with a "non-const" version of the struct by using a union

Code:

typedef struct const_mystruct {
    int i;
    const char * s;
} const_mystruct;

typedef union {
    struct {
        int i;
        char *s;
    };
    const const_mystruct cms;
} mystruct;

This achieves the following goals (analogous to what the const qualifier does for simple pointers):

  • Functions can accept const_mystruct* if they want to promise not to modify the char array, and the compiler will enforce this.
  • mystruct is available for cases where the char array does need to be modified/free()d.
  • Finally, a mystruct can be converted to a const_mystruct by reading mystruct.cms. The (potentially dangerous) conversion const_mystruct -> mystruct by writing mystruct.cms is not possible, because mystruct.cms is const and thus not writable.

Code illustrating the use:

#include <stdio.h>
#include <malloc.h>
/* [structs omitted] */

void i_only_read(const const_mystruct *m){
  printf("mystruct: i=%d, s=%s\n", m->i, m->s);
}

void i_might_modify(mystruct *m){
  printf("noconst: mystruct: i=%d, s=%s\n", m->i, m->s);
}

int main(void){
  const char* cstr = "Hello";
  const_mystruct cm;
  cm.i=99;
  cm.s=cstr;
  // Method promises not to change the structure: OK.
  i_only_read(&cm);
  // Pass a "constant" structure into a method that might modify it:
  // diagnosed by compiler (warning or error).
  i_might_modify(&cm);
  // Trying to remove "const" from the pointer: compiler will not allow this...
  // m.cms.s=cstr; 

  mystruct m;
  m.i=99;
  m.s=malloc(10);
  // Struct is not "const", so modification is OK.
  i_might_modify(&m);
  // Convert to "const" struct, without cast.
  i_only_read(&(m.cms));
  return 0;
}

This gives the same guarantees as using const for pointers, which was my goal (sort of a "recursive const").

Potential problems:

  • The setup seems rather complex, other developers will probably have a hard time understanding it (I certainly had).
  • It feels clumsy to have two versions of the same struct.

I'll see if I can actually use this...

Upvotes: 2

jxh
jxh

Reputation: 70472

This solution is a little convoluted. It combines @paddy's suggestion of the union to allow a const char * assignment with another union on a const-ified version of the data structure to provide strict enforcement.

typedef struct const_mystruct {
    const int i;
    const char * const s;
} const_mystruct;

typedef union {
    struct {
        int i;
        union {
            char *s;
            const char *cs;
        };
    };
    const_mystruct cms;
} mystruct;

void i_only_read(const_mystruct *m){
  printf("mystruct: i=%d, s=%s\n", m->i, m->s);
}

int main(int argc, char **argv){
  const char* cstr = "Hello";
  mystruct m;
  m.i=99;
  m.cs=cstr;
  i_only_read(&m.cms);
}

Upvotes: 1

JackCColeman
JackCColeman

Reputation: 3807

I realize that this answer is what your question is trying to avoid. However, short of using a compiler other than gcc or torquing your code into un-readableness, then sometimes a disciplined approach and few comments in the code solve the problem!

What is the purpose of const in the following code fragment?

void i_only_read(const struct mystruct *m){
  printf("mystruct: i=%d, s=%s\n", m->i, m->s);
}

From C's point of view it implies that this function will NOT modify mystruct. If somehow this definition is giving gcc fits, then remove the the const verbage and enforce the "do NOT update" perspective in the code.

// the programmer can/must only read mystruct
void i_only_read(struct mystruct *m){
  printf("mystruct: i=%d, s=%s\n", m->i, m->s);
}

Upvotes: 0

paddy
paddy

Reputation: 63481

Interestingly, you seem to be able to do this in a union:

struct mystruct {
    int i;

    union {
        char *s;
        const char *cs;
    };
};

Now, the rules for union apply: use only the union member that was assigned to. If the function 'promises' to behave, you can assign the string to cs without warning.

Specifically, what you should not do is assign to cs and then pass the struct as a non-const parameter.

Upvotes: 5

Related Questions