Modifying contents of a const std::vector<T> w/o UB

Viewed 126

I would like to create a const std::vector<T> that can store updateable values that user code can access but not (easily) modify. There are several advantages to this. The vector object (but not its contents) cannot be changed. So references to entries in the vector can be established without risk of dangling if someone decides later to add new elements or other operations that can cause dangling references. And, since it would be a complete const object, it cannot be modified even using placement-new without UB and/or compiler complaints.

This seems to be possible because, while the vector object is const, the Ts are not and must be stored as non-consts. Attempting to store them as const produces the following error:

The C++ Standard forbids containers of const elements because allocator<const T> is ill-formed.

See this

So, since the T's are not const, but are only made to appear const when accessed, it appears that they may be accessed and updated by using a const_cast to remove the const.

I haven't run across this use of modifiable const vectors, but it appears to be quite legal. Am I missing something?

Here's the code including constexpr for additional UB testing:

#include <vector>
#include <iostream>

constexpr int foo()
{
    const std::vector<int> v{ 1,2,3 };
    const int& rci = v[0];              // A const ref to v[0] is ok
    int& ri = const_cast<int&>(v[0]);   // A ref to v[0] as a non-const requires a cast
    ri = 42;                            // v[0] is now 42;
    return v[0];
}

void update_const_v(const std::vector<int>& v)
{
    for (const int& i : v)
        const_cast<int&>(i) = i + 1;
}

void print(const std::vector<int>& v)
{
    for (auto& i:v)
        std::cout << i << '\n';
    std::cout << '\n';
}

int main()
{
    const std::vector<int> v{ 1,2,3 };
    const int& ri = v[0];   // A ref to v[0]
    print(v);
    update_const_v(v);
    print(v);
    std::cout << "Reference to first element of const vector: " << ri << '\n';

    // Check for UB using constexpr
    constexpr int i = foo();
    return i;
}

And here's it running in msvc, clang, and gcc.

1 Answers

The only plausible implementation of std::vector that I'm aware of uses a pointer to an array of T. Since your T is non-const, the objects can be safely modified, the same as if you used const std::unique_ptr<T[]>.

This isn't an ironclad guarantee that someone couldn't write a std::vector implementation which works differently, e.g. it could collude with the compiler to place "const vector" data into read-only memory.

So I think you're left with "It should work and not invoke UB, unless a platform does something very unusual." If you're trying to write 100% portable code, I wouldn't make this assumption, but for most practical purposes it seems valid, if a bit weird.

Related