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

Что такое `int foo:: * bar:: *`?

Прохладная вещь с С++ заключается в том, что она позволяет создавать переменные типов-указателей. Наиболее распространенным случаем использования является получение указателя на метод:

struct foo
{
    int x() { return 5; }
};

int (foo::*ptr)() = &foo::x;
foo myFoo;
cout << (myFoo.*ptr)() << '\n'; // prints "5"

Однако, возившись, я понял, что они могут точно также указывать на переменные-члены:

struct foo
{
    int y;
};

int foo::*ptr = &foo::y;
foo myFoo;
myFoo.*ptr = 5;
cout << myFoo.y << '\n'; // prints "5"

Это довольно рад. Это привело меня к дальнейшему эксперименту: что, если вы могли бы получить указатель на под-член структуры?

struct foo
{
    int y;
};

struct bar
{
    foo aFoo;
};

int bar::*foo::*ptr;

Этот фактически компилируется.

Однако я понятия не имею, как назначить ему что-нибудь полезное. Ни одна из следующих работ:

int bar::*foo::*ptr = &bar::foo::y; // no member named "foo" in "bar"
int bar::*foo::*ptr = &bar::aFoo::y; // no member named "aFoo" in "bar" (??)
int bar::*foo::*ptr = &foo::y; // can't init 'int bar::*foo::*' with 'int foo::*'

Кроме того, в соответствии с ошибкой, которую это порождает, кажется, что этот тип не совсем то, что я имею в виду:

int bar::*foo::*ptr = nullptr;
bar myBar;
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible
                // with object type ‘bar’

Похоже, что это понятие уклоняется от меня. Очевидно, я не могу исключить, что он просто анализируется совершенно иначе, чем я ожидал.

Может кто-нибудь объяснить мне, что такое int bar::*foo::* на самом деле? Почему gcc говорит мне, что указатель на член bar несовместим с объектом bar? Как я могу использовать int bar::*foo::*, и как я могу построить допустимый?

4b9b3361

Ответ 1

Здесь "действительный" способ инициализации такого чудовища:

struct bar;

struct foo
{
    int y;    
    int bar::* whatever;
};

struct bar
{
    foo aFoo;
};

int bar::* foo::* ptr = &foo::whatever;

Как мы видим, ptr является указателем на элемент foo (foo::*, читающий справа налево), где этот элемент сам является указателем на элемент bar (bar::*), где этот член является int.

Как использовать int bar:: * foo:: *

Вы бы, надеюсь, не захотели! Но если вы находитесь под принуждением, попробуйте это!

struct bar
{
    foo aFoo;

    int really;
};

int bar::* foo::* ptr = &foo::whatever;
foo fleh;
fleh.whatever = &bar::really;
bar blah;
blah.*(fleh.*ptr) = 42;
std::cout << blah.really << std::endl;

Ответ 2

Это будет указатель на элемент данных, который сам по себе является указателем на элемент данных (член int bar).

Не спрашивайте меня, на что это действительно полезно - моя голова немного крутится:)

EDIT: Здесь полный пример этого в действии:

#include <iostream>

struct bar {
    int i;
};

struct foo {
    int bar::* p;
};

int main()
{
    bar b;
    b.i = 42;

    foo f;
    f.p = &bar::i;

    int bar::*foo::*ptr = &foo::p;
    std::cout << (b.*(f.*ptr));
}

Выход, конечно, 42.

Это может стать еще более забавным - здесь некоторые указатели на функции-члены, которые возвращают указатели на функции-члены:

#include <iostream>

struct bar {
    int f_bar(int i) { return i; };
};

struct foo {
    int(bar::*f_foo())(int)
    {
        return &bar::f_bar;
    }
};

int main()
{
    int(bar::*((foo::*ptr)()))(int) = &foo::f_foo;

    bar b;
    foo f;

    std::cout << (b.*((f.*ptr)()))(42);
}

Ответ 3

Проведите анализ объявления int bar::*foo::*ptr;.

§8.3.3 [dcl.mptr]/p1:

В объявлении T D, где D имеет вид

nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1

