Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a function with a const-reference as its parameter change the underlying object?

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.

like image 553
Franz Ferdinand Avatar asked Aug 10 '26 16:08

Franz Ferdinand


1 Answers

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; [...]

- [dcl.type.cv] p3

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.

- [dcl.type.cv] p4

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.

Impact on Compiler Optimizations

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 stack
  • mov eax, dword ptr [rsp + 4] loads a from the stack after func was called

If 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.

like image 178
Jan Schultke Avatar answered Aug 13 '26 12:08

Jan Schultke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!