Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can contents of a `const vector` be modified w/o UB?

Tags:

c++

A const vector can't be modified as it's a const object. So inserts, appends, erases, are not allowed. However, its contents are not part of that object but are owned by it. As a similar example:

int* const p = new int[10]{1,2,3,4};

p is a const object that owns non-const data which can be modified: p[1]=5;

Vector's operator[] is conditioned on whether vector is const and if so returns a const int& But if the underlying value wasn't const then a const cast removing const should be legal.

To test this I wrote the following program:

#include <vector>

constexpr int foo()
{
    const std::vector<int> v{ 1,2,3 };
    const int a[3]{ 1,2,3 };
    *const_cast<int*>(&v[1]) = 21;

    // However, this should fail and does on GCC and CLANG
    //*const_cast<int*>(&a[1]) = 21;
    return v[1];
}

int main()
{
    constexpr int sb21 = foo();
    const std::vector<int> v{ 1,2,3 };
    *const_cast<int*>(&v[1]) = 21;
    return v[1] + sb21;
}

compiler explorer

MSVC, CLANG, and GCC all compile and execute.

The code evaluates a constexpr function at compile time. Compilers are supposed to produce compile time errors on UB. For comparison if the array, which contains const elements, is uncommented, Clang and GCC both produce errors as expected. However, MSVC does not which appears to be a bug.

Use case is having a fixed size vector that can't be structurally altered but can have contents updated.

std::vector<T> uses std::allocator<T> and so long as the library implementation of vector doesn't use small sizes like std::string's short string optimization then this should be defined behavior.

Here's an example showing how a const std::string exhibits UB for small strings that are stored within the object while longer allocated ones do not:

#include <string>
consteval int foo()
{
    const std::string v{ "1234" };
    //const std::string v{ "123412341234123412341234" };
    *const_cast<char*>(&v[1]) = 'A';
    return v[1];
}
int main()
{
    return foo();
}

Compiler Explorer

Is this defined behavior or are the compilers not flagging UB?

like image 560
doug Avatar asked Oct 19 '25 03:10

doug


1 Answers

But if the underlying value wasn't const then a const cast removing const should be legal.

This is the weak point of your argument. It's not the underlying value that matters, but the owned object. If the owned object is not a const object, then removing the cast should be legal. However, can you prove that the owned object is not const?

I believe you cannot. Take your own example – a vector containing three ints. Hypothetically, suppose each int is 4 bytes, so the total data is 12 bytes. Also suppose the size of a vector is 24 bytes (allowing 8 bytes for each of a pointer, size, and capacity). It would not be unreasonable to optimize a bit and store the three ints in the vector itself, along with a flag to say that the data is inside the vector instead of being dynamically allocated (a similar approach is used in short string optimization).

Now that we have the possibility that the data is inside the vector itself, we have the possibility that the data is part of a const object, because the vector is const. Casting away this constness to change a value is undefined behavior.

The bottom line is that if you do not own the object, you cannot know for sure how it was created. If the owner tells you it is const, then you have to treat it as const.

like image 196
JaMiT Avatar answered Oct 20 '25 17:10

JaMiT



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!