c++ "const T &const" What is the meaning of the 2nd const? - c++

Could you please explain me the meaning of the second "const" in the following expression:
int i = 42;
const int &const ri = i;
Are there some cases where const T &const is mandatory?
What I understood so far:
Pointers are objects, and they can be reassigned.
int i = 42;
const int *ptr1 = &i; // valid: (low-level const): *ptr1 cannot be changed.
int *const ptr2 = &i; // valid: (high-level const): ptr2 cannot be changed.
const int *const ptr3 = &i: // also valid (combination of 2 precedent lines).
References, unliked pointers are not objects (they have no address) and cannot be reassigned ==> no meaning for an "high-level const"
int i = 42;
int &ri1 = i; // valid: ri1 is a new name for i
const int &ri2 = 7; // valid and const is required
const int &ri3 = i; // valid, what is the use of const?
const int &const ri4 = i; // valid, has the second const an importance?

The form int & const is ill-formed (C++11 §8.3.2):
In a declaration T D where D has either of the forms
& attribute-specifier-seqopt D1
&& attribute-specifier-seqopt D1
and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T,” then the type of the identifier of D is “derived-declarator-type-list reference to T.” The optional attribute-specifier-seq appertains to the reference type. Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3) or of a template type argument (14.3), in which case the cv-qualifiers
are ignored.
So no, it can't be mandatory anywhere. And it has no semantics.

Related

What types of objects can constexpr pointers point to?

cppreference.com says:
A constexpr specifier used in an object declaration implies const.
But I tried to make a constexpr pointer hold the address of a const object of the same base-type but the compiler gave me an error:
const int a = 1;
int main(){
constexpr int *b = &a;
return 0;
}
So, what types can a constexpr pointer point to?
The issue here is not with constexpr. If you said
int *b = &a;
You'd get the same error. i.e. "invalid conversion from const int* to int*"
We can fix that by making it a pointer to a const int.
int const *b = &a;
Now we can add constexpr, and yes, constexpr does imply const
constexpr int const *b = &a;
where b is in fact const. This is exactly the same as the following
constexpr int const * const b = &a;
//^^^^^
// this const is made redundant by the constexpr.
Your example doesn't compile because 'a' is a 'const int', and requires a 'constexpr const int' pointer to point to it.

Compound types, const and auto in C++

I'm trying to understand this piece of code. I'm stuck at figuring out why d and e are int* and const int*. I could use some help.
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
&i means "take the address of i". Since i is a int, the type of &i is int*. The type of d is deduced as int* due to automatic type deduction rules.
The same reasoning can be applied to ci. The only difference is the const qualifier.

Need some explanation behind how the base type is affected by qualifiers and compound types

I'm in the process of learning C++ and I came across this, I just need it explained, I'm guessing I need to read over my books.
All below are legal.
int a, b = 5, c = 10;
int a = 0, *b = &a, &c = a;
int a, *const b = &a, c = 10;
But doing this is illegal, or at least 'b' isn't now a const.
int a = 0, const b = 5, c = 0;
*Edit, I think I've got it myself, it's because a pointer is an object where as the likes of 'b' is just an identifier.
It's a result of the way declarations are constructed. A simple-declaration (the type we are concerned with here) is
decl-specifier-seqopt init-declarator-listopt ;
An init-declarator-list is just a comma-separated list of init-declarators, and an init-declarator is a declarator followed by an optional initializer.
In int const b = 5 (which is totally valid), the const (along with int) is part of the decl-specifier-seq; the b = 5 is the init-declarator - the b is the declarator, the = 5 is the initializer.
In int * const b = 0;, however, the decl-specifier-seq is just int; the declarator is * const b - the * const together is a ptr-operator, which is part of the declarator.
const b is not a valid declarator, and so int a = 0, const b = 5, c = 0; is invalid. * const b is a valid declarator, so int a, *const b = &a, c = 10; is valid.

CV-qualified reference

8.3.2/1:
Cv-qualified references are ill-formed except when the cv-qualifiers
are introduced through the use of a typedef-name (7.1.3, 14.1) or
decltype-specificer (7.1.6.2), in which case the cv-qualifiers are
ignored
int a = 5;
const int &b = a;
int main()
{
}
Compiles fine by both gcc and clang. DEMO
Why? Is it a bug?
Example, provided by the Standard:
typedef int& A;
const A aref = 3; // ill-formed; lvalue reference to non-const initialized with rvalue
In your example you have no cv-qualified reference. The quote of the Standard means the following declarations that are ill formed
int a = 5;
const int & const b = a;
Here b is a cv-qualified reference and the code snippet is ill-formed.
As for your code snippet then it declares a reference to a const object.
This code snippet is ill formed
typedef int& A;
const A aref = 3;
because it is equivalent to
int & const aref = 3;
That it would be more clear compare the two declarations
int x;
int * const p = &x;
int & const r = x;
The declaration of the cv-qualified pointer is valid while the declaration of the cv-qualified reference is ill-formed.

