references to pointers and constness compatibility - c++

For learning purposes, I've written the following code:
void Swap(const int *&Pointer1, const int *&Pointer2)
{
const int *Tmp = Pointer2;
Pointer2 = Pointer1;
Pointer1 = Tmp;
}
I have a few doubts about this code and how top/low level constness works in such cases, with 3 or more "levels".
Obviously my references can't be const, otherwise I wouldn't be able to swap the pointers. Let's suppose however that the code would not touch the pointers values (the adresses they contain): the right syntax would be const int *(const &Pointer) or int * const &Pointer? I have the feeling the latter would mean "reference to a const pointer to const int, but I'm not sure. And if that is the case, the const part would be ignored by the compiler like a more simple const int pass by value or would not because it is under a reference?
Trying to call this function with pointer to int fails. However, it is possible to assign an int adress to pointer to const int and indeed I get no error if I simply remove the references. This makes me think that those references "forces" every const to perfectly match. Is this true? And if so, there is some way around it?

If you want Pointer to be const too, then it would be int const * const & Pointer, let's read it from right to left; so Pointer is a reference to const pointer to const int. (Note that means both Pointer itself and the int pointed by Pointer can't be changed either. This might conflict the intent of Swap.) And both the two const parts won't be ignored when pass-by-reference. Unlike pass-by-value, reference can't be top-level const qualified, and constness qualifed on what it refers to is reserved.
You can't pass int * to the function taking const int *& (i.e. lvalue-reference to non-const pointer). int * could convert to const int* implicitly, but the converted const int* is a temporary, which can't be bound to lvalue-reference to non-const. Temporary could be bound to lvalue-reference to const (or rvalue-reference), so change the parameter type to int const * const & Pointer as stated in #1, passing int * would be fine.

template <class P1,class P2>
void Swap(P1 && Pointer1, P2 && Pointer2)
{/*...*/}
int main()
{
const int a =1, b = 2;
Swap(&a, &b); // &a and &b - r-value, Pointer1 and Pointer2 param this 'const int* &&'
const int * const a_cref_p = &a;
const int * const b_cref_p = &b;
Swap(a_cref_p,b_cref_p); // a_cref_p and b_cref_p - l-value, Pointer1 and Pointer2 param this 'const int* const &'
const int * a_ref_p = &a;
const int * b_ref_p = &b;
Swap(a_ref_p,b_ref_p); // a_ref_p and b_ref_p - l-value, Pointer1 and Pointer2 param this 'const int* &'
return 0;
}

Related

C++) why const int*& parameter can't take int* argument? [duplicate]

This question already has answers here:
Binding a const pointer reference to a non-const pointer
(2 answers)
Closed 11 months ago.
before writing, I'm not good at english. So maybe there are many awkward sentence.
void Func1(const int* _i) { };
void Func2(const int& _j) { };
void Func3(const int* (&_k)) { };
int main()
{
int iNum = 1;
int* pInt = new int(1);
Func1(pInt); // works;
Func2(iNum); //works
Func3(pInt); // error
}
I use Visual Studio and error message said
"Cannot convert argument 1 from 'int *' to 'const int *&'"
I know it cant convert because of '&'. _i equals pInt so it may change dereference.
But i used const. so i think it will work but const keyword doesnt work.
why const keyword doesnt work unlike other cases? ex)Func1,Func2
Func1(pInt); // works; int* could convert to const int* implicitly
Func2(iNum); //works; int could be bound to const int&
Func3(pInt); // error;
pInt is a int*, when being passed to Func3 which expects a reference to const int*, then it would be converted to const int*, which is a temporary and can't be bound to lvalue-reference to non-const like const int* & (lvalue-reference to non-const pointer to const int).
If you change the parameter type of Func3 to int* &, then no conversion is required, pInt could be bound directly. Or change to const int* const & (lvalue-reference to const pointer to const int) or const int* && (rvalue-reference) which could bind to temporary.

What is a legal definition of a pointer to a pointer to a const object?

I know from this answer that a pointer const int** z is supposed to be read as
Variable z is [a pointer to [a pointer to a const int object]].
In my humble opinion, this would mean if z=&y then y should be a pointer to a const int object. However, the following code also compiles:
int x=0;
int const* y=&x;
const int** z=&y;
Why is an int const* object i.e. a const pointer to an int instead of a pointer to a const int acceptable to be the pointed object of z?
You are misunderstanding what the const refers to. A const always refers to the element to the left of it - unless it is the leftmost element itself, in which it refers to the element to the right.
This means that
int const * is a pointer to a const int, not a const pointer to int as you think. To get that, you would have to write int * const
int const * and const int * are two ways of writing exactly the same: a pointer to a const int.
You get it right if you read declarations from right to left. If const is the leftmost element, add a "that is" before it when reading. Ex:
const int *: pointer to int that is const.
int const *: pointer to const int.
int * const: const pointer to int.
const int * const: const pointer to int that is const.
int const * const: const pointer to const int.
Note that 1/2 are the same, and 4/5 are the same. 1 and 4 are called "west const" since const is on the west/left side, while 2 and 5 are called "east const".
Why is an int const* object i.e. a const pointer to an int
No.
int const * and const int * are the same type.
People sometimes prefer writing int const * because it reads right-to-left as "pointer to a const int", whereas const int * really reads as "pointer to an int (which is const)".
A const pointer to an int is int * const.
Try it:
int a = 42;
const int * y = &a;
int const * z = &a;
*y = 24; // compile error assigning to const
*z = 24; // compile error assigning to const
int b = 0;
y = &b;
z = &b; // re-pointing non-const pointers is fine
*z = 1; // still a compile error
int * const x = &a;
*x = 24; // fine, assigning via pointer to non-const
x = &b; // error reassigning a const pointer

Why can i change value of int* in const int** type when getting memory from heap

have some issues with const qualifier.
I tried to do smth like this:
int a{ 3 };
int *p{ &a };
//const int **pp{ &p }; // FIXME: Compiler demands that p must have a const int *type
const int *const* pp{ &p }; // works here but it means, that I have a pointer to the const pointer to the const value
*pp = nullptr; // doesn't work cause of upper type
Results in error:
<source>:8:5: error: read-only variable is not assignable
*pp = nullptr; // doesn't work cause of upper type
~~~ ^
BUT: if i deal with new it works funny
const int **pp1{ new const int* {&p} }; // Here I have a poiner to pointer to const int, so I can change it
*pp1 = nullptr; // works
So, why does compiler demands a const int* const*? Compiler - MSVC, standart - c++17
UPD:
const int *p{ nullptr };
const int **pp{ &p }; // My intention
*pp = nullptr; // it works cause pointer non - const
But how can i get same result without const in const int *p?
There are cases where this site https://cdecl.org/ for C syntax can also help with C++. For this:
const int *const* pp
it tells me:
declare pp as pointer to const pointer to const int
For this:
const int **pp1
it tells me:
declare pp1 as pointer to pointer to const int
Let's use that....
*pp = ... // ERROR !
When you dereference pp then you get a "const pointer to const int" (because pp is "pointer to const pointer to const int"). You cannot assign to a const whatever. What counts it the top level const!
*pp1 ... // OK !?!
When you dereference pp1 you get a "pointer to const int" (because pp1 is a "pointer to pointer to const int"). A pointer to const int is not constant. What counts is the top level const! It points to a const int but the pointer itself is not const.
Conclusion: The difference between your two versions is not the use of new but the type of the pointer that you dereference (and then try to assing to the pointee).

Reference to pointer to const as function argument

I have the following base code:
Base Code
int main()
{
int i = 1;
const int* p = &i;
int* q = &i;
test_ptr(p);
test_ptr(q);
}
Can anyone explain why the first and third example work with the above base code, but the second one doesn't?
Example Implementation test_ptr()
Example 1 works. This works because function with pointer to const int will also accept a pointer to non-const int (but not the other way around)
void test_ptr(const int* p) // pointer to const int
{
}
Example 2 doesn't work. I don't really understand why. It is still a pointer to const int, but passed as a reference. This doesn't align with my understanding about how references work. It fails when I pass a non-const pointer to the function.
void test_ptr(const int*& p) // reference to pointer to const int
{
}
Example 3 works again and I am completely lost. So if case 2 does not work, why does it work again if I express the int* as a typedef?
typedef int* int_ptr;
void test_ptr(const int_ptr& p) // like case 2 but int* expressed as typedef
{
}
This also happens when I use pointer-to-pointer instead of reference-to-pointer.
Edit: Example 3 needs a different main function to make use of the typedef:
int main()
{
int i = 1;
const int_ptr p = &i; // use typedef here
int_ptr q = &i; // use typedef here
test_ptr(p);
test_ptr(q);
}
Example 2:
void test_ptr(const int*& p);
This works for const int* but not int* because the conversion from int* to a const int* implies a temporary and binding to a temporary has to be done using a const& for life extension to kick in.
Example 3 (when using the first main version):
typedef int* int_ptr; // or: using int_ptr = int*;
void test_ptr(const int_ptr& p);
This is the same as both of these:
void test_ptr(int_ptr const& p);
void test_ptr(int* const& p);
const is applied to the new type from right to left so it's not the int that is const, it's the pointer. The function will therefore accept int*, but not const int* since the function is allowed to change the int:s according to its signature.
The function that would accept both int* and const int* should have one of these equivalent signatures:
void test_ptr(const int* const& p);
void test_ptr(int const* const& p);
Disclaimer: I'm very unsure about the wording used in this answer

