This is somewhat related, but also bit different to: C++ "conversion loses qualifiers" compile error
In my code I'm getting following error:
error C2440: 'initializing': cannot convert from 'const git_commit
*const *' to 'const git_commit **'
As much as I understand that assigning from T** to const T** would allow to violate rules of constness, here in the example I gave, that is, assigning from const T*const * to const T** actually gains constness, without loosing any, so where/why this is a problem?
const git_commit * const *
a pointer to a constant array of pointers to constant git_commits
const git_commit * *
a pointer to a mutable array of pointers to constant git_commits
assigning a const array to a mutable array loses const.
int const x = 7;
std::cout << x << '\n'; // compiler can optimize to 7
int const* const px = &x;
std::cout << *px << '\n'; // compiler can optimize to 7
int const*const* ppx = &px;
std::cout << **ppx << '\n'; // compiler can optimize to 7
int const** ppx_cheat = ppx; // illegal, but presume we are allowed to do it
int const y = 1;
int const* py = &y;
*ppx_cheat = py;
std::cout << **ppx << '\n'; // compiler can optimize to 7, *but is wrong*
Related
This may well have been asked in some other way before (I would be surprised if its not) but I am struggling to find it if it is.
Given:
#include <iostream>
#include <string>
int main()
{
int * const pi = new int(1);
long int * const pl = reinterpret_cast<long int * const>(pi);
std::cout << "val: " << *pl << std::endl;
return 0;
}
I get the warning:
<source>: In function 'int main()':
<source>:7:27: warning: type qualifiers ignored on cast result type [-Wignored-qualifiers]
7 | long int * const pl = reinterpret_cast<long int * const>(pi);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ASM generation compiler returned: 0
<source>: In function 'int main()':
<source>:7:27: warning: type qualifiers ignored on cast result type [-Wignored-qualifiers]
7 | long int * const pl = reinterpret_cast<long int * const>(pi);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Execution build compiler returned: 0
Program returned: 0
val: 1
But I am not sure why I get this warning since the re-cast should also be a const pointer - just to a different type. I would like to achieve the same code (which works if ignore the warning), but without the warning.
long int * const is an immutable pointer to mutable data.
reinterpret_cast returns a temporary object. On most temporary objects (including this one), the immutability of the top-level type is irrelevant.
this:
long int * const pl = reinterpret_cast<long int * const>(pi);
is the same as
long int * const pl = reinterpret_cast<long int *>(pi);
the compiler is warning you about it, because presumably you thought typing const there did something, and you are wrong, it did nothing.
Now, this is not directly related to what you asked, but I would be remiss in not mentioning that:
std::cout << "val: " << *pl << std::endl;
results in your program exhibiting undefined behavior. The pointed to object is not a long int, it is an int, and you are accessing it as an object whose type it is not.
reinterpret_cast is not "treat these bytes as a different type", despite many people treating it like it is. Such operations are almost always undefined behavior under the C++ standard.
The proper way to interpret the bytes of object A as an object of type B (when they are suitably trivial types) is to use something like memcpy.
int * const pi = new int(1);
long int l = 0;
static_assert( sizeof(*pi) == sizeof(l) );
::memcpy( &l, pi, sizeof(l) ); // or l = std::bit_cast<long int>(*pi) in C++20
std::cout << "val: " << l << std::endl;
this is legal, if implementation defined output, as you are free to copy bytes of sufficiently trivial types around, and long int is required to have no trap values.
There is no legal way in C++ to have a chunk of memory be read/written to both as a long int and as an int. The amount of aliasing (treating one type as another) in C++ you are allowed to do is limited, because violating aliasing makes certain really powerful optimizations impossible or impractical.
Many programs ignore this fact and rely on the compiler producing "naive" assembly when you do these kind of operations. They are generating code that exhibits undefined behavior.
I wrote the following code:
#include <iostream>
using namespace std;
const int * myFunction()
{
int * p = new int(5);
return p;
}
int main()
{
int * q = myFunction();
cout << "*q=" << *q;
cout << endl;
return 0;
}
I purposely wrote the above code to receive an error. The mistake I made is that I stated the return type of function myFunction() as const int * but when I called myFunction() in main(), the pointer variable q was not declared const. The return type of myFunction() must match exactly to the type of variable which is going to receive its return value (am I correct here? This is what I have understood).
So, I fixed the error by correcting line 11 as const int * q = myFunction();. Now the type of the (pointer)variable q, which is const int *, matched exactly to the return type of myFunction() and the code compiled without error, producing output as *q=5 (is my understanding up to this point correct?).
Then, I wrote the following code:
#include <iostream>
using namespace std;
const int * const myFunction()
{
int * p = new int(5);
cout << "p: " << p;
return p;
}
int main()
{
int a;
const int * q = myFunction();
cout << "\nq=" << q;
cout << "\n*q=" << *q;
delete q;
q = &a;
cout << "\nq: " << q;
cout << endl;
return 0;
}
I was expecting an error here, too. Because now the return type of myFunction() is const int * const but the (pointer)variable q had type const int *. q was not declared as a constant pointer. But the program compiled and I got output as follows:
p: 0x36cb8
q=0x36cb8
*q=5
q: 0x61ff08
I am confused why the second code compiles and runs. What I thought is whoever is going to receive the return value from myFunction() should always take care of it (i.e. it cannot be allowed to take a different memory address), but the pointer variable q took a different memory location.
The return type of myFunction must match exactly to the type of variable which is going the receive it's return value. (Am I correct here? This is what I have understood.)
No, the return type must not match exactly to the type of the variable. But it must be possible to implicitly convert the return type to the type of the variable.
For example something like this will compile:
int someInt = getMeAFloat();
If getMeAFloat returns a float, this will compile because a float can be implicitly converted to an int. (Note that this gives you a warning and is bad because you lose the extra information of the float, but I am just trying to bring my point across)
Your first example does not compile because normally a const int* can not be converted to a int*.
As pointed out by user4581301 the second const in your second example does not matter, because only the value of the pointer, which is returned, gets assigned to the pointer in the main function. The second const makes the pointer itself constant, which has no effect on the value.
That means that const int * const myFunction() is equal to const int * myFunction()
In the 2nd code, q is a const int * - a non-const "pointer to a const int". Since q itself is not const, it can be re-assigned to point at a new address.
The compiler allows q = &a; because an int* (ie, what &a returns since a is an int) can be assigned to a const int*. In other words, a "pointer to non-const data" can be assigned to a "pointer to const data", effectively making read-only access to otherwise-writable data.
The reverse is not true - a "pointer to const data" cannot be assigned to a "pointer to non-const data", as that would allow writable access to read-only data - which is why the 1st code fails to compile.
Here is my code:
using namespace std;
int b = 25;
char x = 'x';
int* p = &b;
int* p2 = &x;
int main()
{
cout << "Pointer p is pointing to the address of variable b: " << p << endl;
cout << "Pointer p2 is pointing to the address of variable x: " << p2 << endl;
return 0;
}
This code will not give me the address of the variable x. It does not compile. It gives me an error as below:
error: cannot convert ‘char*’ to ‘int*’ in initialization
If I run this code with only the integer type I get a hexadecimal address of the variable a.
I am confused because supposedly, int* p2 = &x; should create a integer pointer to the integer address of the single char 'x' as well.
So why can I get the address of a integer but not the address of a char type???
The behaviour of your code would be undefined: your friendly compiler is helping you.
&x is a char* type pointing to a char object, and attempting to convert that to an int* type is a violation of the strict aliasing rule.
One fix would be to write char* p2 = &x; and (const void*)p2 in the corresponding std::cout statement to circumvent the calling of the special overload that std::cout has for const char* types.
The following code allows me to change the value at *p2 even though p2 is declared with const.
int *p1;
const decltype(p1) p2 = new int(100);
*p2 = 200;
cout << "*p2: " << *p2 << endl; // Outputs *p2: 200
However, if I use "int *" instead of "decltype(p1)", then the compiler flags an error.
const int * p2 = new int(100);
*p2 = 200;
cout << "*p2: " << *p2 << endl;
error: assignment of read-only location ‘* p2’
*p2 = 200;
^
I am using g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2.
Does decltype ignore const specifier when applied on pointer variable?
const decltype(p1) p2 means int* const p2 .
This means that you cannot change p2, but you may change the things being pointed to.
const T always applies const to the so-called "top level" of T. When T is a composite type (that is, a type built up from the basic set of types) then you will have to jump through hoops if you want to apply const to the lower levels.
When T is int * , adding top level const would give int * const. The * demarcates a level; to get at the stuff below the * you have to manually remove the *, apply const, then put the * back.
A possible solution is:
const std::remove_pointer<decltype(p1)>::type *p2 = new int(100);
std::pointer_traits would come in handy here. std::pointer_traits::element_type and std::pointer_traits::rebind allow you to write a generic expression which will work well for any pointer-like type:
using element_type = std::pointer_traits<decltype(p1)>::element_type;
using pointer_like_to_const = std::pointer_traits<decltype(p1)>::rebind<std::add_const_t<element_type>>;
pointer_like_to_const p2 = new int(100);
Note that this code would work even if p1 was shared_ptr<int> or unique_ptr<int>.
The const modifier in C++ before star means that using this pointer the value pointed at cannot be changed, while the pointer itself can be made to point something else. In the below
void justloadme(const int **ptr)
{
*ptr = new int[5];
}
int main()
{
int *ptr = NULL;
justloadme(&ptr);
}
justloadme function should not be allowed to edit the integer values (if any) pointed by the passed param, while it can edit the int* value (since the const is not after the first star), but still why do I get a compiler error in both GCC and VC++?
GCC: error: invalid conversion from int** to const int**
VC++: error C2664: 'justloadme' : cannot convert parameter 1 from 'int **' to 'const int **'. Conversion loses qualifiers
Why does it say that the conversion loses qualifiers? Isn't it gaining the const qualifier? Moreover, isn't it similar to strlen(const char*) where we pass a non-const char*
As most times, the compiler is right and intuition wrong. The problem is that if that particular assignment was allowed you could break const-correctness in your program:
const int constant = 10;
int *modifier = 0;
const int ** const_breaker = &modifier; // [*] this is equivalent to your code
*const_breaker = & constant; // no problem, const_breaker points to
// pointer to a constant integer, but...
// we are actually doing: modifer = &constant!!!
*modifier = 5; // ouch!! we are modifying a constant!!!
The line marked with [*] is the culprit for that violation, and is disallowed for that particular reason. The language allows adding const to the last level but not the first:
int * const * correct = &modifier; // ok, this does not break correctness of the code