c++: почему ограничение на offsetof() для нестандартных объектов компоновки или как восстановить родителя из указателя на член
У меня есть ситуация, когда у меня есть указатель на элемент данных, и из него я хочу восстановить адрес содержащего объекта (реализуя класс свойств для объекта).
У меня есть полнофункциональная реализация https://onlinegdb.com/ByQJurll_
- преобразовать PTR-to-member в смещение
- вычесть обратно, чтобы получить родителя
- будьте осторожны с static_cast<> надлежащим образом для обработки множественного наследования
Но, насколько я могу судить, этот код не является переносимым, хорошо определенным C++, когда применяется к объекту-контейнеру, который использует общедоступный/частный.
Это потому, что, насколько я понимаю, трюк offsetof() гарантированно работает только для объектов standard_layout<>, и если вы используете public/private, он больше не считается стандартным_layout.
Я не могу понять мотивацию этого ограничения (тем более, что то, что у меня есть, уже работает на многих компиляторах - все протестировано). Есть ли какой-нибудь переносимый, законный способ в С++ восстановить содержащий объект с указанием указателя на член и адрес этого члена?
(код находится в этой ссылке выше, но краткое описание здесь)
template < typename OUTER_OBJECT, typename DATA_MEMBER_TYPE >
inline size_t
ConvertPointerToDataMemberToOffset (DATA_MEMBER_TYPE
(OUTER_OBJECT::*dataMember))
{
instance
return reinterpret_cast <
char *>(&(((OUTER_OBJECT *) 0)->*dataMember)) - reinterpret_cast <
char *>(0);
}
template < typename APPARENT_MEMBER_TYPE, typename OUTER_OBJECT,
typename AGGREGATED_OBJECT_TYPE >
inline OUTER_OBJECT * GetObjectOwningField (APPARENT_MEMBER_TYPE *
aggregatedMember,
AGGREGATED_OBJECT_TYPE
(OUTER_OBJECT::
*aggregatedPtrToMember))
{
std::byte * adjustedAggregatedMember =
reinterpret_cast < std::byte * >(static_cast <
AGGREGATED_OBJECT_TYPE *
>(aggregatedMember));
ptrdiff_t adjustment =
static_cast < ptrdiff_t >
(ConvertPointerToDataMemberToOffset (aggregatedPtrToMember));
return reinterpret_cast <
OUTER_OBJECT * >(adjustedAggregatedMember - adjustment);
}
void DoTest ();
struct X1
{
int a;
int b;
};
struct X2
{
public:
int a;
private:
int b;
private:
friend void DoTest ();
};
void
DoTest ()
{
{
X1 t;
static_assert (is_standard_layout_v < X1 >);
void *aAddr = &t.a;
void *bAddr = &t.b;
assert (GetObjectOwningField (aAddr, &X1::a) == &t);
assert (GetObjectOwningField (bAddr, &X1::b) == &t);
}
{
// Check and warning but since X2 is not standard layout, this isn't guaranteed to work
X2 t;
static_assert (not is_standard_layout_v < X2 >);
void *aAddr = &t.a;
void *bAddr = &t.b;
assert (GetObjectOwningField (aAddr, &X2::a) == &t);
assert (GetObjectOwningField (bAddr, &X2::b) == &t);
}
}