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