13

Does the following program have undefined behavior?

#include <iostream>
#include <vector>

struct Foo
{
    const std::vector<int> x;
};

int main()
{
    std::vector<int> v = {1,2,3};
    auto f = new Foo{v};
    const_cast<int&>(f->x[1]) = 42; // Legal?
    std::cout << f->x[1] << "\n";
}

Note that it not using const_cast to strip constness from f->x, but instead stripping constness from f->x[x], which presumably is represented by a separate array. Or is a translation allowed to presume that f->x[1] is immutable after it is created?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Arch D. Robison
  • 3,829
  • 2
  • 16
  • 26
  • 5
    The struct makes no difference, the issue would be the same as `int main() { const std::vector y(1); (int&)y[0] = 42; }` – M.M May 03 '19 at 22:55
  • 4
    If you wrote your own class that was an exact clone of std::vector I'm pretty confident that the code would be legal . Sometimes the standard has extra clauses saying that standard library containers have special properties (e.g. std::vector of incomplete type causes UB); however I can't find anything relevant in this case. – M.M May 04 '19 at 03:44
  • 1
    If you have a library handing out `const std::vector &`s, then be careful that the library isn't assuming the `int`s don't change, even if the compiler can't assume that. – Caleth Nov 18 '19 at 15:05

1 Answers1

2

There is no Undefined Behavior in your example.

The above code does not invoke undefined behavior because the underlying data (int) is mutable. Let's look at a simpler example.

#include <iostream>

struct IntPtr {
    int* data;
};

int main() {
    int a = 0;
    const IntPtr p { &a }; 
    *p.data = 10;
    std::cout << a; // Prints 10
}

All of this is perfectly legal to do because making IntPtr const results in data being a constant pointer to an int, NOT a pointer to a constant int. We can modify the data that p.data points to; we just can't modify p.data itself.

const IntPtr p { &a };

*p.data = 10; // This is legal

int b;
p.data = &b; // This is illegal (can't modify const member)

So how does this example apply to std::vector? Let's add the ability to index into an IntPtr:

class IntPtr {
    int* data;
   public:
    IntPtr() = default;
    IntPtr(int size) : data(new int[size]) {}
    
    // Even though this is a const function we can return a mutable reference 
    // because the stuff data points to is still mutable. 
    int& operator[](int index) const {
        return data[index]; 
    }
};

int main() {
    const IntPtr i(100); 
    i[1] = 10; // This is fine
};

Even though the IntPtr is const, the underlying data is mutable because it's created by allocating an array of mutable ints. It's the same for std::vector: the underlying data is still mutable, so it's safe to const_cast it.

Community
  • 1
  • 1
Alecto Irene Perez
  • 10,321
  • 23
  • 46