Why can't a pointer of a "struct with const member" type point to a "struct with non-const member"?

179 Views Asked by At

This code doesn't compile:

struct s_t {
    int a;
};

struct c_s_t {
    const int a;
};

s_t s;
c_s_t *c_s = &s;
ibug@ubuntu:~ $ g++ -fsyntax-only t.cpp
t.cpp:10:15: error: cannot convert ‘s_t*’ to ‘c_s_t*’ in initialization
 c_s_t *c_s = &s;
               ^

However this one compiles perfectly:

int a, *pa = &a, **ppa = &pa, ***pppa = &ppa;
const int * const * const * const cpcpcpa = pppa;

I understand that a pointer that is more CV-qualified at any level can point to a less CV-qualified object at any level, but why isn't it the same for structures?


The above problem statement is a MCVE of a more complex problem, where my friend was trying to convert pointers between t_s_t<T> and t_s_t<const T>, where t_s_t is a template struct type with one template parameter typename, and T is an arbitrary type.

4

There are 4 best solutions below

0
On BEST ANSWER

The reason is that s_t and c_s_t are different types.

Even if you define c_s_t as:

struct c_s_t {
    int a; // <-- non-const!
};

Then:

s_t s;
c_s_t *c_s = &s;

It is still not going to work.

0
On

Those two are different structs, and they're not convertible. The fact that they have the same members is irrelevant, and even if you removed const from c_s_t, it wouldn't change anything.

Your other example works because you're applying modifiers to one type. For example, this is perfectly legal:

struct s_t {
    int a;
};

s_t s;
const s_t const* cs = &s;
0
On

Type error is your problem you are simply trying to assign the wrong types.

This would work:

s_t s;
s_t *c_s = &s; //types are matching
11
On

I understand that a pointer that is more CV-qualified at any level can point to a less CV-qualified object at any level

This isn't actually true, at least not in the way you've described. Only the topmost CV-qualifier can be added arbitrarily (and, of course, a CV-qualifier on the pointer itself!), which is the case in both C and C++.

Here's a counter-example for the "any level" notion, taken straight from [conv.qual/3] in the current draft standard:

[ Note: If a program could assign a pointer of type T** to a pointer of type const T** (that is, if line #1 below were allowed), a program could inadvertently modify a const object (as it is done on line #2). For example,

int main() {
  const char c = 'c';
  char* pc;
  const char** pcc = &pc;       // #1: not allowed
  *pcc = &c;
  *pc = 'C';                    // #2: modifies a const object
}

— end note ]

Anyway, then you ask:

but why isn't it the same for structures?

Sure, you can point a const T* to a T, but that's not what you're doing. This rule doesn't apply recursively. Classes can hold more than one member so your approach just doesn't work in general (and there's no need for a special rule for single-member classes).

In this particular case, the two classes are layout-compatible, so I'd expect a reinterpret_cast to appear to work most of the time:

struct s_t {
    int a;
};

struct c_s_t {
    const int a;
};

int main()
{
   s_t s;
   c_s_t *c_s = reinterpret_cast<c_s_t*>(&s);
}

(live demo)

However, it appears that aliasing on the merits of layout-compatibility is not actually well-defined so ultimately you're better off rethinking your design.

tl;dr: Different types are different types.