Можно ли использовать 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++ как язык высокого уровня. Это означает, что указатель - это просто объект, который позволяет получить доступ к другому объекту данного типа, а массив и его тип элемента - совершенно разные типы. Адреса памяти теперь немного больше, чем детали реализации. Поэтому попытка конвертировать указатель на массив в указатель на его первый элемент затем противоречит философии языка и может укусить программиста в более поздней версии компилятора. Конечно, реальный компилятор по-прежнему принимает его по соображениям совместимости, но мы не должны даже пытаться использовать его в современных программах.