В вашем коде вам нужно std::launder
, чтобы заставить reinterpret_cast
делать то, что вы хотите.Это отдельная проблема от повторного использования памяти.Согласно стандарту ([expr.reinterpret] .cast] 7), ваше выражение
reinterpret_cast<One*>(&storage)
эквивалентно:
static_cast<One*>(static_cast<void*>(&storage))
Однако внешний static_cast
не удаетсяв создании указателя на вновь созданный объект One
, потому что согласно [expr.static.cast] / 13,
, если исходное значение указателя указывает на объект a и существует объект b типа T
(игнорирующий квалификацию cv), который может быть преобразован в указатель (6.9.2) с a , результатом является указатель на B * * тысяча двадцать два.В противном случае значение указателя не изменяется при преобразовании.
То есть результирующий указатель по-прежнему указывает на объект storage
, а не на объект One
, вложенный в него, и использует его какуказатель на объект One
нарушил бы строгое правило наложения имен.Вы должны использовать std::launder
, чтобы результирующий указатель указывал на объект One
.Или, как указано в комментариях, вы можете просто использовать указатель, возвращаемый путем непосредственного размещения нового, а не тот, который получен из reinterpret_cast
.
Если, как предлагается в комментариях, вместо этого вы использовали объединениеиз aligned_storage
,
union {
One one;
Two two;
};
вы бы обошли проблему взаимозаменяемости указателей, поэтому std::launder
не понадобится из-за невозможности взаимозаменяемости указателей.Тем не менее, существует проблема повторного использования памяти.В этом конкретном случае std::launder
не требуется из-за повторного использования, потому что ваши классы One
и Two
не содержат каких-либо нестатических членов данных const
-качественного или ссылочного типа ([basic.life)] / 8).
Наконец, возник вопрос, почему реализация std::optional
в libstdc ++ не использует std::launder
, хотя std::optional
может содержать классы, содержащие нестатические члены-данные const
-квалифицированный или ссылочный тип.Как отмечено в комментариях, libstdc ++ является частью реализации и может просто пропустить std::launder
, когда разработчики знают, что GCC все равно будет правильно компилировать код без него.Обсуждение, которое привело к введению std::launder
(см. CWG 1776 и связанной темы, N4303 , P0137 ), по-видимому, указывает на то, что вПо мнению людей, которые понимают стандарт намного лучше меня, std::launder
действительно требуется для того, чтобы основанная на объединении реализация std::optional
была четко определена при наличии членов const
-квалифицированного или ссылочного типа.Однако я не уверен, что стандартный текст достаточно ясен, чтобы сделать это очевидным, и, возможно, стоит обсудить, как его можно уточнить.