а спецификатор вложенных имен обозначает класс, а тип идентификатор в объявлении T D1 "производный-declarator-type-list T", тогда тип идентификатора of D является "производным-declarator-type-list cv-qualifier-seq pointer член класса вложенного имени-спецификатора типа T".

  • Шаг 1: Это объявление указанной формы, где T= int, nested-name-specifier = bar:: и D1 = foo::* ptr. Сначала рассмотрим объявление T D1 или int foo::* ptr.

  • Шаг 2: Мы снова применяем то же правило. int foo::* ptr является объявлением вышеуказанной формы, где T= int, nested-name-specifier = foo:: и D1= ptr. Очевидно, что тип идентификатора в int ptr равен "int", поэтому тип идентификатора ptr в объявлении int foo::* ptr является "указателем на член класса foo типа int".

  • Шаг 3. Вернемся к первоначальной декларации; тип идентификатора в T D1 (int foo::* ptr) является "указателем на член класса foo типа int" на шаг 2, поэтому тип-тип-декларатор-type является "указателем на член класса foo типа". Подстановка говорит нам, что это объявление объявляет ptr как "указатель на член класса foo указателя типа на член класса bar типа int".

Надеюсь, вам никогда не понадобится использовать такое чудовище.

Ответ 4

В случае, если кто-то задается вопросом, вы не можете создать указатель на элемент, который глубоко вложен в несколько слоев. Причиной этого является то, что все указатели между собой на самом деле намного сложнее, чем то, что они смотрят на первый взгляд; они не просто содержат определенное смещение для этого конкретного элемента.

Использование простого смещения не работает из-за виртуального наследования и подобных; в основном может случиться так, что даже в пределах одного типа смещения определенного поля различаются между экземплярами, и, таким образом, во время выполнения необходимо выполнить разрешение указателя на член. В основном это связано с тем, что стандарт не указывает, как может работать внутренняя компоновка для не-POD-типов, поэтому нет способа заставить ее работать статически.

Если это так, выполнение двууровневого глубокого разрешения не может быть выполнено с помощью обычного указателя на элемент, но ему потребуется компилятор для создания указателя таким образом, чтобы он содержал двойную информацию об однострочном указателе, на член.

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

Ответ 5

Во-первых, чтобы помочь "читаемости" вы можете использовать скобки (компиляция будет работать):

struct bar;

struct foo
{
    int y;
    int (bar:: *whatever); // whatever is a pointer upon an int member of bar.
};

struct bar
{
    foo aFoo;
};

// ptr is a pointer upon a member of foo which points upon an int member of bar.
int (bar:: *(foo:: *ptr)) = &foo::whatever;

Обратите внимание, что

int (bar:: * whatever)

эквивалентно

int (* whatever)

с ограничением о членстве в баре.

Что касается

int (bar:: * (foo:: * ptr))

это эквивалентно

int (* (* ptr))

с двумя ограничениями на членство в foo и bar.

Они просто указатели. Они не проверяют, действительно ли в баре или foo есть совместимый элемент, потому что это предотвратит использование прямого объявления класса bar и класса bar, не проверяет, ссылаются ли другие классы на его членов с помощью указателей. Кроме того, вам также может понадобиться ссылаться на непрозрачный класс (т.е. С классом класса, определенным в отдельном блоке).

Как насчет полезности? возможно, для С++-рефлексии как способ установить/получить значение члена класса через оболочку класса?

template< typename Class, typename Type >
struct ClassMember
{
    using MemberPointer = Type (Class:: *);
    MemberPointer member;
    ClassMember(MemberPointer member) : member(member) {}
    void operator()(Class & object, Type value) { object.*member = value; }
    Type operator()(Class & object)  { return object.*member; }
};

template< typename Class, typename Type > ClassMember< Class, Type > MakeClassMember(Type(Class:: *member))
{
    return ClassMember< Class, Type >(member);
}

struct Rectangle
{
    double width;
    double height;

    Rectangle(double width = 0., double height = 0.) : width(width), height(height) {}
};

int main(int argc, char const *argv[])
{
    auto r = Rectangle(2., 1.);

    auto w = MakeClassMember(&Rectangle::width);
    auto h = MakeClassMember(&Rectangle::height);

    w(r, 3.);
    h(r, 2.);

    printf("Rectangle(%f, %f)\n", w(r), h(r));

    return 0;
}

Конечно, этот пример не показывает конкретного использования указателя двойного элемента, потому что я не вижу простого способа проиллюстрировать его здесь или повод для этого концептуально.