Topic: address-of-member is not a replacement for offsetof


Author: Krzysztof =?UTF-8?B?xbtlbGVjaG93c2tp?= <giecrilj@stegny.2a.pl>
Date: Sat, 28 Aug 2010 22:31:20 CST
Raw View
The address-of-member operator [expr.unary.op/3] tries to serve a double
duty:

1. It is a syntactic equivalent of address of function for member functions.
2. It is a semantic equivalent of the offsetof macro.

We shall demonstrate that unifying these tasks in one lexical constructs is
not only logically inconsistent (which is already evident from the
descriptions) but also severely affects the performance of task 2.

Consider the following declaration:
struct B { void f (); int n; };
struct A { struct B b1, b2; };

The following expressions are well-formed:

B ().* &B:: f ();       // == B (). f ();
A (). b1.* &B:: f ();
A ().* &A:: b1.* &B:: f ();

The last expression obviously cannot be abbreviated to

/*!*/ A ().* &A:: b1. f ();

because the hypothetical pointer-to-member &A:: b1. f() has no way of
knowing that it should operate on b1 and not on b2.  So far, so good.

Now replace f with n:

B ().* &B:: n;  // == B (). n;
A (). b1.* &B:: n;
A ().* &A:: b1.* &B:: n;
/*!*/ A ().* &A:: b1. n;

The last expression is ill-formed because the corresponding expression with
f is ill-formed, and it is the only reason; there is no semantic obstacle
why such a pointer-to-member should be impossible to implement.

On the other hand, the offsetof macro that the address-of-member operator is
supposed to replace has no problem with this situation (yes, I know it is
undocumented):

A a;
assert (&a. b1. n == (int *) ((unsigned char *) &a + offsetof (A, b1. n)));

Of course, offsetof is not type-safe and should not be used if possible;
however, it seems that it has no decently corresponding construct in C++.

I suggest that C++ should allow &A:: b1. n for data members (as an
expression of type int A::*).

Thanks for reviewing,
Chris


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: David Krauss <potswa@gmail.com>
Date: Sun, 29 Aug 2010 19:26:21 CST
Raw View
On Aug 28, 11:31 pm, Krzysztof    elechowski <giecr...@stegny.2a.pl>
wrote:
> The address-of-member operator [expr.unary.op/3] tries to serve a double
> duty:
>
> 1. It is a syntactic equivalent of address of function for member functions.
> 2. It is a semantic equivalent of the offsetof macro.
>
> We shall demonstrate that unifying these tasks in one lexical constructs is
> not only logically inconsistent (which is already evident from the
> descriptions) but also severely affects the performance of task 2.

If a semantic equivalent isn't as performant, then is it really
equivalent?

> Consider the following declaration:
> struct B { void f (); int n; };
> struct A { struct B b1, b2; };
>
> The following expressions are well-formed:
>
> B ().* &B:: f ();       // == B (). f ();
> A (). b1.* &B:: f ();
> A ().* &A:: b1.* &B:: f ();

Function-call binds before ptmf, so you're missing parentheses around
each expression...

( B ().* &B:: f )();       // == B (). f ();
( A (). b1.* &B:: f )();
( A ().* &A:: b1.* &B:: f )();

>
> The last expression obviously cannot be abbreviated to
>
> /*!*/ A ().* &A:: b1. f ();
>
> because the hypothetical pointer-to-member &A:: b1. f() has no way of
> knowing that it should operate on b1 and not on b2.  So far, so good.

It knows to operate on b1 because you nominated b1. It doesn't compile
because, without parentheses, it's interpreted as

 A().* (&A::b1 .f()); // member function call on unbound ptmf

this compiles fine:

 ( A().* &A::b1 ).f();

> Now replace f with n:
>
> B ().* &B:: n;  // == B (). n;
> A (). b1.* &B:: n;
> A ().* &A:: b1.* &B:: n;
> /*!*/ A ().* &A:: b1. n;
>
> The last expression is ill-formed because the corresponding expression with
> f is ill-formed, and it is the only reason; there is no semantic obstacle
> why such a pointer-to-member should be impossible to implement.

