A follow-up question to this one
We have the following code:
#include <iostream>
struct A
{
static int n;
};
int A::n = 5;
int main()
{
A* a; //uninitialized on purpose
std::cout << a->n; //UB?
}
Is such access an Undefined Behaviour? On one side, no object is needed to access static class members, on the other, operator-> on uninitialized pointer is asking for trouble.
Note: GCC and MSVC compiles this code without any warnings, Clang complains about uninitialized usage. https://godbolt.org/z/Gy5fR2
The semantics of a->n are that *a is evaluated, but not accessed, since the data member is static. See C++17 [expr.ref]:
... The postfix expression before the dot or arrow is evaluated ... The expression
E1->E2 is converted to the equivalent form (*(E1)).E2 ...
There is also a footnote that says:
If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.
In this case, the expression *a is evaluated. Since a is an automatic variable that has not been initialized, [dcl.init]/12 applies:
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases: [ ... ]
Evaluating *a clearly requires accessing the value of the pointer variable a, which is an indeterminate value, therefore it is UB.
Related
See the code below, for clang (14.0.6), almost all member function calling via pointer were compile-error, except calling by 0x0. But GCC would accept more.
It seems lacks useful information on cppreference.(https://en.cppreference.com/w/cpp/language/constant_expression)
How can I call constexpr member function and constexpr conversion function by pointer?
#include <memory>
struct MyType {
constexpr static bool fixed() { return true; }
constexpr bool dyn() const { return true; }
constexpr MyType() {}
};
int main() {
std::unique_ptr<MyType> uptr;
MyType inst;
MyType *ptr;
MyType &ref = inst;
static_assert( (*(const MyType* const)0).fixed() ); //clang_Pass!
static_assert( (*(const MyType* const)1).fixed() ); //clang_error
static_assert(uptr->fixed()); //error
static_assert(uptr->dyn()); //error
static_assert(inst.fixed()); //Pass!
static_assert(inst.dyn()); //Pass!
static_assert(ptr->fixed()); //GCC_Pass! clang_error
static_assert(ptr->dyn()); //error
static_assert(ref.fixed()); //GCC_Pass! clang_error
static_assert(ref.dyn()); //error
return 0;
}
(*(const MyType* const)0).fixed()
Has defined behavior and is a constant expression, because 0 is a null pointer constant, so that this resolves to a static_cast, not reinterpret_cast. Therefore none of the disqualifiers in [expr.const]/5 apply. Clang is correct to accept the static_assert and GCC should also accept it. This wouldn't work if you retrieved 0 from a variable or anything but using the literal 0 directly in the cast.
(*(const MyType* const)1).fixed()
Behavior of the cast is implementation-defined, but in either case, it uses reinterpret_cast which is not allowed in constant expressions.
uptr->fixed()
You default-initialized uptr which means it is empty. operator-> of std::unique_ptr has a pre-condition that it is not empty. Therefore this has undefined behavior. It is unspecified whether library undefined behavior causes an otherwise constant expression to not be a constant expression. So it is unspecified whether compilation will fail with the static_assert.
uptr->dyn()
Same.
inst.fixed()
That has defined behavior and is a constant expression, because none of the disqualifiers in [expr.const]/5 apply.
inst.dyn()
Same.
ptr->fixed()
That's UB for dereferencing a pointer with indeterminate value and therefore also not a constant expression. GCC is wrong to accept it in the static_assert.
ptr->dyn()
Same.
ref.fixed()
Defined behavior, but not a constant expression until recently because ref isn't usable in constant expressions, as it hasn't been initialized with to refer to an object of static storage duration. That's specifically a requirement when using a reference's name in a constant expression. (see [expr.const]/5.12)
This was recently improved in the standard as a defect report against older versions, but compilers haven't implemented it yet as far as I know. I think (should probably check) this should work with that defect report implemented. I guess GCC passing it was previously a bug, not due to implementation of the DR.
ref.dyn()
Same.
How can I call constexpr member function and constexpr conversion function by pointer?
The pointer variable needs to be marked constexpr and initialized to refer to an object with static storage duration or the pointer's lifetime must start during evaluation of the constant expression. In the case of fixed it should also be fine to mark the pointer constexpr and initialize it with a null pointer constant, preferably nullptr over a literal 0.
Recently I was surprised that the following code compiles in clang, gcc and msvc too (at least with their current versions).
struct A {
static const int value = 42;
};
constexpr int f(A a) { return a.value; }
void g() {
A a; // Intentionally non-constexpr.
constexpr int kInt = f(a);
}
My understanding was that the call to f is not constexpr because the argument i isn't, but it seems I am wrong. Is this a proper standard-supported code or some kind of compiler extension?
As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.
There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.
The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).
Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.
Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.
Note that, as mentioned above, the rules are stricter if you use references, e.g.
A a;
A& ar = a;
constexpr int kInt = f(ar);
will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)
#include <iostream>
constexpr int func2(int const& id){
return id;
}
template<int v>
struct Test{
};
int main(){
const int v = 0;
Test<func2(v)> c;
}
Consider the above code,I just don't understand why the code is well-formed.My pointview is that the name v is used as a glvalue when evalute expression func2,becuase the parameter of func2 is of reference type,the v need to be bound to the id-expression id.So we look at the requirement of a glvalue constant expression,here are quotes about that.
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints.
We ignore the case of prvalue,because here v is used as a glvalue.
An entity is a permitted result of a constant expression is:
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
In my program portion,The const int v = 0; does not have static storage duration,it just has automatic storage duration.So when evaluting the expression func2(v) to determine whether it is a constant expression,Firstly,the v must be a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression,therefore,why the program is well-formed here?If I lose any important quote,Please correct me.
We ignore the case of prvalue, because here v is used as a glvalue
Is it though? This is an example from cppreference:
void test() {
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}
And yes, const int b = 42 is rather weird here because technically speaking, you can bind b to const int&, const_cast the const away and assign a runtime value to it. However, considering what is an integral constant expression and what are the requirements of a const object it makes perfect sense:
Integral constant expression is an expression of integral or unscoped
enumeration type implicitly converted to a prvalue, where the
converted expression is a core constant expression. If an expression
of class type is used where an integral constant expression is
expected, the expression is contextually implicitly converted to an
integral or unscoped enumeration type.
Variable b sure does look like something you could implicitly convert to a prvalue constant expression because it basically serves as an alias for literal 42 in this context and integer literals are prvalues by definition.
Now for the problematic part - this:
const object - an object whose type is const-qualified, or a
non-mutable subobject of a const object. Such object cannot be
modified: attempt to do so directly is a compile-time error, and
attempt to do so indirectly (e.g., by modifying the const object
through a reference or pointer to non-const type) results in undefined
behavior.
And:
A core constant expression is any expression whose evaluation would
not evaluate any one of the following:
...
an expression whose evaluation leads to any form of core language
undefined behavior (including signed integer overflow, division by
zero, pointer arithmetic outside array bounds, etc). Whether standard
library undefined behavior is detected is unspecified.
Means that as soon as you start doing funny things with that b, you can expect anything to happen. For example, this is what I tried doing to your code in latest MSVC with all standard-conformance options switched on:
#include <iostream>
#include <random>
constexpr int func2(int const& id) {
return id;
}
template<int v>
struct Test {
long array[v];
};
int main() {
const int v = 0;
const int& ref = v;
const_cast<int&>(ref) = std::random_device()() % std::numeric_limits<int>::max();
Test<func2(v)> c;
return 0;
}
With language extensions turned on I got a C4200: nonstandard extension used : zero-sized array in struct/union warning. After switching them off, the program wouldn't compile. And when I deleted the array part from the struct it started compiling again.
I try to answer this question.why the func2(v) is a constant expression,Becuase For expression func2(v),when evaluting this postfix-expression,there's no requirement that v must be a glvalue constant expression in the list of "would evaluate one of the following expressions:",Even,these rules does not mandate that the one expression within a potentially core constant expression would be a glvalue constant expression,only require the expression does not violate the listed requirement.So let's continue,When initialization of the parameter,It's another rule here:
A full-expression is:
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer
So,when evalute this full-expression,it only does not violate these listed condition,then the func2(v) would be evaluted as a constant expression,So let's look at these rules:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or,
its lifetime began within the evaluation of e;
For id-expression id,its preceding initialization is the corresponding argument,Because of this rule:
When a function is called, each parameter ([dcl.fct]) shall be initialized ([dcl.init], [class.copy], [class.ctor]) with its corresponding argument.
So,the first condition is true.And "it is initialized with a constant expression" is false,the condition "its lifetime began within the evaluation of e" is true.In conlusion,the expression func2(v) indeed a constant expression
Consider the following example. When bar is constructed, it gives it's base type (foo) constructor the address of my_member.y where my_member is data member that hasn't been initialized yet.
struct foo {
foo(int * p_x) : x(p_x) {}
int * x;
};
struct member {
member(int p_y) : y(p_y) {}
int y;
};
struct bar : foo
{
bar() : foo(&my_member.y), my_member(42) {}
member my_member;
};
#include <iostream>
int main()
{
bar my_bar;
std::cout << *my_bar.x;
}
Is this well defined? Is it legal to take the address of an uninitialized object's data member? I've found this question about passing a reference to an uninitialized object but it's not quite the same thing. In this case, I'm using the member access operator . on an uninitialized object.
It's true that the address of an object's data member shouldn't be changed by initialization, but that doesn't necessarily make taking that address well defined. Additionally, the ccpreference.com page on member access operators has this to say :
The first operand of both operators is evaluated even if it is not necessary (e.g. when the second operand names a static member).
I understand it to mean that in the case of &my_member.y my_member would be evaluated, which I believe is fine (like int x; x; seems fine) but I can't find documentation to back that up either.
First let's make accurate the question.
What you are doing isn't using an uninitialized object, you are using an object not within its lifetime. my_member is constructed after foo, therefore the lifetime of my_member hasn't begun in foo(&my_member.y).
From [basic.life]
before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any glvalue that refers to the original object may be used but only in limited ways. [...] such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
the glvalue is used to access the object, or [...]
Here accessing it means specifically to either read or modify the value of the object.
The evaluation of my_member yields a lvalue, and there is nothing necessitating a conversion to prvalue, hence it stays a lvalue. Likewise, the evaluation of my_member.y is also a lvalue. We then conclude that no object's value have been accessed, this is well-defined.
Yes you are allowed to pass &my_member.y to foo's constructor, and even copy the pointer - which you do with x(p_x).
The behaviour on dereferencing that pointer though in foo's constructor is undefined. (But you don't do that.)
I think that the the variable declared as const applies only Static Initialization. I've written the following:
#include <cstdlib>
#include <iostream>
struct A{ };
const A *i = new A();
int main(){ }
and it works fine.
Ideone
But I expected that the the code is invalid because new A() is a new-expression and it is not a constant expression. Actually:
sec. 5.19/2 N3797:
A conditional-expression e is a core constant expression unless the
evaluation of e, following the rules of the abstract machine (1.9),
would evaluate one of the following expressions:
[...]
— a new-expression (5.3.4);
[...]
and
A constant expression is either a glvalue core constant expression
whose value refers to an object with static storage duration or to a
function,
First off, you probably meant A * const i (a constant pointer to A) and not const A * i (a non-constant pointer to const A).
Still, even with this modification, it is perfectly legal to initialise a const variable with a value that is not a constant expression (such as a value computed at runtime). However, it is then not possible to use such a variable inside constant expressions. If you tried that, the constant expression definition would kick in and you'd get an error.
The initialisation of a const variable does not require a constant expression.