A cool thing with C++ is that it lets you create variables of pointer-to-member types. The most common use case seems to be to get a pointer to a method:
c++有一个很酷的功能,它允许您创建指针到成员类型的变量。最常见的用例似乎是获得指向一个方法的指针:
struct foo
{
int x() { return 5; }
};
int (foo::*ptr)() = &foo::x;
foo myFoo;
cout << (myFoo.*ptr)() << '\n'; // prints "5"
However, messing around, I realized that they can just as well point to member variables:
然而,我发现他们也可以指向成员变量:
struct foo
{
int y;
};
int foo::*ptr = &foo::y;
foo myFoo;
myFoo.*ptr = 5;
cout << myFoo.y << '\n'; // prints "5"
This is pretty rad. It led me to a further experiment: what if you could get a pointer to a sub-member of a structure?
这真是太棒了,它让我做了一个进一步的实验:如果你能得到一个指向结构的子元素的指针呢?
struct foo
{
int y;
};
struct bar
{
foo aFoo;
};
int bar::*foo::*ptr;
This actually compiles.
这实际上编译。
However, I have no idea how to assign it anything useful. None of the following works:
然而,我不知道如何分配任何有用的东西。以下作品均无:
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::*'
Furthermore, according to the error that this generates, it appears that this type is not exactly what I have in mind:
此外,根据它所产生的错误,看来这种类型并不是我想要的:
int bar::*foo::*ptr = nullptr;
bar myBar;
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible
// with object type ‘bar’
It appears that this concept evades me. Obviously, I can't rule out that it simply gets parsed in a way entirely different from what I would expect.
这一概念似乎使我避而远之。显然,我不能排除它被解析的方式与我预期的完全不同。
Would anyone please explain me what an int bar::*foo::*
actually is? Why does gcc tell me that a pointer to a member of bar
is incompatible with a bar
object? How would I use an int bar::*foo::*
, and how would I construct a valid one?
谁能给我解释一下int bar::*foo::*实际上是什么?为什么gcc告诉我bar成员的指针与bar对象不兼容?我如何使用int bar::*foo: *,如何构造一个有效的?
5 个解决方案
#1
53
Here's a "valid" way of initializing such a monstrosity:
这里有一种“有效”的方式来初始化这种怪物:
struct bar;
struct foo
{
int y;
int bar::* whatever;
};
struct bar
{
foo aFoo;
};
int bar::* foo::* ptr = &foo::whatever;
As we can see, ptr
is a pointer to a member of foo
(foo::*
, reading right to left), where that member is itself a pointer to a member of bar
(bar::*
), where that member is an int.
正如我们所看到的,ptr是一个指向foo (foo::*,读取右向左)的成员的指针,该成员本身就是指向bar (bar: *)成员的指针,该成员是一个int类型的成员。
How would I use an int bar::* foo::*
如何使用int bar::* foo: *
You wouldn't, hopefully! But if you are under duress, try this!
你不会,希望!但如果你是*的,试试这个!
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
19
That would be a pointer to a data member that is itself a pointer to a data member (an int
member of bar
).
它是指向数据成员的指针,数据成员本身就是指向数据成员的指针(bar的int成员)。
Don't ask me what it is actually useful for - my head is spinning a little :)
不要问我它到底有什么用——我的头在打转:
EDIT: Here's a full example of it in action:
编辑:这里有一个完整的例子:
#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));
}
Output is, of course, 42.
当然,输出是42。
It can get even more fun - here's some pointers to member functions that return pointers to member functions:
它可以得到更多的乐趣-这里有一些指向成员函数的指针,返回指向成员函数的指针:
#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
13
Let's parse the declaration int bar::*foo::*ptr;
.
让我们解析声明int bar::*foo: *ptr;
§8.3.3 [dcl.mptr]/p1:
§8.3.3[dcl.mptr]/ p1:
In a declaration
T D
whereD
has the form在声明td中,D有表格
nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1
and the nested-name-specifier denotes a class, and the type of the identifier in the declaration
T D1
is “derived-declarator-type-listT
”, then the type of the identifier ofD
is “derived-declarator-type-list cv-qualifier-seq pointer to member of class nested-name-specifier of typeT
”.而neste -name-specifier表示一个类,声明T D1中的标识符类型为“derived-declarator-type-list T”,则D的标识符类型为“derived-declarator-type-list cv-qualifier-seq指针指向类neste -name-specifier类型T的成员”。
-
Step 1: This is a declaration of the above form where
T
= int, nested-name-specifier =bar::
, andD1 = foo::* ptr
. We first look at the declarationT D1
, orint foo::* ptr
.步骤1:这是上述表单的声明,其中T = int, nested-name说明符= bar::, D1 = foo: * ptr。我们首先看声明td1,或int foo: * ptr。
-
Step 2: We apply the same rule again.
int foo::* ptr
is a declaration of the above form whereT
= int, nested-name-specifier =foo::
, andD1
=ptr
. Obviously the type of the identifier inint ptr
is "int
", so the type of the identifierptr
in the declarationint foo::* ptr
is "pointer to member of classfoo
of typeint
".第二步:我们再次应用相同的规则。* ptr是上述形式的声明,其中T = int, nested-name-specifier = foo::, D1 = ptr。很明显,在int ptr中标识符的类型是“int”,因此在声明int foo中,标识符ptr的类型::* ptr是“指向类型int的类foo成员的指针”。
-
Step 3. We go back to the original declaration; the type of the identifier in
T D1
(int foo::* ptr
) is "pointer to member of classfoo
of typeint
" per step 2, so the derived-declarator-type-list is "pointer to member of classfoo
of type". Substitution tells us that this declaration declaresptr
to be "pointer to member of classfoo
of type pointer to member of classbar
of typeint
".步骤3。我们回到最初的宣言;td1 (int foo: * ptr)中的标识符的类型是“指针指向type类型为int的foo类成员”,因此派生-declarator-type-list是“指向type类型为foo的类成员的指针”。替换告诉我们,这个声明声明将ptr声明为“指向类foo成员的指针指向类型int的类bar成员的指针”。
Hopefully you will never need to use such a monstrosity.
希望你永远不需要使用这样的怪物。
#4
3
In case anyone is wondering, you can't create a pointer-to-member which nests multiple layers deep. The reason for this is that all pointer-to-members are actually way more complicated that what they look at a first glance; they are not simply containing a particular offset for that specific member.
如果有人想知道,你不可能创建一个洞穴到成员的窝在多层的深处。这样做的原因是所有的指针对成员实际上比他们第一眼看到的要复杂得多;它们不只是包含特定成员的特定偏移量。
Using a simple offset does not work due to virtual inheritance and the likes; basically it can happen that, even within a single type, the offsets of a particular field vary between instances, and thus pointer-to-member resolution needs to be done at runtime. Mostly this is due to the fact that the standard does not specify how the internal layout for non-POD types might work, so there's no way to make it work statically.
由于虚拟继承和类似的原因,使用简单的偏移量不起作用;基本上,即使在单个类型中,特定字段的偏移量也会在实例之间发生变化,因此需要在运行时进行指针对成员的解析。这主要是由于标准没有指定非pod类型的内部布局如何工作,因此无法使其静态工作。
If this is the case, doing two-level deep resolution cannot be done with a normal pointer-to-member, but would need the compiler to generate a pointer such that it contains double the information of a one-deep pointer-to-member.
如果是这种情况,那么做两级深度解析不能使用普通的指针到成员,但需要编译器生成一个指针,使其包含一种深度指针到成员的信息的两倍。
I imagine that since pointers-to-member are not that common, there is no need to actually create a syntax to allow for setting multiple-layer deep members, when you can still use multiple pointers to achieve the same result.
我认为,由于对成员的指针不是那么常见,所以当您仍然可以使用多个指针来实现相同的结果时,实际上不需要创建允许设置多层深层成员的语法。
#5
1
First, to help the "readability" you could use parenthesis (compiling will work) :
首先,为了帮助“可读性”,可以使用括号(编译就可以了):
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;
Note that
请注意,
int (bar:: *whatever)
int(酒吧::*不管)
is equivalent to
相当于
int (*whatever)
int(*)
with a constraint about membership of bar.
限制了bar的成员资格。
As for
至于
int (bar:: *(foo:: *ptr))
int(酒吧::*(foo::* ptr))
, it is equivalent to
,它等于
int (*(*ptr))
int(*(* ptr))
with two constraints about memberships of foo and bar.
有两个限制符。
They are just pointers. They do not check if bar or foo really have a compatible member because that would prevent from using a forward declaration of class bar and class bar does not check if other classes are referring to its members through pointers. Besides, you may also need to refer an opaque class (that is, having a class bar defined in a separate unit).
他们只是指针。他们不检查bar或foo是否真的有一个兼容的成员,因为这将防止使用类bar的forward声明,而类bar不会检查其他类是否通过指针指向它的成员。此外,您可能还需要引用一个不透明的类(即,在一个单独的单元中定义一个类条)。
What about the usefulness ? maybe for C++ reflection as a way to set/get the value of a member of a class through a class wrapper ?
有用性是什么?也许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;
}
Sure, this example does not show a particular usage of a double member pointer because I do not see a simple way to illustrate it here or a good reason to do so conceptually speaking.
当然,这个例子并没有说明双成员指针的具体用法,因为我在这里没有看到一个简单的方法来说明它,也没有一个从概念上来说这样做的好理由。
#1
53
Here's a "valid" way of initializing such a monstrosity:
这里有一种“有效”的方式来初始化这种怪物:
struct bar;
struct foo
{
int y;
int bar::* whatever;
};
struct bar
{
foo aFoo;
};
int bar::* foo::* ptr = &foo::whatever;
As we can see, ptr
is a pointer to a member of foo
(foo::*
, reading right to left), where that member is itself a pointer to a member of bar
(bar::*
), where that member is an int.
正如我们所看到的,ptr是一个指向foo (foo::*,读取右向左)的成员的指针,该成员本身就是指向bar (bar: *)成员的指针,该成员是一个int类型的成员。
How would I use an int bar::* foo::*
如何使用int bar::* foo: *
You wouldn't, hopefully! But if you are under duress, try this!
你不会,希望!但如果你是*的,试试这个!
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
19
That would be a pointer to a data member that is itself a pointer to a data member (an int
member of bar
).
它是指向数据成员的指针,数据成员本身就是指向数据成员的指针(bar的int成员)。
Don't ask me what it is actually useful for - my head is spinning a little :)
不要问我它到底有什么用——我的头在打转:
EDIT: Here's a full example of it in action:
编辑:这里有一个完整的例子:
#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));
}
Output is, of course, 42.
当然,输出是42。
It can get even more fun - here's some pointers to member functions that return pointers to member functions:
它可以得到更多的乐趣-这里有一些指向成员函数的指针,返回指向成员函数的指针:
#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
13
Let's parse the declaration int bar::*foo::*ptr;
.
让我们解析声明int bar::*foo: *ptr;
§8.3.3 [dcl.mptr]/p1:
§8.3.3[dcl.mptr]/ p1:
In a declaration
T D
whereD
has the form在声明td中,D有表格
nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1
and the nested-name-specifier denotes a class, and the type of the identifier in the declaration
T D1
is “derived-declarator-type-listT
”, then the type of the identifier ofD
is “derived-declarator-type-list cv-qualifier-seq pointer to member of class nested-name-specifier of typeT
”.而neste -name-specifier表示一个类,声明T D1中的标识符类型为“derived-declarator-type-list T”,则D的标识符类型为“derived-declarator-type-list cv-qualifier-seq指针指向类neste -name-specifier类型T的成员”。
-
Step 1: This is a declaration of the above form where
T
= int, nested-name-specifier =bar::
, andD1 = foo::* ptr
. We first look at the declarationT D1
, orint foo::* ptr
.步骤1:这是上述表单的声明,其中T = int, nested-name说明符= bar::, D1 = foo: * ptr。我们首先看声明td1,或int foo: * ptr。
-
Step 2: We apply the same rule again.
int foo::* ptr
is a declaration of the above form whereT
= int, nested-name-specifier =foo::
, andD1
=ptr
. Obviously the type of the identifier inint ptr
is "int
", so the type of the identifierptr
in the declarationint foo::* ptr
is "pointer to member of classfoo
of typeint
".第二步:我们再次应用相同的规则。* ptr是上述形式的声明,其中T = int, nested-name-specifier = foo::, D1 = ptr。很明显,在int ptr中标识符的类型是“int”,因此在声明int foo中,标识符ptr的类型::* ptr是“指向类型int的类foo成员的指针”。
-
Step 3. We go back to the original declaration; the type of the identifier in
T D1
(int foo::* ptr
) is "pointer to member of classfoo
of typeint
" per step 2, so the derived-declarator-type-list is "pointer to member of classfoo
of type". Substitution tells us that this declaration declaresptr
to be "pointer to member of classfoo
of type pointer to member of classbar
of typeint
".步骤3。我们回到最初的宣言;td1 (int foo: * ptr)中的标识符的类型是“指针指向type类型为int的foo类成员”,因此派生-declarator-type-list是“指向type类型为foo的类成员的指针”。替换告诉我们,这个声明声明将ptr声明为“指向类foo成员的指针指向类型int的类bar成员的指针”。
Hopefully you will never need to use such a monstrosity.
希望你永远不需要使用这样的怪物。
#4
3
In case anyone is wondering, you can't create a pointer-to-member which nests multiple layers deep. The reason for this is that all pointer-to-members are actually way more complicated that what they look at a first glance; they are not simply containing a particular offset for that specific member.
如果有人想知道,你不可能创建一个洞穴到成员的窝在多层的深处。这样做的原因是所有的指针对成员实际上比他们第一眼看到的要复杂得多;它们不只是包含特定成员的特定偏移量。
Using a simple offset does not work due to virtual inheritance and the likes; basically it can happen that, even within a single type, the offsets of a particular field vary between instances, and thus pointer-to-member resolution needs to be done at runtime. Mostly this is due to the fact that the standard does not specify how the internal layout for non-POD types might work, so there's no way to make it work statically.
由于虚拟继承和类似的原因,使用简单的偏移量不起作用;基本上,即使在单个类型中,特定字段的偏移量也会在实例之间发生变化,因此需要在运行时进行指针对成员的解析。这主要是由于标准没有指定非pod类型的内部布局如何工作,因此无法使其静态工作。
If this is the case, doing two-level deep resolution cannot be done with a normal pointer-to-member, but would need the compiler to generate a pointer such that it contains double the information of a one-deep pointer-to-member.
如果是这种情况,那么做两级深度解析不能使用普通的指针到成员,但需要编译器生成一个指针,使其包含一种深度指针到成员的信息的两倍。
I imagine that since pointers-to-member are not that common, there is no need to actually create a syntax to allow for setting multiple-layer deep members, when you can still use multiple pointers to achieve the same result.
我认为,由于对成员的指针不是那么常见,所以当您仍然可以使用多个指针来实现相同的结果时,实际上不需要创建允许设置多层深层成员的语法。
#5
1
First, to help the "readability" you could use parenthesis (compiling will work) :
首先,为了帮助“可读性”,可以使用括号(编译就可以了):
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;
Note that
请注意,
int (bar:: *whatever)
int(酒吧::*不管)
is equivalent to
相当于
int (*whatever)
int(*)
with a constraint about membership of bar.
限制了bar的成员资格。
As for
至于
int (bar:: *(foo:: *ptr))
int(酒吧::*(foo::* ptr))
, it is equivalent to
,它等于
int (*(*ptr))
int(*(* ptr))
with two constraints about memberships of foo and bar.
有两个限制符。
They are just pointers. They do not check if bar or foo really have a compatible member because that would prevent from using a forward declaration of class bar and class bar does not check if other classes are referring to its members through pointers. Besides, you may also need to refer an opaque class (that is, having a class bar defined in a separate unit).
他们只是指针。他们不检查bar或foo是否真的有一个兼容的成员,因为这将防止使用类bar的forward声明,而类bar不会检查其他类是否通过指针指向它的成员。此外,您可能还需要引用一个不透明的类(即,在一个单独的单元中定义一个类条)。
What about the usefulness ? maybe for C++ reflection as a way to set/get the value of a member of a class through a class wrapper ?
有用性是什么?也许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;
}
Sure, this example does not show a particular usage of a double member pointer because I do not see a simple way to illustrate it here or a good reason to do so conceptually speaking.
当然,这个例子并没有说明双成员指针的具体用法,因为我在这里没有看到一个简单的方法来说明它,也没有一个从概念上来说这样做的好理由。