---- 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?