The situation is analogous:  ( A ().* &A:: b1 ). n;  does what you
want. However, it is somewhat coincidental that the "()" and "."
operators both have higher precedence than ".*".

> On the other hand, the offsetof macro that the address-of-member operator is
> supposed to replace has no problem with this situation (yes, I know it is
> undocumented):

"offsetof" is defined by 18.1/5. (18.1/4 in the FCD.)

> A a;
> assert (&a. b1. n == (int *) ((unsigned char *) &a + offsetof (A, b1. n)));
>
> Of course, offsetof is not type-safe and should not be used if possible;
> however, it seems that it has no decently corresponding construct in C++.

The type semantics of offsetof are well defined: only use it on POD-
types (standard layout classes in C++0x).

The type semantics of your casts, however, are NOT well-defined, quite
the opposite in fact. Please avoid C-style casts and keep
static_cast<> and reinterpret_cast<> distinct.

If you ask me, the difference between offsetof and pointer-to-member
is that offsetof is numeric and pointer-to-member is not, and may be
NULL.

> I suggest that C++ should allow &A:: b1. n for data members (as an
> expression of type int A::*).

I suspect that all this confusion resulted from a relaxed
compiler :v( . Please run your code through
http://www.comeaucomputing.com/tryitout/
as a double check (although it doesn't support C++0x). Also, download
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdf and
search for your favorite C89/C99 features.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Thu, 16 Sep 2010 11:38:42 CST
Raw View
On Aug 31, 4:07 am, Krzysztof =AFelechowski <giecr...@stegny.2a.pl>
wrote:
> David Krauss wrote:
> >> The last expression obviously cannot be abbreviated to
>
> >> /*!*/ A ().* &A:: b1. f ();
>
> >> because the hypothetical pointer-to-member &A:: b1. f() has no way of
> >> knowing that it should operate on b1 and not on b2.  So far, so good.
>
> > It knows to operate on b1 because you nominated b1. It doesn't compile
> > because, without parentheses, it's interpreted as
>
> >  A().* (&A::b1 .f()); // member function call on unbound ptmf
>
> > this compiles fine:
>
> >  ( A().* &A::b1 ).f();
>
> Except that I tried to argue that (&A:: b1. f) as a separate entity is
> impossible.

Correct, but you are comparing here two very different things.
offsetof takes advantage of a mechanism that is only guaranteed
to be true for a selected subset of types while dereferencing
pointer-to-members is a much more general operation. The former
bases on arithmetic on address-constant expressions, but the latter
does not, as David already  pointed out.

> > "offsetof" is defined by 18.1/5. (18.1/4 in the FCD.)
>
> N3126:18.1/3 says "offsetof (_type_, _member-designator_).  Since
> _member-designator_ is undefined, so is offsetof, QED.

This assertion is incorrect. There is no need that C++ itself gives
it a meaning, the meaning follows by reference to the C99
standard, as described in my reply in your different thread. The
/member-designator/ designates a structure member (7.17/3):

"from the beginning of its structure (designated by type).
The type and member designator shall be such that given

static type t;

then the expression &(t.member-designator) evaluates to an
address constant. (If the specified member is a bit-field, the
behavior is undefined.)"

In C++ the type shall be a standard-layout class, and some
further requirements relevant for noexcept are specified.

> >> Of course, offsetof is not type-safe and should not be used if possibl=
e;
> >> however, it seems that it has no decently corresponding construct in C=
++.
>
> > The type semantics of offsetof are well defined: only use it on POD-
> > types (standard layout classes in C++0x).
>
> > The type semantics of your casts, however, are NOT well-defined, quite
> > the opposite in fact. Please avoid C-style casts and keep
> > static_cast<> and reinterpret_cast<> distinct.
>
> That code was extern "C".

It is absolutely fine to use static_cast or reinterpret_cast in extern
"C"
C++ code.

> > as a double check (although it doesn't support C++0x). Also, download
> >http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdfand
> > search for your favorite C89/C99 features.
>
> I do not know about N3092, but N3126 clearly restricts the applicability =
of
> address-of-class-member construct to immediate members.  I was trying to
> understand why this restriction was imposed, and I believe I have identif=
ied
> the culprit.

I don't see how you can read this from the standard (draft). The C++
standard allows for standard layout classes as arguments and there
is no saying that the members must be immediate members
(in C we have no such relation, therefore there is no such
restriction).
E.g. the following example should be well-formed and well-defined in
C++0x:

#include <cstddef>

struct B { int m; };
struct D : B {};

static_assert(offsetof(D, m) == 0, "Wrong position");

Note that "m" is no immediate member of D.

I don't think that you can conclude the restrictions of what pointer-
to-members can realize by inspecting the capabilities of offsetof.
One of the main differences is that pointer-to-members do
not allow for "random access" traversal through the structure, in
contrast to what offsetof allows given it's supported target types.

HTH & Greetings from Bremen,

Daniel Kr=FCgler

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Krzysztof =?UTF-8?B?xbtlbGVjaG93c2tp?= <giecrilj@stegny.2a.pl>
Date: Sun, 19 Sep 2010 18:15:50 CST
Raw View
Daniel Kr   gler wrote:

>> > "offsetof" is defined by 18.1/5. (18.1/4 in the FCD.)
>>
>> N3126:18.1/3 says "offsetof (_type_, _member-designator_).  Since
>> _member-designator_ is undefined, so is offsetof, QED.
>
> This assertion is incorrect. There is no need that C++ itself gives
> it a meaning, the meaning follows by reference to the C99
> standard, as described in my reply in your different thread. The
> /member-designator/ designates a structure member (7.17/3):
>
> "from the beginning of its structure (designated by type).
> The type and member designator shall be such that given
>
> static type t;
>
> then the expression &(t.member-designator) evaluates to an
> address constant. (If the specified member is a bit-field, the
> behavior is undefined.)"
>

Good to know, because glibc documentation [52] actually restricts the
application of offsetof to immediate members only, in the following words:
"that is the offset of the structure member _named_ *member*".

>> > The type semantics of your casts, however, are NOT well-defined, quite
>> > the opposite in fact. Please avoid C-style casts and keep
>> > static_cast<> and reinterpret_cast<> distinct.
>>
>> That code was extern "C".
>
> It is absolutely fine to use static_cast or reinterpret_cast in extern
> "C"
> C++ code.

Ouch! I actually expected that answer.  That code was extern "C", and in C.

>
>> > as a double check (although it doesn't support C++0x). Also, download
>> >http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdfand
>> > search for your favorite C89/C99 features.
>>
>> I do not know about N3092, but N3126 clearly restricts the applicability
>> =
> of
>> address-of-class-member construct to immediate members.  I was trying to
>> understand why this restriction was imposed, and I believe I have
>> identif=
> ied
>> the culprit.
>
> I don't see how you can read this from the standard (draft). The C++
> standard allows for standard layout classes as arguments and there
> is no saying that the members must be immediate members
> (in C we have no such relation, therefore there is no such
> restriction).
> E.g. the following example should be well-formed and well-defined in
> C++0x:
>
> #include <cstddef>
>
> struct B { int m; };
> struct D : B {};
>
> static_assert(offsetof(D, m) == 0, "Wrong position");
>
> Note that "m" is no immediate member of D.

I was talking about address-of-class-member, not about offsetof,  and an
immediate member is referenced with just one dot so your "m" is immediate.
If you have a better term for that concept, please share it with us.
Perhaps glibc   s "named member" would be less ambiguous?

In short, what I am trying to push is:

Given the definitions

struct B { int x; };
struct A { B b; };

the expression (&A::b.x) should be well-formed and of type (int A::*).
(because so is (offsetof (A, b.x))).

Moreover, given the definitions

B A::* pB;
int B::* pi;

the expression (pB.*pi) should be well-formed and of type (int A::*).

The reason why it is not legal is syntactic, not semantic, and it is a
deficiency in the C++ language.


>
> I don't think that you can conclude the restrictions of what pointer-
> to-members can realize by inspecting the capabilities of offsetof.
> One of the main differences is that pointer-to-members do
> not allow for "random access" traversal through the structure, in
> contrast to what offsetof allows given it's supported target types.

Does my wish above constitute "random access" traversal?

Compare:

A a;
The expression (&a.b.x)[01] is well-formed, although undefined behaviour.
So you get your "random access" in C++ as well.

Thanks for your time,
Chris

___
[52] <URL:http://www.gnu.org/software/libc/manual/html_node/Structure-
Measurement.html#Structure-Measurement>

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Krzysztof =?UTF-8?B?xbtlbGVjaG93c2tp?= <giecrilj@stegny.2a.pl>
Date: Mon, 30 Aug 2010 20:07:22 CST
Raw View
David Krauss wrote:

>> The last expression obviously cannot be abbreviated to
>>
>> /*!*/ A ().* &A:: b1. f ();
>>
>> because the hypothetical pointer-to-member &A:: b1. f() has no way of
>> knowing that it should operate on b1 and not on b2.  So far, so good.
>
> It knows to operate on b1 because you nominated b1. It doesn't compile
> because, without parentheses, it's interpreted as
>
>  A().* (&A::b1 .f()); // member function call on unbound ptmf
>
> this compiles fine:
>
>  ( A().* &A::b1 ).f();

Except that I tried to argue that (&A:: b1. f) as a separate entity is
impossible.

>
>> Now replace f with n:
>>
>> B ().* &B:: n;  // == B (). n;
>> A (). b1.* &B:: n;
>> A ().* &A:: b1.* &B:: n;
>> /*!*/ A ().* &A:: b1. n;
>>
>> The last expression is ill-formed because the corresponding expression
>> with f is ill-formed, and it is the only reason; there is no semantic
>> obstacle why such a pointer-to-member should be impossible to implement.
>
> The situation is analogous:  ( A ().* &A:: b1 ). n;  does what you
> want. However, it is somewhat coincidental that the "()" and "."
> operators both have higher precedence than ".*".
>
>> On the other hand, the offsetof macro that the address-of-member operator
>> is supposed to replace has no problem with this situation (yes, I know it
>> is undocumented):
>
> "offsetof" is defined by 18.1/5. (18.1/4 in the FCD.)

N3126:18.1/3 says "offsetof (_type_, _member-designator_).  Since
_member-designator_ is undefined, so is offsetof, QED.

>
>> A a;
>> assert (&a. b1. n == (int *) ((unsigned char *) &a + offsetof (A, b1.
>> n)));
>>
>> Of course, offsetof is not type-safe and should not be used if possible;
>> however, it seems that it has no decently corresponding construct in C++.
>
> The type semantics of offsetof are well defined: only use it on POD-
> types (standard layout classes in C++0x).
>
> The type semantics of your casts, however, are NOT well-defined, quite
> the opposite in fact. Please avoid C-style casts and keep
> static_cast<> and reinterpret_cast<> distinct.

That code was extern "C".

>
> If you ask me, the difference between offsetof and pointer-to-member
> is that offsetof is numeric and pointer-to-member is not, and may be
> NULL.
>
>> I suggest that C++ should allow &A:: b1. n for data members (as an
>> expression of type int A::*).
>
> I suspect that all this confusion resulted from a relaxed
> compiler :v( . Please run your code through
> http://www.comeaucomputing.com/tryitout/

I tried but I got no response from server.

> as a double check (although it doesn't support C++0x). Also, download
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdf and
> search for your favorite C89/C99 features.

I do not know about N3092, but N3126 clearly restricts the applicability of
address-of-class-member construct to immediate members.  I was trying to
understand why this restriction was imposed, and I believe I have identified
the culprit.

Thanks for reviewing,
Chris

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]