Understanding const

If you want to write an iterator, you usually write
T* operator->() const;
My problem is understanding this "const" with pointers and reference.
For instance, you can write the following struct:
struct A{
int* x;
A(int& x0):x{&x0}{}
int* ptr() const {return x;}
int& ref() const {return *x;}
};
And you can use it this way:
int x = 10;
const A a{x};
int& r = a.ref();
int* p = a.ptr();
*p = 4;
cout << x << endl; // prints 4
r = 25;
cout << x << endl; // prints 25
But why this compiles and works right (at least with g++ and clang). Why?
As I defined
const A a{x};
this "a" is const. So when I call
int* p = a.ptr();
I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?
And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns? Something like "int& const"??? <--- I suppose this is the same as "int&".
Thanks for your help.
There is a different between bitwise const and logical const.
When you have a const A, you can't modify its int* member. But there's a difference between modifying the member itself (which int that x points to) and modifying through the member (the value of the int that x points to). Those are different kinds of const. In the simplest case:
struct Ptr { int* p; };
int i = 42;
const Ptr ptr{&i};
*ptr.p = 57;
ptr.p still points to i, nothing changed there, so the const mechanic is enforced. But it's not logically const since you still changed something through a const object. The language doesn't enforce that for you though. That's up to you as the library writer.
If you want to propagate const-ness, you just provide an interface that is logically const:
int const* ptr() const {return x;}
int const& ref() const {return *x;}
// ^^^^^
Now, users can't modify through your const A both bitwise (can't change what x points to) and logically (can't change that value of that pointee either).
But why this compiles and works right (at least with g++ and clang). Why?
Because the program is well formed and has defined behaviour. Const correctness was not violated.
I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?
Because it is completely OK to make copies of const objects. Those copies need not to be const. Copying an object does not make modifications to the original object (assuming there is no user defined copy constructor that does silly things).
And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns?
int& ref() always returns int&. Just like int* ptr() always returns int*.
Something like "int& const"???
There is no such thing like int& const. References cannot have top level qualifiers (they can never be re-assigned).
In struct A, when you make an instance of it const, you make the pointer constant, but that doesn't automatically make the pointed-to object constant.
Declaring something like const A a(ref); is basically equivalent to invoking the following code:
struct A_const {
int * const x;
A(int& x0):x{&x0}{}
int* ptr() const {return x;}
int& ref() const {return *x;}
};
If you remember your pointer rules, this means that x is a constant pointer, which means it cannot be made to point to something else (it's functionally similar to a reference, but can be null), but critically, the int that it is pointing to is not constant, which means nothing stops you from writing something like this:
int val = 17;
const A a(val);
*(a.val) = 19; //Totally valid, compiles, and behaves as expected!
int val2 = 13;
//a.val = &val2; //Invalid, will invoke compile-time error
This is also the reason why std::unique_ptr<int> and std::unique_ptr<const int> represent different objects.
If you want the pointed-to object to not be modifiable on a const object, you need to enforce that in the code itself. Since functions can be overloaded on the basis of whether the source object is const or not, that's pretty easy:
struct A {
int * x;
A(int& x0):x{&x0}{}
int * ptr() {return x;}
int & ref() {return *x;}
int const* ptr() const {return x;}
int const& ref() const {return *x;}
};
int val = 17;
A a(val);
a.ref() = 19;//Okay.
*a.ptr() = 4;//Okay.
const A b(val);
b.ref() = 13;//Compile error
*b.ptr() = 17;//Compile error