Why can't a const T*& bind to a T*?

VS2010 shows error C2440: 'return' : cannot convert from 'char *' to 'const char *& in f3 below. Why is it not possible to return a char* when the return type is const char*&?
const char* const& f1()
{
static char* x = new char[5]();
return x;
}
char* const& f2()
{
static char* x = new char[5]();
return x;
}
const char* & f3()
{
static char* x = new char[5]();
return x; // error C2440
}
char* & f4()
{
static char* x = new char[5]();
return x;
}
From Paragraph 8.5.3/2 of the C++11 Standard:
A reference cannot be changed to refer to another object after initialization. Note that initialization of a
reference is treated very differently from assignment to it. Argument passing (5.2.2) and function value
return (6.6.3) are initializations.
This basically tells you that returning a value from a function is equivalent to performing an initialization. Therefore, function f3() doesn't compile for the same reason the last initialization in the code snippet below doesn't compile:
char c = 'a';
char* x = &c;
const char*& y = x; // ERROR!
The type of the object referenced by y is const char*, while the type of the expression we are trying to initialize it with (i.e. the type of x) is char*. Those are different types, and when binding a reference the types of the initializer expression and of the object referenced by the initialized reference must be identical (with an exception for base and derived classes, which are not involved here), apart from top-level cv qualifications.
Here, the const qualification in const char* is not a top-level qualification, because it applies to the pointed object, and not to the pointer itself: while the pointed char value cannot be modified through a const char* pointer, the pointer itself can be reassigned.
In Standard terms, this means that the types const char* and char* are not reference-related:
Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. [...]
On the other hand, function f2() compiles, consistently with the fact that the initialization in the last line below is legal:
char c = 'a';
char* x = &c;
char* const& y = x; // OK
Here, the type of the referenced object is char* const, which (unlike const char*) is reference-compatible with char* in the sense defined by the Paragraph quoted above *(the const qualification is a top-level qualification in this case).
In Particular, per 8.5.3/5:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
— If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
[...]
The omitted part is not relevant to this case. Here, char* const and char* are reference-compatible, and the initialization is legal (and f2() compiles). On the other hand, const char* and char* are not reference-compatible, and the initialization is illegal (and f3() does not compile).
For the same reason you can't cast from char ** to const char **. Otherwise you could write
f3() = "hello";
and a subsequent call to f3 would be able to write to the memory of the string literal, aliased to the static local x.
#include <iostream>
void problem( const char*& output, const char* input )
{
output = input; // legal, const char*& = const char*
}
int main() {
const char buff[] = "hello";
char* out = nullptr;
problem( const_cast<const char*&>(out), &buff[0] );
out[0] = 'H'; // I just changed a const array's first character
std::cout << &buff[0] << "\n";
}
This prints "Hello", which is evidence I just engaged in undefined behavior. To do so, I had to use a const_cast. If you could initialize a const char*& with a char*, I wouldn't have to do the const_cast to invoke the undefined behavior.
"But" one might say "this doesn't match the exact case that the OP posted!"
Which is true.
// the "legal" way to do what the OP wants to do, but it leads to undefined
// behavior:
const char*& problem2( char*& c ) { return const_cast<const char*&>(c); }
void problem3( const char*& left, const char* right ) { left = right; }
#include <iostream>
int main() {
char* c = nullptr;
char const* buff = "hello undefined behavior";
problem3( problem2(c), buff );
c[0] = 'H';
std::cout << buff << "\n";
}
also prints "Hello undefined behavior".
"But", one might say, "you turned a char*& into a const char*&, not a char* into a const char*&.
Very well:
void problem3( const char*& left, const char* right ) { left = right; }
const char*& problem4( char x = 0 ) {
static char* bob = nullptr;
if (bob && x)
bob[0] = x;
return const_cast<const char*&>(bob);
}
#include <iostream>
int main() {
char const* buff = "hello problem";
problem3( problem4(), buff );
problem4('H');
std::cout << buff << "\n";
}
Again, that incriminating capital H (well, actually, undefined behavior that typically is a capital H).