问题
The following C++11 program:
int x = 42;
void f()
{
int y = 43;
static_assert(&x < &y, "foo");
}
int main()
{
f();
}
Doesn't compile with gcc 4.7 as it complains:
error: ‘&y’ is not a constant expression
This would agree with my intuition. The address of y
potentially changes with each invocation of f
, so of course it cannot be calculated during translation.
However none of the bullet points in 5.19 [expr.const] seem to preclude it from being a constant expression.
The only two contenders I see are:
an lvalue-to-rvalue conversion...
but unless I am mistaken (?) there are no lvalue-to-rvalue conversions in the program.
And
an
id-expression
that refers to a variable [snip] unless:
- it is initialized with a constant expression
which y
is - it is initialized with the constant expression 43
.
So is this an error in the standard, or am I missing something?
Update:
It's confusing as hell, but I think I am on top of it, so let me show an example that will show off what is going on:
int x = 42;
void f()
{
int y = 43;
// address constant expressions:
constexpr int* px = &x; // OK
constexpr int* py = &y; // ERROR: pointer context for local variable
// boolean constant expressions:
constexpr bool bx = &x; // OK
constexpr bool by = &y; // OK
// comparison constant expressions:
constexpr bool eq = (&x == &y); // OK
constexpr bool lt = (&x < &y); // ERROR: undefined behaviour disqualifies
a constant expression
}
int main()
{
f();
}
First distinguish between a core constant expression (5.19p2) and a constant expression (5.19p4). Specifcally sub-expressions of a constant expression only have to be core constant expressions, not constant expressions. That is, being a constant expression is a property of the full expression, not sub-expressions. It further requires to look at the context it which the full expression is used.
So, as it turns out the gcc error is misleading. Firstly &y
may be a constant expression in some contexts. Secondly, the reason &x < &y
isn't a constant expression is because of the comparison of unrelated pointers, not of the sub-expression &y
.
回答1:
Let's try to determine which requirements the expression in the static_assert-declaration has to fulfil step-by-step, using n3485.
[dcl.dcl]/1
static_assert-declaration:
static_assert (
constant-expression,
string-literal) ;
[dcl.dcl]/4
In a static_assert-declaration the constant-expression shall be a constant expression that can be contextually converted to
bool
.
[expr.const]/4
Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.
So what type of constant expression is &x < &y
? It is not an address constant expression:
[expr.const]/4
An address constant expression is a prvalue core constant expression (after conversions as required by the context) of type
std::nullptr_t
or of pointer type [...].
The type of &x < &y
is bool
as per [expr.rel]/1.
It isn't a reference constant expression either, so it must be a literal constant expression, if any.
A literal constant expression is a prvalue core constant expression of literal type [...]
Therefore, &x < &y
has to fulfil the requirements of a core constant expression.
As pointed out by TemplateRex and hvd in the comments, in this particular case, &x < &y
does not fulfil the requirements of a core constant expression:
[expr.const]/2
[a core constant expression must not contain] a relational or equality operator where the result is unspecified;
[expr.rel]/2
If two pointers
p
andq
of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results ofp<q
,p>q
,p<=q
, andp>=q
are unspecified.
However, for an example like
int arr[2] = {1, 2};
static_assert(&a[0] < &a[1], "");
The expression a < a+1
fulfils this requirement as well.
回答2:
Yes, you're missing the fact that, while y
itself is initialised with a constant expression, that's not the same as &y
.
The address of y
can vary a great deal depending on your call stack history.
Paragraph 3 of C++11 5.19 Constant expressions
states the conditions under which the address-of operator can be considered a constant expression (how core constant expressions detailed in paragraph 2 are allowed to morph into "real" constant expressions):
... An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t. Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.
Since &y
is none of those things, it's not considered a constant expression.
回答3:
Taking the address of something is not the culprit here, but rather the pointer comparison using operator<
on unrelated objects.
Relational operators on pointers is only specified for pointers to objects within the same class or within an array (5.9 Relational operators [expr.rel], points 3 and 4.) Relational comparison for pointers to unrelated objects is unspecified.
Comparing the address for equality rather than ordering does work:
int x = 42;
void f()
{
int y = 43;
static_assert(&x != &y, "foo");
^^ <--- "<" on unrelated objects is unspecified
}
int main()
{
f();
}
Live example
Just to show that this has nothing to do with const-expressions per se,
void f()
{
int y[2] = { 42, 43 };
static_assert(&y[0] < &y[1], "foo");
^ <--- "<" on objects within an array is specified
}
int main()
{
f();
}
Another live example.
回答4:
5.19p2 does not define constant expressions, it defines core constant expressions.
A core constant expression only becomes a constant expression if it conforms to one of the rules in 5.19p3. There, the relevant part was already pointed out by jrok:
An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type
std::nullptr_t
.
Your core constant expression &y
does not evaluate to any of those, so it's not an address constant expression, and thus not a constant expression.
回答5:
Sorry, I agree that the previous answer was probably an incorrect reading of the items. Instead, the actual relevant clause is 5.19 [expr.const] paragraph 3 which reads (highlightening added):
A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. An integral constant expression is a literal constant expression of integral or unscoped enumeration type. [ Note: Such expressions may be used as array bounds (8.3.4, 5.3.4), as bit-field lengths (9.6), as enumerator initializers if the underlying type is not fixed (7.2), as null pointer constants (4.10), and as alignments (7.6.2). —end note ] A converted constant expression of type T is a literal constant expression, implicitly converted to type T, where the implicit conversion (if any) is permitted in a literal constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4). [ Note: such expressions may be used as case expressions (6.4.2), as enumerator initializers if the underlying type is fixed (7.2), and as integral or enumeration non-type template arguments (14.3). —end note ] A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function. An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t. Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.
A core constant expression isn't directly a constant expression, there are additional conditions which are spelled out in this third paragraph.
回答6:
In C++ pre-C++11:
Other expressions [than integral constant expressions] are considered constantexpressions only for the purpose of nonlocal static object initialization (3.6.2). Such constant expressions shall evaluate to one of the following:
[...]
-- an address constant expression,
[...]
An address constant expression is a pointer to an lvalue designating an object of static storage duration, a string literal (2.13.4), or a function.
Since y
doesn't have static storage duration, &y
would not
be a constant expression.
C++11 seems to have changed this; I suspect that this is an oversignt, however. (C++ pre-C++11 lists the things that are constant expressions. C++11 lists the things that aren't. It would be easy for one to have been forgotten.)
Regardless, of course: your comparison isn't usable in standard C++; the results of comparing two addresses which don't point into the same object is unspecified. (On the other hand, I have occasionally used something similar in machine dependent code. Not statically, but on platforms like Linux on PC or Solaris, it's possible to determine whether a pointer points to an object with static lifetime, and auto variable, or dynamically allocated memory with such tricks.)
EDIT:
The answser by paxdiablo has quoted the passage I didn't find in my reading of C++11; C++11 follows the same rule as C++ pre-11 in this respect, and in order to be a constant address expression, the address must be that of an object with static lifetime (or a function, or a null pointer).
来源:https://stackoverflow.com/questions/18352385/is-taking-the-address-of-a-local-variable-a-constant-expression-in-c11