2

Consider the following example:

typedef struct Collection
{
    ...
} Collection;

typedef struct Iterator
{
    Collection* collection;
} Iterator;

Iterator is to offer collection-modifying functions, hence it holds address of a non-const Collection. However, it is also to offer non-modifying functions, which are perfectly legal to use with a const Collection.

void InitIterator(Iterator* iter, Collection* coll)
{
    iter->collection = coll;
}

void SomeFunction(const Collection* coll)
{
    // we want to use just non-modifying functions here, as the Collection is const
    Iterator iter;
    InitIterator(&iter, coll); // warning, call removes const qualifier
}

I'm looking for a solution in C. I do see some options:

1. At Init, cast Collection to non-const. It's probably not undefined behaviour because the object ultimately should not get modified. But it is hair-raising, as having a const object, doing this is asking for trouble. The Iterator is to become a widely used, generic mechanism for working with collections. Having no compiler warnings when one is about to modify a const collection is really bad.

2. Two iterator types, one being a read-only version with a const Collection* member. This complicates usage, potentially requires duplication of some functions, possibly reduced efficiency due to translation step. I really do not want to complicate API and have two different structs along with two sets of functions.

3. Iterator having both pointers, and Init taking both Collection pointers.

typedef struct Iterator
{
    const Collection* collectionReadable; // use this when reading
    Collection* collectionWritable;       // use this when writing
} Iterator;

When having a const Collection, the non-const argument has to become NULL and ultimately trying to modify the collection would crash (trying to dereference NULL pointer), which is good (safe). We have extra storage cost. It is awkward, taking two pointers of the same collection.

I'm also a bit worried about compiler seeing both pointers in same context, meaning that if the Collection gets modified through the writable pointer, the compiler has to realize that the read-only pointer may be pointing to the same object and it needs to be reloaded despite having the const qualifier. Is this safe?

Which one would you pick and why? Do you know any better approach to this problem?

user2231
  • 21
  • 2
  • Why does SomeFunction *have* to take a pointer to const? Just let the user pass a non const object. – reader Mar 21 '15 at 13:21
  • @reader There are countless cases where functions 'guarantee' that they will not modify an object. It benefits optimization and is priceless in multithreaded code. – user2231 Mar 21 '15 at 13:24

1 Answers1

0

Yes, it is safe to have two pointers to the same object, one const and the other non-const. The const just means the object cannot be modified via this particular pointer, but it can be modified via other pointers if there are such pointers. However, although it is safe, this does not definitely mean that you should choose the solution of having two pointers.

For an example of code that works perfectly correctly, see:

int x = 5;
const int *p = &x;
printf("%d\n", *p);
x = 7;
printf("%d\n", *p);

Now *p changes even though p is a const pointer.

Your goal here is to avoid code duplication, so what you want is simply to cast the const pointer to a non-const pointer. It's safe as long as you don't modify the object. There are plenty of similar examples in the C standard library. For example, strchr() takes a const char pointer into the string and then returns a char pointer just in case the string wasn't const and you want to modify it via the return value. I would choose the solution adopted in the C standard library, i.e. typecasting.

You have essentially noticed that the const qualifier in C isn't perfect. There are plenty of use cases when you run into problems like this, and the easiest way forward is typically to accept that it isn't perfect and do typecasting.

juhist
  • 4,210
  • 16
  • 33
  • I'm not sure typecasting is the best solution, because of the specific detail that Iterator is to become externaly widely used object. And I think asking all users to break their const corectness is bad. Otherwise I agree with you - if used only internally, where I can control usage, typecasting would probably be best. Your answer actually made me lean more towards 3rd option, but I'm still undecided. – user2231 Mar 21 '15 at 14:54