We have code that uses offsetof
with a runtime value. Example:
#include <cstddef>
struct type {
int array[4];
};
int main(int argc, char**) {
return offsetof(type, array[argc]);
}
It doesn't matter which C++ version I compile with. g++ 11.1 and later says:
<source>: In function 'int main(int, char**)':
<source>:8:33: error: 'argc' is not a constant expression
8 | return offsetof(type, array[argc]);
| ^~~~
This compiles in our older (pre 11.1) g++ compilers and the most up to date versions of clang++, MSVC and icx all accept it and "do the right thing". I'm confused about the wording in the standard.
From C++20 first post-publication draft:
17.2.4 Sizes, alignments, and offsets [support.types.layout]
The macro
offsetof(type, member-designator )
has the same semantics as the corresponding macro in the C standard library header<stddef.h>
, but accepts a restricted set of type arguments in this document. Use of theoffsetof
macro with a type other than a standard-layout class (11.2) is conditionally-supported.The expression
offsetof(type, member-designator )
is never type-dependent (13.8.3.3) and it is valued-dependent (13.8.3.4) if and only if type is dependent. The result of applying theoffsetof
macro to a static data member or a function member is undefined. No operation invoked by theoffsetof
macro shall throw an exception andnoexcept(offsetof(type, member-designator ))
shall betrue
.
From this I get that the offsetof
expression ...
I interpret this as g++ comes to the conclusion that the expression is not valuedependent while all the others agree that it is.
The section on value-dependent expressions didn't help me to understand if g++ is right or wrong but perhaps there is something in there that I missed so I include that too:
13.8.3.4 Value-dependent expressions [temp.dep.constexpr]
- Except as described below, an expression used in a context where a constant expression is required is value-dependent if any subexpression is value-dependent.
- An id-expression is value-dependent if:
(2.1) — it is a concept-id and any of its arguments are dependent,
(2.2) — it is type-dependent,
(2.3) — it is the name of a non-type template parameter,
(2.4) — it names a static data member that is a dependent member of the current instantiation and is not initialized in a member-declarator,
(2.5) — it names a static member function that is a dependent member of the current instantiation, or
(2.6) — it names a potentially-constant variable (7.7) that is initialized with an expression that is valuedependent.
Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent:
sizeof unary-expression
sizeof ( type-id )
typeid ( expression )
typeid ( type-id )
alignof ( type-id )
noexcept ( expression )
[Note 1 : For the standard library macrooffsetof
, see 17.2. — end note]- Expressions of the following form are value-dependent if either the type-id or simple-type-specifier is dependent or the expression or cast-expression is value-dependent:
simple-type-specifier ( expression-listopt )
static_cast < type-id > ( expression )
const_cast < type-id > ( expression )
reinterpret_cast < type-id > ( expression )
( type-id ) cast-expression
- Expressions of the following form are value-dependent:
sizeof ... ( identifier )
fold-expression- An expression of the form &qualified-id where the qualified-id names a dependent member of the current instantiation is value-dependent. An expression of the form &cast-expression is also value-dependent if evaluating cast-expression as a core constant expression (7.7) succeeds and the result of the evaluation refers to a templated entity that is an object with static or thread storage duration or a member function.
The terms value-dependent and type-dependent have nothing to do with your issue. These terms are used to specify how lookup in templates is handled, nothing more. (The implicit meaning of "dependent" is always more or less something like "dependent on a template parameter".)
To see whether your usage is correct or not, you need to look at the C specification referred to by the C++ specification instead.
C requires for example in draft N3096 §7.21:
The type and member designator shall be such that given
static type t;
then the expression
&(t.member-designator)
evaluates to an address constant.
An address constant can't access the value of any object according to C and even if you would extend the restriction to constant expressions more like common in C++, then the lvalue-to-rvalue conversion of argc
still disqualifies the expression as a core constant expression.
There are other problems with the specification of offsetof
's member-designator, e.g. what happens if you used an integer literal instead of argc
in the array subscript, but I think in your case it is relatively clear.
offsetof
is also required to be an integer constant expression (see also §7.21), so its result can't possibly rely on a runtime value.