На каких компиляторах или с какими флагами компилятора нарушение правил арифметики указателей может вызвать проблемы?

Стандарт c ++ ограничивает арифметику указателей для выполнения в массиве ([Expr.add]) что затрудняет реализацию вектороподобных контейнеров.

Можно реализовать вектороподобный контейнер с реализацией, подобной этой:

//First approach

//Allocation
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
auto p=new(buffer) int{};
new(p+1) int{};
//Example of use of an iterator, assign 10 to the second element.
*(p+1)=10;//UB p+1 is a pointer past the end of an object.

Этот предыдущий код иллюстрирует, как примерно std::vector реализовано в libstdc ++ и libc ++. Это кажется
что компиляторы принимают этот вид кода как расширение языка c ++.

Если я хочу быть стандартным, я мог бы реализовать vector и его связанный iterator таким образом, что операции, выполняемые над вектором и его итератором, могут быть упрощены до следующего кода:

//Second approach

//Allocation:
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
new(buffer) int{};
new(buffer+sizeof(int)) int{};
//Example of use of an iterator assign 10 to the second element
*(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=10;

(Первый вопрос, не является ли этот подход также UB? Здесь арифметика указателей выполняется для массива unsigned char, который обеспечивает хранение для объектов int. launder используется потому что buffer и int объекты не являются взаимозаменяемыми указателями)

Проблема с этим вторым подходом — это код, сгенерированный компилятором (GCC):

#include <new>

int test_approach_1(unsigned char* buffer){
//Construction
auto p = new(buffer) int{};
new(p+1) int{10};
//Example of use of an iterator assign 10 to the second element
*(p+1)=13;//UB
return *(p+1);//UB
}

int test_approach_2(unsigned char* buffer){
//Construction
new(buffer) int{};
new(buffer+sizeof(int)) int{10};
//Example of use of an iterator assign 10 to the second element
*(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=13;
return *(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))));
}

Сгенерированная сборка:

test_approach_1(unsigned char*):
movabs  rax, 55834574848
mov     QWORD PTR [rdi], rax
mov     eax, 13
ret
test_approach_2(unsigned char*):
movabs  rax, 42949672960
mov     QWORD PTR [rdi], rax
mov     eax, 13
mov     DWORD PTR [rdi+4], 13
ret

Код, сгенерированный для test_approach_1 оптимально. Поэтому я думаю, что не буду использовать второй подход (и у меня была бы еще одна причина не использовать его, если кто-то покажет, что это тоже UB.)

Я не нахожу документацию для этих расширений языка, которые позволяют нам реализовывать векторные контейнеры, используя первый подход (это UB согласно стандарту). Есть ли документация для этого? На каком компиляторе можно ожидать его работы и с какими флагами компилятора?

1

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …