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


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

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 несовместимо с

5 68

5 ответов:

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

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;

это будет указатель на элемент данных, который сам является указателем на элемент данных (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);
}

давайте разберем объявление 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 is "derived-declarator-type-listT", затем тип идентификатора из D это "derived-declarator-type-list cv-qualifier-seq указатель члену класса nested-name-specifier типа 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, так что derived-declarator-type-list - это "указатель на член класса foo типа". Подстановка говорит нам, что это объявление объявляет ptr будет "указатель на член класса foo типа указатель на член класса bar типа int".

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

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

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

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

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

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

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.

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

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

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