Why does constinit allow UB? - c++

Say I initialize variables like this:
#include <cstdint>
constexpr uint16_t a = 65535;
constinit int64_t b = a * a; // warning: integer overflow in expression of type 'int' results in '-131071' [-Woverflow]
constexpr int64_t c = a * a; // error: overflow in constant expression [-fpermissive]
Both b and c produce undefined behavior because of integer overflow.
With constinit the variable is constant initialized. Which makes no guarantee about UB.
With constexpr the variable is initialized with a constant expression. Constant expression guarantee not to have any UB. So here the signed integer overflow in an error. But the variable is also automatically const.
So how do I best initialize a non-const variable with a constant expression?
Do I have to write
constexpr int64_t t = a * a; // error: overflow in constant expression [-fpermissive]
constinit int64_t b = t;
or
constinit int64_t b = []()consteval{ return a * a; }(); // error: overflow in constant expression
every time?

This is related to CWG issue 2543.
As it stands currently, because the compiler is allowed to replace any dynamic initialization with static initialization if it can and because constinit is only specified to enforce "no dynamic initialization", it might still allow an initializer which is not a constant expression (maybe dependent on the interpretation as discussed in the linked issue). constinit therefore reflects whether there will actually be initialization at runtime (which is relevant to avoiding dynamic initialization order issues). It does not necessarily reflect whether the initializer is a constant expression.
As stated in the issue description, this is practically not really implementable though because the dynamic/static initialization choice is made too late in the compilation process to always make constinit reflect it properly.
With one possible resolution of the issue, the specification of constinit might be changed to actually require the variable to be constant-initialized instead of just requiring that there is no dynamic initialization. If that was the resolution taken, then your first example for the initialization of b would also require the compiler to diagnose the UB and all of the other solutions would become obsolete.
The issue description doesn't seem to really favor any direction though.
For the current situation (and if the resolution is taken in another direction), an alternative to the solutions you gave is:
template<typename T>
consteval auto force_compiletime(T&& t) {
return std::forward<T>(t);
}
or
template<typename To, typename T>
consteval To force_compiletime2(T&& t) {
return std::forward<T>(t);
}
and then
constinit auto t = force_compiletime(static_cast<int64_t>(a * a));
or
constinit auto t = force_compiletime2<int64_t>(a * a);
Note that you need to include the target type in this way in the initializer, otherwise any potentially UB in the conversion will not be diagnosed. If you don't care about that
constinit int64_t t = force_compiletime(a * a);
would also be fine.
Technically the solution with the consteval lambda from your question is ill-formed, no diagnostic required, because the lambda is marked consteval but can never produce a constant expression when called. But I would expect any non-malicious compiler to still diagnose such a call.

Related

Why cannot call constexpr member function by pointer?

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.

Can one volatile constexpr variable initialize another one in C++?

C++ standard allows constexpr volatile variables per defect report 1688, which was resolved in September 2013:
The combination is intentionally permitted and could be used in some circumstances to force constant initialization.
It looks though that the intention was to allow only constinit volatile, which was not available before C++20.
Still the current compilers diverge in treatment of constexpr volatile in certain circumstances. For example, this program initializes one such variable by the other one:
int main() {
constexpr volatile int i = 0;
constexpr volatile int j = i;
return j;
}
It is accepted in GCC and MSVC, but Clang complains:
error: constexpr variable 'j' must be initialized by a constant expression
constexpr volatile int j = i;
^ ~
note: read of volatile-qualified type 'const volatile int' is not allowed in a constant expression
constexpr volatile int j = i;
Online demo: https://gcc.godbolt.org/z/43ee65Peq
Which compiler is right here and why?
Clang is correct. The initialization of j from i requires that an lvalue-to-rvalue conversion be performed on i, but according to [expr.const]/5.9, an lvalue-to-rvalue conversion on a volatile glvalue is never permitted inside a constant expression. Since i is a constexpr variable, it must be initialized by a constant expression.
I have no idea why GCC and MSVC choose not to enforce this rule, other than that all C++ compilers are perpetually short-staffed and can't implement everything they're expected to.
The defect report you linked shows it should not work, so Clang is correct.
(...) “a non-volatile object defined with constexpr” (...) is permitted but that such a variable cannot appear in a constant expression. What is the intent?
But more interesting is: why does Clang care while other compilers don't?
In my opinion, this happened because of JF Bastien, a very influential figure in the world of Clang / LLVM, that personally dislikes volatile :)
He has been proposing to remove it from the language for a long time. So if it was allowed to ban volatile somewhere, he probably spared no effort to make it so. If for no other reason than simply to prevent people from writing code that will have to be rewritten if his proposal is eventually accepted.
He also made a presentation at CppCon about his deprecation proposal, if you want to know his reasoning.

