Home > Back-end >  Is it legal to write to a `const T*` through a `T**`?
Is it legal to write to a `const T*` through a `T**`?

Time:01-12

I'm currently working on a project where I often have to build linked lists of various C structs. Since I don't want to keep repeating myself setting next pointers, I wrote some helper templates, but soon found out that it falls apart if one of the next fields is a pointer-to-const.

My linked list elements look something like this:

struct WorkingElementType {
  void *pNext;
  /* stuff */
};
struct TroublesomeElementType {
  const void *pNext;
  /* stuff */
};

In reality, there are of course a lot more of these structs. My helper functions have to keep a pointer to the last element's pNext field in order to write to it when the linked list gets extended, so I went for a void **ppNext = &last->pNext. Unfortunately, that of course breaks down with TroublesomeElementType and its const void *pNext.

In the end, what I'd like to achieve is this:

void **m_ppNext;

/* In one function */
m_ppNext = &last->pNext;

/* In a different function, extending the list */
T *elementToAppend = ...;
*m_ppNext = elementToAppend;

I solved this by using a std::variant<void **, const void **> ppNext instead, but using a std::variant and std::visit just for a difference in constness that doesn't even affect the code's function feels like a bit of a waste.

That's why I'm wondering: Is it legal to use const_cast here to cast away const and stuff the const void ** into a void ** only for updating the pointed-to pointer? No const object actually gets modified, after all.

In other words: I'm not sure whether it's legal to alias const void* and void *. (My gut feeling says no, it's not legal because these are incompatible types, but I don't know for sure.)

The C standard in question is C 20.

Here's some simple example code:

#include <variant>


int g_i = 42;


/* This is legal */

void setIntPtr1(std::variant<int **, const int **> v) {
    std::visit([](auto& p) { *p = &g_i; }, v);
}

int testNonConst1() {
    int *i;
    setIntPtr1(&i);
    return *i;
}

int testConst1() {
    const int *i;
    setIntPtr1(&i);
    return *i;
}


/* But I'm not sure about this */

void setIntPtr2(int **p) {
    *p = &g_i;
}

int testNonConst2() {
    int *i;
    setIntPtr2(&i);
    return *i;
}

int testConst2() {
    const int *i;
    setIntPtr2(const_cast<int **>(&i)); // Is this legal?
    return *i;
}

On Godbolt, all of the various test... functions compile to the exact same assembly, but I don't know if testConst2 is legal C .

I've found the following two existing questions:

  1. Is it legal to modify any data pointer through a void **
  2. Why isn't it legal to convert "pointer to pointer to non-const" to a "pointer to pointer to const"

However, both of them don't seem to quite answer my question. The first one deals with casting any T** to a void **, which is not what I'm doing; I'm just casting away constness. The second one asks why it's a compile error to convert a void ** to a const void **, but not whether interpreting the memory of a void * as a const void * and vice-versa (without actually overwriting a const object) would be a violation of the aliasing rules.

CodePudding user response:

Yes, it is legal.

[basic.lval]/11:

If a program attempts to access the stored value of an object through a glvalue whose type is not similar to one of the following types the behavior is undefined:

  • the dynamic type of the object [...]

T* and const T* are similar:

Two types T1 and T2 are similar if they have cv-decompositions with the same n such that corresponding Pi components are either the same or one is "array of Ni" and the other is "array of unknown bound of", and the types denoted by U are the same.

  • Related