Подтвердить что ты не робот

Запрещен ли указатель на "внутреннюю структуру"?

У меня есть вложенная структура, и я хотел бы иметь указатель-член для одного из вложенных элементов:

является ли он законным?

struct InnerStruct
{
    bool c;
};
struct MyStruct {
    bool t;
    bool b;
    InnerStruct inner;
}; 

MyStruct mystruct;
//...
bool MyStruct::* toto = &MyStruct::b;

нормально, но:

bool MyStruct::* toto = &MyStruct::inner.c;

нет. любая идея?

спасибо

Вот некоторые подробности Да, это MyStruct:: b, а не mystruct:: b; Код из пользовательской системы RTTI/Property. Для каждого указанного класса мы сохраняем массив "Свойство", включая Ptr-to-member Он используется следующим образом:

//somewhere else in code...
( myBaseClassWithCustomRTTIPointer)->* toto = true;
4b9b3361

Ответ 1

Да, это запрещено. Вы не первый, кто придумал эту совершенно логичную идею. По-моему, это один из очевидных "ошибок" / "упущений" в спецификации указателей на членов на С++, но, по-видимому, комитет не заинтересован в дальнейшем развитии спецификации указателей на членов ( случай с большинством "низкоуровневых" языковых функций).

Обратите внимание, что все необходимое для реализации функции уже существует, на языке. Указатель на элемент a-data-of-a-member ничем не отличается от указателя на непосредственный элемент данных. Единственное, что отсутствует, это синтаксис для инициализации такого указателя. Однако комитет, по-видимому, не заинтересован в представлении такого синтаксиса.

С чистой формальной логической точки зрения это должно быть разрешено в С++

struct Inner {
  int i;
  int j[10];
};

struct Outer {
  int i;
  int j[10];
  Inner inner;
};

Outer o;
int Outer::*p;

p = &Outer::i; // OK
o.*p = 0; // sets `o.i` to 0

p = &Outer::inner.i; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.i` to 0

p = &Outer::j[0]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[0]` to 0
// This could have been used to implement something akin to "array type decay" 
// for member pointers

p = &Outer::j[3]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[3]` to 0

p = &Outer::inner.j[5]; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.j[5]` to 0

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

В языке C эта функция эмулируется явными смещениями, полученными с помощью стандартного макроса offsetof. А в C я могу получить offsetof(Outer, inner.i) и offsetof(Outer, j[2]). К сожалению, эта возможность не отражается в С++ указателях на данные.

Ответ 2

InnerStruct, о котором вы заботитесь, содержится в экземпляре MyStruct, но это не влияет на то, как вы получаете указатель на член InnerStruct.

bool InnerStruct::* toto2 = &InnerStruct::c;

Изменить: перечитывая свой вопрос, я предполагаю, что вы хотите определить указатель на член внешней структуры и указать его прямо на члена внутренней структуры. Это просто не разрешено. Чтобы получить член внутренней структуры, который содержится во внешней структуре, вам нужно будет создать указатель на внутреннюю структуру itelft, а затем на ее член. Чтобы использовать его, вы бы разделили оба указателя на члены:

// Pointer to inner member of MyStruct:
InnerStruct MyStruct::* toto = &MyStruct::inner;

// Pointer to c member of InnerStruct:
bool InnerStruct::* toto2 = &InnerStruct::c;

// Dereference both to get to the actual bool:
bool x = mystruct.*toto.*toto2;

Ответ 3

Как указано в ответе AnT, это, по-видимому, является упущением стандарта и отсутствием правильного синтаксиса для выражения того, что вы хотите сделать. На днях мой коллега столкнулся с этой проблемой и назвал ваш вопрос и его ответ доказательством того, что это невозможно. Ну, мне нравится вызов, и да, это можно сделать... но это не очень.

Во-первых, важно понимать, что указатель на элемент в основном является смещением от указателя на struct [1]. У языка есть оператор смещения, который подозрительно подобен этому, и достаточно интересно дает нам выразительность, необходимую для выполнения того, что мы хотим.

Проблема, с которой мы немедленно сталкиваемся, заключается в том, что С++ запрещает указывать указатели элементов. Ну, почти... у нас есть союз, заброшенный нашими рукавами. Как я уже сказал, это некрасиво!

В конце концов, нам также нужно знать правильный тип указателя, который нужно выполнить.

Итак, без лишнего шума, вот код (проверенный на gcc и clang):

template <typename C, typename T, /*auto*/size_t P>
union MemberPointerImpl final {
  template <typename U> struct Helper
    { using Type = U C::*; };
  template <typename U> struct Helper<U&>
    { using Type = U C::*; };

  using MemberPointer = typename Helper<T>::Type;

  MemberPointer o;
  size_t i = P; // we can't do "auto i" - argh!
  static_assert(sizeof(i) == sizeof(o));
};

#define MEMBER_POINTER(C, M) \
  ((MemberPointerImpl<__typeof__(C),          \
      decltype(((__typeof__(C)*)nullptr)->M), \
      __builtin_offsetof(__typeof__(C), M)    \
  >{ }).o)

Сначала рассмотрим макрос MEMBER_POINTER. Он принимает два аргумента. Первая, C - это структура, которая будет базой для указателя-члена. Обертка в __typeof__ не является строго необходимой, но позволяет передавать либо тип, либо переменную. Второй аргумент M предоставляет выражение для члена, которому мы хотим указатель.

Макрос MEMBER_POINTER извлекает из этих аргументов две дополнительные части информации и передает их в качестве параметров для объединения шаблона MemberPointerImpl. Первая часть - это тип элемента, на который указывает. Это делается путем построения выражения с использованием нулевого указателя, на котором мы используем decltype. Вторая часть - это смещение от базовой структуры к соответствующему члену.

Внутри MemberPointerImpl нам нужно построить тип MemberPointer, который будет тем, что возвращается макросом. Это делается с помощью вспомогательной структуры, которая удаляет ссылки, которые бесполезно возникают, если наш член является элементом массива, что также позволяет поддерживать это. Он также позволяет gcc и clang дать нам хороший полностью расширенный тип в диагностике, если мы присваиваем возвращаемое значение переменной с несоответствующим типом.

Итак, чтобы использовать MEMBER_POINTER, просто измените свой код на:

bool MyStruct::* toto = &MyStruct::inner.c;

в

bool MyStruct::* toto = MEMBER_POINTER(MyStruct, inner.c);

[1] Хорошо, оставьте момент: это может быть неверно для всех архитектур/компиляторов, поэтому переносимые разработчики кода теперь отвлекают внимание!