Home > Net >  Apparently you can modify const values w/o UB. Or can you?
Apparently you can modify const values w/o UB. Or can you?

Time:04-04

It's well established you can't modify const values unless they were originally non const. But there appears an exception. Vectors containing objects with const members.

Here's how:

#include <vector>
#include <iostream>

    struct A {
        constexpr A(int arg) : i{ arg } {}
        const int i;
    };

int main()
{
    std::vector<A> v;
    v.emplace_back(1);  // vector of one A initialized to i:1
    A& a = v[0];

    // prints: 1 1
    std::cout << v[0].i << " " << a.i << '\n';

    //v.resize(0); // ending the lifetime of A and but now using the same storage
    v.pop_back();
    v.emplace_back(2);  // vector of one A initialized to i:1

    // prints: 2 2
    std::cout << v[0].i << " " << a.i << '\n';

}

Now this seems to violate the general rule that you can't change the const values. But using a consteval to force the compiler to flag UB, we can see that it is not UB

consteval int foo()
{
    std::vector<A> v;
    v.emplace_back(1);  // vector of one A initialized to i:1
    A& a = v[0];
    v.pop_back();
    v.emplace_back(2);
    return a.i;
}

// verification the technique doesn't produce UB
constexpr int c = foo();

So either this is an example of modifying a const member inside a vector w/o UB or the UB detection using consteval is flawed. Which is it or am I missing something else?

CodePudding user response:

It is UB to modify a const object.

However it is generally not UB to place a new object into storage previously occupied by a const object. That is UB only if the storage was previously occupied by a const complete object (a member subobject is not a complete object) and also doesn't apply to dynamic storage, which a std::vector will likely use to store elements.

std::vector is specified to allow creating new objects in it after removing previous ones, no matter the type, so it must implement in some way that works in any case.

What you are doing has undefined behavior for a different reason. You are taking a reference to the vector element at v[0]; and then you pop that element from the vector. std::vector's specification says that this invalidates the reference. Consequently, reading from the reference afterwards with a.i has undefined behavior. (But not via v[0]).

So your code has undefined behavior (since you are doing this in both examples).

However, it is unspecified whether UB in standard library clauses of the standard such as using the invalidated reference needs to be diagnosed in constant expression evaluation. Only core language undefined behavior needs to be diagnosed (and even then there are exceptions that obviously shouldn't be required to be diagnosed, since it is impossible, e.g. order-of-evaluation undefined behavior). Therefore the compiler does not need to give you an error for the UB here.

Also, consteval is not required here. constexpr would have done the same since you already force constant evaluation with constexpr on the variable.

  •  Tags:  
  • c
  • Related