Можно ли использовать std :: wash для преобразования указателя объекта в свой указатель на массив?

Текущий проект стандарта (и предположительно С++ 17) говорит в [basic.compound/4]:

[Примечание. Объект массива и его первый элемент не являются взаимопереключателями, хотя они имеют одинаковый адрес. - конечная нота]

Таким образом, указатель на объект не может быть reinterpret_cast 'd, чтобы получить свой встроенный указатель на массив.

Теперь, std::launder washder, [ptr.launder/1]:

template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;

Требует: p представляет адрес A байта в памяти. Объект X, который находится в пределах его времени жизни и тип которого аналогичен T, расположен по адресу A. Все байты хранения, которые могут быть достигнуты через результат, достижимы через p (см. Ниже).

И определение достижимости находится в [ptr.launder/3]:

Замечания: вызов этой функции может использоваться в основном постоянном выражении всякий раз, когда значение его аргумента может использоваться в выражении постоянной константы. Байт памяти доступен по значению указателя, указывающему на объект Y, если он находится в хранилище, занятом Y, объектом, который является взаимно конвертируемым с Y, или непосредственно окружающим объектом массива, если Y является элементом массива. Программа плохо сформирована, если T - тип функции или cv void.

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

Но. Если p указывает на объект массива, байты массива достижимы в соответствии с этим определением (даже если p не является взаимно конвертируемым с указателем на указатель), как и результат стирания. Таким образом, кажется, что в определении ничего не говорится об этой проблеме.

Таким образом, можно ли std::launder использовать для преобразования указателя объекта в свой указатель на массив?

Ответ 1

Это зависит от того, является ли объект охватывающего массива полным объектом, а если нет, можете ли вы получить доступ к большему количеству байтов с помощью указателя на этот охватывающий объект массива (например, потому что он сам является элементом массива или взаимопревращаемым указателем с более крупным объектом, или указатель-взаимообратимый с объектом, который является элементом массива). Требование "достижимое" означает, что вы не можете использовать launder для получения указателя, который позволит вам получить доступ к большему количеству байтов, чем позволяет значение указателя источника, при боли от неопределенного поведения. Это гарантирует, что возможность того, что какой-то неизвестный код может вызвать метод " launder, не влияет на анализ escape-кода компилятора.

Полагаю, некоторые примеры могут помочь. Каждый пример ниже reinterpret_cast a int* указывающий на первый элемент массива из 10 int в int(*)[10]. Поскольку они не являются взаимопереключателями, reinterpret_cast не изменяет значение указателя, и вы получаете int(*)[10] со значением "указатель на первый элемент (независимо от массива)". Каждый пример затем пытается получить указатель на весь массив, вызвав std::launder на указатель заливки.

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); 

Хорошо; вы можете получить доступ ко всем элементам x через указатель источника, а результат launder не позволит вам получить доступ к чему-либо еще.

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 

Это не определено. Вы можете обращаться к элементам x2[0] помощью указателя источника, но результат (который был бы указателем на x2[0]) позволил бы вам получить доступ к x2 [1], который вы не можете получить через источник.

struct X { int a[10]; } x3, x4[2]; // assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK

Хорошо. Опять же, вы не можете получить доступ через указатель на x3.a любой байт, к x3.a вы не можете получить доступ уже.

auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); 

Это (должно быть) не определено. Вы могли бы достичь x4[1] из результата, потому что x4[0].a является взаимно x4[0] с x4[0], поэтому указатель на первый может быть reinterpret_cast чтобы получить указатель на последний, который затем может использоваться для арифметики указателя. См. Https://wg21.link/LWG2859.

struct Y { int a[10]; double y; } x5;
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); 

И это снова не определено, потому что вы могли бы достигнуть x5.y из полученного указателя (путем reinterpret_cast к Y*), но указатель источника не может быть использован для доступа к нему.

Ответ 2

Замечание: любой компилятор без шизофреника, вероятно, с радостью согласится с этим, так как он согласился бы с приложением C-стиля или с повторной интерпретацией, поэтому просто попробуйте и посмотрите, это не вариант.

Но ИМХО, ответ на ваш вопрос - нет. Подчеркнутый сразу-охватывающий объект массива, если Y является элементом массива, находится в параграфе "Замечание", а не в "Требуется". Это означает, что при соблюдении раздела, требующего соблюдения, замечания также применяются. Поскольку массив и его тип элемента не являются похожими типами, требование не выполняется, и std::launder нельзя использовать.

Далее следует более общая (философская) интерпретация. Во время K & R C (в 70-е годы) C предполагалось заменить язык ассемблера. По этой причине это правило было: компилятор должен подчиняться программисту, если исходный код может быть переведен. Таким образом, строгого правила псевдонимов и указателя не было больше, чем адрес с дополнительными правилами арифметики. Это сильно изменилось в C99 и C++ 03 (не говоря о C++ 11 +). Программисты теперь должны использовать C++ как язык высокого уровня. Это означает, что указатель - это просто объект, который позволяет получить доступ к другому объекту данного типа, а массив и его тип элемента - совершенно разные типы. Адреса памяти теперь немного больше, чем детали реализации. Поэтому попытка конвертировать указатель на массив в указатель на его первый элемент затем противоречит философии языка и может укусить программиста в более поздней версии компилятора. Конечно, реальный компилятор по-прежнему принимает его по соображениям совместимости, но мы не должны даже пытаться использовать его в современных программах.