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 header
offsetof ( 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 macro
offsetof ( 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 by
size_t , from the first byte of any object of type
member - 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 lvalue
type of type
t ,
type The expression offsetof(type, member-designator)
is not a bit-field,
t . member - designator designates ([expr.ref]) a member subobject of
t . 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