How can I enforce an action without changing any part of the specified variable?

When you refer to something, you can add additional const qualifiers so that the bound variable cannot be changed, for example:

 int *ptr; int const * const &rptr = ptr; //ptr can't be changed and *ptr can't be changed 

Or like this, with an array:

 int arr[1]; int const (&rarr)[1] = arr; //arr[0] can't be changed 

Or even so, with an array of pointers:

 int *ptrarr[1]; int * const (&rptrarr)[1] = ptrarr; //ptrarr[0] cannot be changed, but *ptrarr[0] can be 

Why don't I combine them and do it?

 int *ptrarr[1]; int const * const (&why)[1] = ptrarr; //error 

When trying this, Clang 3.5 causes the following error , and GCC 4.8.1 - a similar one:

error: a reference to the type const int *const [1] could not bind to an lvalue of type int *[1]

What is the correct way to protect all parts of the mentioned array of pointers with const ?

Note. This is an artificial example, but I hope this leads to a knowledge of a language that may come in handy later.

+6
source share
1 answer

This is a bit of a bend of the mind, but here's what I think is at the heart of these problems. Here are the "problematic" examples:

 int *ptrarr[1]; int const * (&rptrarr)[1] = ptrarr; // error (1) int const * const (&why)[1] = ptrarr; // error (2) 

Case (1)

First, I will look at an example given in a comment by Dan Nissenbaum, which is as follows:

 int const * (&rptrarr)[1] 

This is actually covered in section 4.4 / 4 of the standard, which describes acceptable cv-qualification conversions when you have several levels of pointers or types. The indicated important requirement is as follows:

if cv 1, j and cv 2, j are different, then const is in each cv2, k for 0 <k <k.

What this means is that when you clear type layers, there must always be all-consts in the outer layers before you achieve cv conversion (i.e. cv1, j and cv2, j are different). This is true for pointer conversions for the first layer (which is covered by pointer conversions, not cv conversions). In other words, this is normal:

 int* p_a; int const * p_b = p_a; // OK 

because the first conversion performed is a pointer conversion (copies the address value), and the second conversion is a cv conversion. Although this is not correct:

 int** p_a; int const ** p_b = p_a; // BAD 

because here the first conversion is still a pointer-transformation, which is only OK if the destination type is compatible (has a valid cv conversion between them). In this case, this requires a cv conversion of this non-constant pointer to a non-const int, to a non-constant pointer to a const int, and this is prohibited in the cited rule of section 4.4 / 4, since the first layer is not const, and for the second level, cv- conversion (from non-const int to const int).

I know this is all confusing, but think about it for a while and it will become clear. Now the reason this rule exists in section 4.4 / 4 is that if you were allowed to do this, I could do this:

 int** pp_a; int const ** pp_b = pp_a; // let say, the compiler accepted this.. int const * p_c; // I have some pointer to a const int (that is really const). pp_b[0] = p_c; // the compiler would have to accept this too!!! 

Obviously, the last line of this example cannot be acceptable in any way, since it will be in complete violation of cv-qualifications, i.e. it is a silent and implicit const-cast, while the whole purpose of these cv conversion rules is to make sure that this is not possible.

At this point, it should be pretty obvious why int const * (&rptrarr)[1] = ptrarr; not allowed, because the first level is a reference that obeys essentially the same conversion rules as pointers, the second level is the conversion of cv from a non-const array of pointers to a non-constant array of pointers, and the last layer adds a const specifier to the int type. And this directly contradicts the rule that I quoted above.

Case (2)

Now, to the main question, which is much less clear, but I think it should be connected with the same argument. Just to repeat, here's the thing:

 int const * const (&why)[1] = ptrarr; // error 

The way I described the first case may be a faulty single layer, but I'm not sure, or maybe the compiler adds one layer too much. As I described, a second const necessary because any conversion that comes before a non-const-const conversion must be of type const as the destination, which is the rule. Case (1) did not work because of these three levels of transformation. Here, if I apply the same logic with the transformation layers, I get three "conversions": (1) from lvalue (ptrarr) to lvalue-reference (why); (2) from non-constant arrays of pointers to const pointers; and (3) from non-const int to const int.

But here is the problem. What to do if conversion cv conversion of "pointer array" is not only a conversion step of one level. If the compiler must break it into two levels, as in: (2-a) from a non-constant array to a non-constant array; and, (2-b) from the pointer is not const for the pointer const. Then it is quite obvious why this will again be connected with the rules in section 4.4 / 4. And in accordance with this hypothesis, all the cases described by you and 2 considered here are perfectly explained.

Now the problem is that I could not find a place in the standard that would explain why this transformation needs to be divided. The standard is pretty clear in section 3.9.3 / 2 that cv qualifiers are not applied to array types and are instead carried over to the array element type. In other words, according to the standard, there is no difference between a “const array T” and a “const T array”. And therefore, this seems to be contrary to the standard.

It’s possible that GCC and Clang are shortened a bit and make the "array-link" the same as the "pointer" (because it's kind of), or that they are a little messed up in ordering conversions (which is unlikely). In any case, I could not find a final justification for such behavior in the standard. I hope someone does this.

Another possible thing that can be played here is alignment. As you probably know, array elements must obey the alignment rules that apply to them. If the alignment rules for int * not the same as for int const * const (either because of the first or second const ), then there is an obvious incompatibility and, therefore, an error. Again, the standard does say that if the alignment rule is different, there is a problem, but I could not find anything that would indicate that the type of the const pointer would have stricter (or different) alignment rules compared to the type non-constant pointer, but it’s not entirely impossible that such incompatibility exists (knowledge of some archaic baggage that the C ++ standard carries).

In any case, this is my best chance to explain it. Hope this can be at least somewhat enlightening.

+1
source

All Articles