Why cannot call constexpr member function by pointer? - c++

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.

Related

Why does constinit allow UB?

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.

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.

Is std::less supposed to allow comparison of unrelated pointers at compile-time?

Consider this code:
#include <functional>
#include <typeinfo>
template <typename T>
inline constexpr const void *foo = &typeid(T);
int main()
{
constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);
}
Run on gcc.gotbolt.org
If I use < instead of std::less here, the code doesn't compile. This is not surprising, because the result of a relational pointer comparsion is unspecified if the pointers point to unrelated objects, and apparently such a comparsion can't be done at compile-time.
<source>:9:20: error: constexpr variable 'a' must be initialized by a constant expression
constexpr bool a = foo<int> < foo<float>;
^ ~~~~~~~~~~~~~~~~~~~~~
<source>:9:33: note: comparison has unspecified value
constexpr bool a = foo<int> < foo<float>;
^
The code still doesn't compile, even if I use std::less. The compiler error is the same. std::less appears to be implemented as < in at least libstdc++ and libc++; I get the same results on GCC, Clang, and MSVC.
However, the cppreference page about std::less claims that:
Its operator() is constexpr.
It magically implements strict total order on pointers, i.e. can be used to compare unrelated pointers with sensible results.
So, is it a bug in all those compilers, or am I missing some detail about std::less that makes the code above ill-formed?
I don't think there's a clear answer to the question that you're asking. This is a specific case of LWG 2833: marking a library function constexpr does not explain the circumstances under which calling the function will yield a constant expression.
Until this issue is resolved, I think you simply cannot rely on std::less being able to compare unrelated pointers at compile time.
To be valid constexpr function, it should have parameters for which the result is constexpr, not necessary all parameters.
For example
constexpr int foo(bool b) { if (!b) throw 42; return 42; }
is valid, f(true) can be used in constexpr (even if f(false) cannot).
constexpr int a[2]{};
constexpr bool b = std::less<const void*>{}(&a[0], &a[1]);
is valid and is enough to allow less::operator() to be constexpr.
I don't think it is specified which ranges/values are correct for constexpr in standard.
So all compilers are correct.
In your question, you declare a variable like constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);, a constexpr variable is required to satisfy the following rule:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression. (Note the emphasized part)
a constant expression must be a core constant expression, hence it must satisfy the core constant expression rules:
a relational or equality operator where the result is unspecified;
In your code, &typeid(int) and &typeid(float) is unspecified due to:
Comparing unequal pointers to objects is defined as follows:
If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript compares greater.
If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control and provided their class is not a union.
Otherwise, neither pointer compares greater than the other.
&typeid(int) and &typeid(float) conform with the third bullet point. And
If two operands p and q compare equal, p<=q and p>=q both yield true and pq both yield false. Otherwise, if a pointer p compares greater than a pointer q, p>=q, p>q, q<=p, and q<p all yield true and p<=q, p<q, q>=p, and q>p all yield false. Otherwise, the result of each of the operators is unspecified.
So, the result of comparing &typeid(int) with &typeid(float) is unspecified, Hence it's not a constant expression.

Is accessing static class member via unitilialized pointer UB?

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.