constexpr result from non-constexpr call

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)

For constant expressions, why can't I use a use a pointer-type object, as standards says?

I was trying to figure out the restrictions of constexpr in cpp11/14. There are some usage requirements I found in CPP14-5.19-4:
A constant expression is either a glvalue core constant expression
whose value refers to an object with static storage duration or to a
function, or a prvalue core constant expression whose value is an
object where, for that object and its subobjects:
...
if the object or subobject is of pointer type, it contains the address of another object with static storage duration, the address past
the end of such an object (5.7), the address of a function, or
a null pointer value.
I've run some tests(code shown below) for expressions that involves address-of operator &, in order to ensure the correctness of the standards' statements quoted above.
Simply put, I tried to take the address of a global int variable global_var, which is an object with static storage duration(if I was not thinking wrong), everything works just as standards points out. But, what confused me is that, when I tried to assign another pointer-type object(global_var_addr1 in code), which stored the address of the same object global_var, the program won't compile. And GCC says:
error: the value of ‘global_var_addr1’ is not usable in a constant expression
note: ‘global_var_addr1’ was not declared ‘constexpr’
, while Clang-Tidy says:
error: constexpr variable 'x2' must be initialized by a constant expression [clang-diagnostic-error]
note: read of non-constexpr variable 'global_var_addr1' is not allowed in a constant expression
and I don't know why, is there anything I missed?
So my question is:
1. Why, in a constant expression, I cannot use a pointer-type object which contains the address of an object with static storage duration, as standards says?
2. Why everything goes different in the same context as (1), when the object is auto specified?
Any advices would be welcomed, thanks in advance!
Code:
const int global_var_c = 123;
int global_var = 123;
const void *global_var_addr1 = &global_var;
const void *global_var_addr2 = nullptr;
auto global_var_addr3 = nullptr;
auto main() -> int
{
constexpr const int x00 = global_var_c; // OK
constexpr const void *x0 = &global_var; // OK
// Operate on the object of pointer type
constexpr const void *x1 = &global_var_addr1; // OK
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr variable 'global_var_addr1'...
// Operate on nullptr
constexpr const void *x3 = &global_var_addr2; // OK
constexpr const void *x4 = global_var_addr2; // ERROR: read of non-constexpr variable 'global_var_addr2'...
// Operate on nullptr (with type deduction)
constexpr const void *x5 = global_var_addr3; // OK
constexpr const void *x6 = &global_var_addr3; // OK
}
In both
constexpr const void *x2 = global_var_addr1;
and
constexpr const void *x4 = global_var_addr2;
a lvalue-to-rvalue conversion happens from the variable global_var_addr1/global_var_addr2 glvalue to the pointer value they hold. Such a conversion is only allowed if the variable's lifetime began during the evaluation of the constant expression (not the case here) or if it is usable in constant expressions, meaning that it is constexpr (not the case here) or initialized by a constant expression (is the case here) and of reference or const-qualified integral/enumeration type (not the case here).
Therefore the initializers are not constant expressions.
This is different in the case of
constexpr const int x00 = global_var_c;
since global_var_c is of const-qualified integral type.
I am not exactly sure about
constexpr const void *x5 = global_var_addr3; // OK
Intuitively it should work, because the type of nullptr and consequently the deduced type of global_var_addr3 is std::nullptr_t which doesn't need to carry any state, so that a lvalue-to-rvalue conversion wouldn't be necessary. Whether the standard actually guarantees that, I am not sure at the moment.
Reading the current wording (post-C++20 draft), [conv.ptr] specifies only conversion of a null pointer constant (i.e. a prvalue of std::nullptr_t) to another pointer type and [conv.lval] specifically states how the lvalue-to-rvalue conversion of std::nullptr_t produces a null pointer constant. [conv.lval] also clarifies in a note that this conversion doesn't access memory, but I don't think that makes it not a lvalue-to-rvalue conversion given that it still written under that heading.
So it seems to me that strictly reading the standard
constexpr const void *x5 = global_var_addr3; // OK
should be ill-formed (whether global_var_addr3 is const-qualified or not).
Here is an open clang bug for this. There seems to be a link to come internal discussion by the standards committee, which I cannot access.
In any case, the auto placeholder doesn't matter. You could have written std::nullptr_t for it instead directly.
All of these are requirements for being a core constant expression, which is a prerequisite to the requirements you mention in your question.
The variable declared here is clearly not constexpr (nor even const):
const void *global_var_addr1 = &global_var;
And you can't use non-constexpr values to initialize constexpr values. So it's no surprise this fails to compile:
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr
The address of a non-constexpr value can be used in cases like you've shown, however, but the value stored in a variable and the address of a variable are not the same thing.

