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);
      }
    }

0 ответов

Другие вопросы по тегам