1. Introduction
Currently, C++ delegates the semantics of to the C standard,
as explained in [support.types.layout\] paragraph 1:
The macro
has the same semantics as the corresponding macro in the C standard library headeroffsetof ( type , member - designator ) , but accepts a restricted set of type arguments in this document. [...]< stddef . h >
Further restrictions and clarifications follow. However, even with those additional clarifications, multiple problems arise with the C standard wording when pulled into C++ like this:
1.1. CWG2784. Unclear definition of member-designator for offsetof
[CWG2784] raises the question whether the following code is valid:
struct S { int a ; }; int x = offsetof ( S , S :: a );
C requires from the user
(see 7.21 paragraph 3)
that for the macro ,
given the declaration :
the expression
evaluates to an address constant.& ( t . member - designator )
Since there is no qualified-id in C,
it is unclear whether can be used as a member-designator.
MSVC and GCC support this, but clang rejects the code.
All in all, CWG2784 raises three questions:
-
Is a qualified-id allowed to appear in a member-designator?
-
Is a template-id allowed to appear in a member-designator?
-
Is an
expression a core constant expression?offsetof
EWG is soliciting a paper to thoroughly explore the design space.
1.2. Interaction with overloaded & operator
The C wording is also problematic because as stated above, C requires from the user that
evaluates to an address constant.& ( t . member - designator )
It is unclear from normative wording whether the refers to the non-overloaded operator in C, or the potentially overloaded operator in C++.
[support.types.layout] footnote 165 states that
offsetof is required to work as specified even if unary
is overloaded for any of the types involved.operator &
However, this doesn’t clearly answer the question of what in the C wording means when pulled into C++.
It also isn’t normative, and seemingly unsupported by any normative wording.
1.3. Interaction with non-public members
As stated above, C requires that
evaluates to an address constant.& ( t . member - designator )
If this expression has semantics defined in C
(without operator overloading and access control),
then a member-designator which designates a member should be accepted as well.
However, MSVC, GCC, and Clang reject where is a private member.
Note: A class can be standard-layout as long as all non-static data members have the same access control.
1.4. Undefined behavior for non-default-constructible classes
As stated above, the C standard describes restrictions given the declaration .
Obviously, this would not work for non-default-constructible types,
which suggests that has undefined behavior for any class type
that has no accessible default constructor.
2. Impact
3. Implementation experience
Note that the current implemented behavior historically originated from the C implementation:
#define offsetof(T,m) ((size_t)&((T*)0)->m)
However, this doesn’t handle overloaded operators properly (among other issues),
so MSVC, GCC, and Clang now delegate this to .
is a core language feature masquerading as a library feature,
and when considering design questions (§ 4 Design Considerations)
we are not restricted by what can be self-hosted in C++.
4. Design Considerations
The general approach is to re-define entirely in C++.
The C wording is simply unfit to be pulled into C++ as it is now;
there are too many open questions resulting from this.
For this proposal, the design is is essentially for to give the user the offset of (which must be well-formed),
where is an lvalue of type ,
and is an unqualified-id or qualified-id which designates
a non-static data member.
This naturally answers whether a qualified-id is allowed,
what role access control plays,
and eliminates questions regarding the overloaded operator.
5. Proposed wording
Modify [support.types.layout] paragraph 1 as follows:
The macrooffsetof ( type , member - designator ) has the same semantics as the corresponding macro in the C standard library headerexpands to a prvalue constant expression of type, but accepts a restricted set of type arguments in this document. , the value of which is the offset in bytes, to the subobject designated bysize_t , from the first byte of any object of typemember - designator .type The expression is well-formed only ifmember - designator : qualified - id unqualified - id member - designator . qualified - id member - designator . unqualified - id member - designator [ assignment - expression ] is a type-id which denotes a complete class type and given an lvaluetype of typet ,type The expression offsetof(type, member-designator)
is not a bit-field,t . member - designator designates ([expr.ref]) a member subobject oft . member - designator (directly or indirectly) or array element thereof,t - for any use of the subscript operator within member-designator, the left operand shall be of of array type and shall designate a member subobject of
(directly or indirectly) or array element thereof, and the right operand shall be an integral constant expression ([expr.const]).t is never type-dependent and it is value-dependent if and only ifis type-dependent or value-dependent when the expressionis dependent.type is type-dependent or value-dependent, respectively.t . member - designator The result of applying the offsetof macro to a static data member or a function member is undefined.No operation invoked by the offsetof macro shall throw an exception andshall be true.noexcept ( offsetof ( type , member - designator ))
In Annex C, modify subclause [diff.offsetof] paragraph 1 as follows:
The macro offsetof, defined in, accepts a restricted set of type arguments in C++ ., and supportss which would not be valid in C. Subclause [support.types.layout] describes the change.member - designator