A const reference to a non-const variable can be cast to a non-const reference and then the underlying object can be modified through the reference. Does it also hold for const-references in function declarations?
Namely, in the following code, is func() allowed to change 'a' provided 'a' is not originally const? Or would this lead to undefined behaviour?
void func(const int& arg);
int main()
{
int a = 5; //non-const
func(a);
std::cout << a; //Can func modify 'a' through a const-cast and thus output ≠5?
}
I am asking this since this would prevent the compiler from doing an optimization as it is forced to look the value of 'a' up again after func is evaluated; especially if the definition of func is in another translation unit.
Yes, it can.
Calling func
will make a reference which has an "unnecessary" const
:
A pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does; [...]
func
can remove this "unnecessary" const
with const_cast
:
void func(const int& arg)
{
int& evil = const_cast<int&>(arg); // OK so far
evil = 123; // undefined behavior only if arg refers to a const object
}
In this case, func
can modify a
through evil
, because a
is not a const object. However, if a
was actually const
, then this would be undefined behavior:
Any attempt to modify a const object during its lifetime results in undefined behavior.
In general, if a function is given a reference or pointer, it can simply const_cast
or reinterpret_cast
that reference to any type it wants. However, if the type of object that is accessed is not similar to that of the reference, this is undefined behavior in most cases. The access may be undefined, the const_cast
or reinterpret_cast
itself is fine.
Consider the following simplified example:
void func(const int&); // "Black box" function.
// The compiler cannot prove that func isn't modifying
// its arguments, so it must assume that this happens.
int main()
{
int a = 5;
func(a); // func may be modifying a.
return a; // The compiler cannot assume that this is 'return 5'.
}
With optimizations, clang outputs:
main:
push rax
mov dword ptr [rsp + 4], 5
lea rdi, [rsp + 4]
call func(int const&)@PLT
mov eax, dword ptr [rsp + 4]
pop rcx
ret
See live example at Compiler Explorer
This code does suffer from the fact that func
can modify a
.
mov dword ptr [rsp + 4], 5
stores a
on the stackmov eax, dword ptr [rsp + 4]
loads a
from the stack after func
was calledIf you write const int a = 5;
instead, the assembly ends with mov eax, 5
, and a
is not spilled onto the stack, because it cannot be modified by func
. This is more efficient.