Запрещен ли указатель на «внутреннюю структуру»? - PullRequest
17 голосов
/ 18 декабря 2009

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

это законно?

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. Для каждого указанного класса мы сохраняем массив «Property», включая Ptr-to-member Используется так:

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

Ответы [ 3 ]

19 голосов
/ 18 декабря 2009

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

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

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

// 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;
17 голосов
/ 18 декабря 2009

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

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

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

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

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

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

1 голос
/ 08 ноября 2016

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

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

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

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

Итак, без лишних слов, вот код (протестирован на 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] Хорошо, момент предостережения: это может быть не так для всех архитектур / компиляторов, поэтому переносчики, пишущие код, сейчас отводят взгляд!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...