'Apparently you can modify const values w/o UB. Or can you?
---- Begin Edit ----
User @user17732522 pointed out the flaw that invokes UB is from the fact pop_back() invalidates the references used according to the vector library documentation. And constexpr evaluation is not required to detect this when it occurs as it's not part of the C++ core.
However, the fix, which was also pointed out by @user17732522, is simple. Replace occurrences of these two consecutive lines of code:
v.pop_back();
v.emplace_back(...);
with these two lines:
std::destroy_at(&v[0]); // optional since A has a trivial destructor
std::construct_at<A, int>(&v[0], ...);
---- Begin Original ----
While references are invalidated upon the destroy_at, they are reified automatically by the construct_at. See: https://eel.is/c++draft/basic#life-8,
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:2
// 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?
Solution 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
