Изменение элемента const std::vector‹T› через const_cast

Имеет ли следующая программа неопределенное поведение?

#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";
}

Обратите внимание, что он не использует const_cast для удаления константности из f->x, а вместо этого удаляет константность из f->x[x], которая предположительно представлена ​​отдельным массивом. Или перевод может предполагать, что f->x[1] неизменен после его создания?


person Arch D. Robison    schedule 03.05.2019    source источник
comment
Структура не имеет значения, проблема будет такой же, как int main() { const std::vector<int> y(1); (int&)y[0] = 42; }   -  person M.M    schedule 04.05.2019
comment
Если бы вы написали свой собственный класс, который был бы точным клоном std::vector, я почти уверен, что код был бы законным. Иногда в стандарте есть дополнительные пункты, говорящие о том, что контейнеры стандартных библиотек имеют специальные свойства (например, std::vector неполного типа вызывает UB); однако я не могу найти ничего подходящего в этом случае.   -  person M.M    schedule 04.05.2019
comment
Если у вас есть библиотека, раздающая const std::vector<int> &s, будьте осторожны, чтобы библиотека не предполагала, что ints не меняются, даже если компилятор не может этого предположить.   -  person Caleth    schedule 18.11.2019


Ответы (1)


В вашем примере нет неопределенного поведения.

Вышеприведенный код не вызывает неопределенное поведение, поскольку базовые данные (int) могут изменяться. Давайте рассмотрим более простой пример.

#include <iostream>

struct IntPtr {
    int* data;
};

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

Все это совершенно законно, потому что создание IntPtr const приводит к тому, что data является постоянным указателем на int, а НЕ указателем на константу int. Мы можем изменить данные, на которые указывает p.data; мы просто не можем изменить сам p.data.

const IntPtr p { &a };

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

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

Как этот пример применим к std::vector? Давайте добавим возможность индексации в 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
};

Несмотря на то, что IntPtr является константой, базовые данные могут изменяться, поскольку они создаются путем выделения массива изменяемых целых чисел. То же самое и для std::vector: базовые данные по-прежнему изменяемы, так что const_cast безопасно.

person Alecto Irene Perez    schedule 03.05.2019