What Are Top-Level Const Qualifiers

What are top-level const qualifiers?

A top-level const qualifier affects the object itself. Others are only
relevant with pointers and references. They do not make the object
const, and only prevent modification through a path using the pointer or
reference. Thus:

char x;
char const* p = &x;

This is not a top-level const, and none of the objects are immutable.
The expression *p cannot be used to modify x, but other expressions
can be; x is not const. For that matter

*const_cast<char*>( p ) = 't'

is legal and well defined.

But

char const x = 't';
char const* p = &x;

This time, there is a top-level const on x, so x is immutable. No
expression is allowed to change it (even if const_cast is used). The
compiler may put x in read-only memory, and it may assume that the
value of x never changes, regardless of what other code may do.

To give the pointer top-level const, you'd write:

char x = 't';
char *const p = &x;

In this case, p will point to x forever; any attempt to change this
is undefined behavior (and the compiler may put p in read-only memory,
or assume that *p refers to x, regardless of any other code).

Understanding 'const' at top level, which may reduce code readability without improving const correctness

You are allowed to use const there, but Clang-Tidy is also correct in flagging it as ineffective.

The related Q&As under Should useless type qualifiers on return types be used, for clarity?, Benefits of using “const” with scalar type? cover the part of why const is ineffective in this context.

The other part of the question about why "code readability is reduced", that's because a superfluous const qualifier can potentially distract from the core fact that const'ness is not carried over value assignments. It could, for example, tempt the client code into mistakenly believing that the return value must be used a const as well e.g. const size_t val = get_length(); which is not true, as the posted code shows.

A similar caveat goes for arguments. Suppose the return value of const size_t get_length(); is always passed to another function baz(get_length());. The argument of baz needs not (and arguably should not) be declared as const size_t but just plain size_t i.e. void baz(size_t);.

Top-level or low-level constness or neither?

No, not really.

The distinction you're making is between an object with const-qualified type, and an expression with const-qualified type.

Since (from a usage perspective) it very rarely matters which one is in play, there is no meaningful terminology to distinguish them in any given case.

This does admittedly make it a little cumbersome to explain why the following program is totally valid and well-defined (if really, really bad style), despite casting away the const:

void foo(const int& x)
{
// the expression `x` has type `const int&` (before decay),
// but after casting away the constness we can modify the
// referent, because it happens to actually be non-`const`.
const_cast<int&>(x) = 66;
}

int main()
{
int x = 42; // object is not const!
foo(x);
}

For what it's worth, although "top-level const" is standard terminology, I'm not so sure about your "low-level const". The terms aren't even properly symmetrical! Pfft.

In foo() above we wouldn't actually write the const_cast because we would assume that all inputs are references to objects that really are const.

Where is the definition of `top-level cv-qualifiers` in the C++11 Standard?

From Dan Saks's Top-Level cv-Qualifiers in Function Parameters:

In C++, a cv-qualifier that applies to the first level of a type is called a toplevel cv-qualifier. For example, in:

T *const p;

the top-level cv-qualifier is const, and in:

T const *volatile q;

the top-level cv-qualifier is volatile. On the other hand:

T const volatile *q;

has no top-level cv-qualifiers. In this case, the cv-qualifiers const and volatile appear at the second level.

The signature of a function includes all cv-qualifiers appearing in that function’s parameter types, except for those qualifiers appearing at the top-level of a parameter type.

For example, in:

int f(char const *p);

the const qualifier is not at the top level in the parameter declaration, so it is part of the function’s signature.

On the other hand, in:

int f(char *const p);

the const qualifier is at the top level, so it is not part of the function’s signature. This function has the same signature as:

int f(char *p);

I couldn't find a definition in the standard either but what I posted above is explicitly stated in N3337 §8.3.5-5

After producing the list of parameter types, any top-level
cv-qualifiers modifying a parameter type are deleted when forming the
function type.


Edit:
At the time of writing the above post a definition in the standard could not be found but now there's one as pointed out by Shafik:

n4296 excerpt:

In this International Standard, the notation cv (or cv1 , cv2 , etc.),
used in the description of types, represents an arbitrary set of
cv-qualifiers, i.e., one of {const}, {volatile}, {const, volatile}, or
the empty set. For a type cv T, the top-level cv-qualifiers of that
type are those denoted by cv. [Example: The type corresponding to the
type-id const int& has no top-level cv-qualifiers. The type
corresponding to the typeid volatile int * const has the top-level
cv-qualifier const. For a class type C, the type corresponding to the
type-id void (C::* volatile)(int) const has the top-level cv-qualifier
volatile. — end example ]

What's the meaning of multiple const qualifiers?

In your example all but the top level of indirection all const qualified.

const char            /* const qualified base level */
*const /* const qualified level 1 */
*const /* const qualified level 2 */
* /* regular level 3 */
foo(int bar);

foo is a function that accepts an int argument and returns a regular pointer.

The pointer it returns points to a const qualified pointer

which, in turn, points to another const qualified pointer

which points to const qualified char

C++ : Is class type const always a top-level const?

int const ca = 24;
int a = ca;

You would expect this to compile right? And indeed it does. Here I initialized a integer with the value 24.

You code is the same situation, except that instead of integer you have pointer to integer:

m is const so m.x is const. The x is const, i.e. x cannot be modified (via m). In stricter terms m.x is of type int * const i.e. constant pointer to integer.

int *r = m.x

Here you just initialize the pointer r with the value of the pointer m.x. The fact that m.x is const is not an issue. Both types without their top level cv are identical: pointer to (mutable) integer.



const A &m = a; // clause 1 - Is this top-level const?

Yes Talking about top level const on references is strange indeed. A reference cannot be rebound. However if we are talking about the referenced type, i.e. const A then yes, it is top level. But then again talking about top level const is useful only on pointer types. E.g. int *const cp vs const int *p.



Related Topics



Leave a reply



Submit