Using constinit variable to initialize a constexpr variable

Look at this little example:
constinit int a = 0;
constexpr int b = a;
clang doesn't compile it (godbolt):
2:15: error: constexpr variable 'b' must be initialized by a constant expression
Is this correct diagnostics?
If yes, why doesn't the standard allow this? I understand, that a's value may change during running (or even during dynamic-initialization), but at constant-initialization, its value is known, so it could be used to initialize b.
Yes, the diagnostic is correct. constexpr variables must be initialized with a constant expression, and a is not a constant expression (it's a mutable variable).
The purpose of constinit (P1143) is to force a variable declaration to be ill-formed if it's initialization is not constant. It doesn't change anything about the variable itself, like it's type or anything (in the way that constexpr is implicitly const). Silly example:
struct T {
int i;
constexpr T(int i) : i(i) { }
T(char c) : i(c) { }
};
constinit T c(42); // ok
constinit T d('X'); // ill-formed
That is all constinit is for, and the only real rule is [dcl.constinit]/2:
If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed.
[ Note: The constinit specifier ensures that the variable is initialized during static initialization ([basic.start.static]).
— end note
]
The const in constinit refers only to the initialization, not the variable, not any types. Note that it also doesn't change the kind of initialization performed, it merely diagnoses if the wrong kind is performed.
In:
constinit int a = 0;
constexpr int b = a;
0 is a constant expression, so the initialization of a is well-formed. Once we get past that, the specifier doesn't do anything. It's equivalent to:
int a = 0; // same behavior, a undergoes constant initialization
constexpr int b = a;
Which is straightforwardly ill-formed.
but at constant-initialization, its value is known, so it could be used to initialize b.
Sure, at this moment. What about:
constinit int a = 0;
cin >> a;
constexpr int b = a;
That's obviously not going to fly. Allowing this would require extending what a constant expression is (already the most complex rule in the standard, in my opinion) to allow for non-constant variables but only immediately after initialization? The complexity doesn't seem worth it, since you can always write:
constexpr int initializer = 0;
constinit int a = initializer;
constexpr int b = initializer;
constexpr combines constinit and const without exception.
constinit forces initialization with a compiletime constant expression, and during static initialization, disallowing dynamic initialization. It does not change the variable itself in any way though.
const forbids changing the variable, though can be weakened by mutable members.
Both together make it a compiletime constant expression.
In summary, yes, the diagnostic is right.
is this correct diagnostics?
I would say yes. According to cppreference:
constinit - specifies that a variable must have static initialization,
i.e. zero initialization and constant initialization, otherwise the
program is ill-formed.
Static (constant) initialization and constant expression are different concepts in that a constant expression may be used in a constant initialization but not the other way around. constinit shouldn't be confused with const. It means the initialization (only) is constant.
However, constinit const can be used in a constexpr and they are supposed to be the same.
Counter-example:
constinit int a = 0;
struct X{
X() {
a = 4;
}
};
X x{};
constexpr int b = a;
What is b supposed to be ?
The point is that a can be changed in non-const ways before b is seen.