1

I was going through const, volatile and restrict type qualifier pages on cppreference. I had lots of doubts and confusions about the explanation/examples given there.

  1. This was an example given there:
char *p = 0;
const char *pp = p;  // OK

char **q = 0;
const char **qq = q;  // ERROR

The explanation for this was

for two types to be compatible, their qualifications must be identical.

But if the bottom has incompatible qualifications, then how top one is compatible? Similar statements were there in both C and V pages.

  1. What is the difference among:
typedef int A[5];
const A x = {1, 2, 3, 4, 5};

const int x[5] = {1, 2, 3, 4, 5};

When I ran them, I did not see any difference. But the page says,

If an array type is declared with the const type qualifier (through the use of typedef), the array type is not const-qualified, but its element type is. (until C23)

An array type and its element type are always considered to be identically const-qualified. (since C23)

I did not get what these two statements are meaning! Similar statements were there in all C, V and R pages.

  1. Is this an error?
typedef int A[2][3];
const A x = {{4, 5, 6}, {7, 8, 9}};
void *unqual_ptr = x;

That page says this is OK till C23. But gcc 11.2 is giving me error in std=C89 itself!

  1. What is the meaning of qualifications used in this way?
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]);

A good explanation on how to read/understand and write such complex qualifications would be appreciated.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35
  • 1
    What exactly is "CVR" supposed to mean? Did you just invent another useless TLA? – Lundin Feb 02 '22 at 13:47
  • @Lundin The first line of the body of the question explains. But I see your point. – Adrian Mole Feb 02 '22 at 13:50
  • @AdrianMole The point is that we 1) shouldn't invent useless TLA and 2) question titles here need to be searchable. – Lundin Feb 02 '22 at 13:55
  • On the first case (`const **`) see here: [Double pointer const-correctness warnings in C](https://stackoverflow.com/q/5055655/10871073) – Adrian Mole Feb 02 '22 at 13:56
  • For the first question, see [Why isn't it legal to convert "pointer to pointer to non-const" to a "pointer to pointer to const"](https://stackoverflow.com/q/2220916/). – jamesdlin Feb 02 '22 at 13:56
  • Overall I suspect there are some incorrect statements on the cppreference site since neither clang nor gcc behaves as that site claims that under `-std=c17`. – Lundin Feb 02 '22 at 14:08
  • @Lundin I had seen the term `cv-qualification` with respect to C++. I just extended it here. Sorry for the confusion though. – Sourav Kannantha B Feb 02 '22 at 14:14
  • That's a C++ only term and far as I know, C++ doesn't support `restrict` unless it was added in recent versions. – Lundin Feb 02 '22 at 14:19

1 Answers1

4

Question 1

for two types to be compatible, their qualifications must be identical.

But if the bottom has incompatible qualifications, then how top one is compatible?

char * and const char * are not compatible types. char *p = 0; const char *pp = p; is allowed because initialization allows assigning a pointer to a non-qualified type to a pointer to a qualified type that would be compatible without the qualifiers.

Initialization inherits its rules from the rules for assignment, and the specific wording for this case is in 6.5.16.1 1:

… the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;…

The “type the left operand would have after lvalue conversion” is the unqualified version of the type, per 6.3.2.1 2:

… an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue;…

The type pp points to is const char. After lvalue conversion, this would be char. And p points to char, and char is of course compatible with char. Further, the type pp points to has all the qualifiers of the type p points to, so this initialization is allowed.

In char **q = 0; const char **qq = q;, qq points to const char *. After lvalue conversion, that would still be const char *, because lvalue conversion would remove the qualifiers of the pointer but not of the type pointed to. So this is not compatible with the type that q points to, char *.

Question 2

typedef int A[5];
const A x = {1, 2, 3, 4, 5};

const int x[5] = {1, 2, 3, 4, 5};

When I ran them, I did not see any difference.

You do not show any experiment that might detect a difference between these, so it is hard to comment.

But the page says,

If an array type is declared with the const type qualifier (through the > use of typedef), the array type is not const-qualified, but its element type > is. (until C23)

This says that in const A x;, the const is transferred from the array to its elements, so the type of x is “array of 5 const int”.

An array type and its element type are always considered to be identically const-qualified. (since C23)

This says a change is expected in a forthcoming version of the C standard, expected in 2023, that will make the type of x be “const array of 5 const int”.

Question 3

typedef int A[2][3];
const A x = {{4, 5, 6}, {7, 8, 9}};
void *unqual_ptr = x;

That page says this is OK till C23. But gcc 11.2 is giving me error in std=C89 itself!

The page appears to be wrong. C 2018 6.5.16.1 1 says:

… the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;…

That case does not allow void *unqual_ptr = x; since the left operand does not have the const qualifier that the right operand does. And none of the other cases in that paragraph would apply.

Question 4

What is the meaning of qualifications used in this way?

void f(int m, int n, float a[restrict m][n], float b[restrict m][n]);

In declarations of function parameters, qualifiers may appear inside the [ and ] of the top-level array declarator. A function parameter that is declared as an array is automatically adjusted to be a pointer instead. Any qualifiers inside the [ and ] are then applied to the pointer instead of the array or its element type. So float a[restrict m][n] declares a to be a restrict-qualified pointer to an array of n float, as if it had been declared float (* restrict a)[n].

Lundin
  • 195,001
  • 40
  • 254
  • 396
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    Also relevant to question 1 is 6.5.4 constraints: "Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast." As in, the quoted parts in 6.5.16.1 are the only allowed implicit pointer conversions. – Lundin Feb 02 '22 at 14:23
  • By the sentence 'qualifiers may appear inside the [ and ] of the top-level array declarator' did you mean `float a[restrict m][n]` is allowed but not `float a[m][restrict n]`? – Sourav Kannantha B Feb 02 '22 at 14:28
  • Regarding last line in answer to question 2, isn't arrays already constant? If I just declare `int x[10];` then I cannot reassign x like `x = y`. By making array elements const, I would not be allowed to make assignments like `x[1] = 1`. But what does does it mean when you say const array? – Sourav Kannantha B Feb 02 '22 at 14:33
  • @SouravKannanthaB: Yes, qualifiers in brackets may appear only in the brackets for an array parameter that is going to be converted to a pointer type. `float a[m][restrict n]` is not allowed. C 2018 6.7.6.2 1 says “… The optional type qualifiers and the keyword `static` shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.” – Eric Postpischil Feb 03 '22 at 00:18
  • @SouravKannanthaB: The reason you cannot use `x = y` where `x` is an array is not because an array is `const`. It is because arrays are automatically converted to pointers except when used as the operand of `sizeof`, as the operand of unary `&`, or as a string literal used to initialize an array. So in `x = y` is automatically converted to `&x[0] = y`, which fails because it attempts to assign to a value that is not an lvalue—`&x[0]` is an expression with a value; it does not designate an object into which anything can be stored. – Eric Postpischil Feb 03 '22 at 00:22
  • 1
    @SouravKannanthaB: However, Clang and GCC appear to treat arrays of `const` elements as `const` even if the array is not declared `const`. For example, they complain that `const int A[3]; void *x = &A;` discards qualifiers. I suspect this is the reason for the forthcoming change in C 2023: It will say explicitly that arrays have the same qualifiers as their elements, and it was recognized this was a defect in previous versions of the standard because it is needed for cases like this example. – Eric Postpischil Feb 03 '22 at 00:24
  • regarding `void *unqual_ptr = x`, the intent was to highlight pre-C23 wording "If the specification of an array type includes any type qualifiers, the element type is so-qualified, **not the array type.**" – Cubbi Feb 17 '22 at 05:02