Constexpr and Initialization of a Static Const Void Pointer With Reinterpret Cast, Which Compiler Is Right

constexpr and initialization of a static const void pointer with reinterpret cast, which compiler is right?

TL;DR

clang is correct, this is known gcc bug. You can either use intptr_t instead and cast when you need to use the value or if that is not workable then both gcc and clang support a little documented work-around that should allow your particular use case.

Details

So clang is correct on this one if we go to the draft C++11 standard section 5.19 Constant expressions paragraph 2 says:

A conditional-expression is a core constant expression unless it
involves one of the following as a potentially evaluated subexpression
[...]

and includes the following bullet:

— a reinterpret_cast (5.2.10);

One simple solution would be to use intptr_t:

static constexpr intptr_t ptr = 0x1;

and then cast later on when you need to use it:

reinterpret_cast<void*>(foo::ptr) ;

It may be tempting to leave it at that but this story gets more interesting though. This is know and still open gcc bug see Bug 49171: [C++0x][constexpr] Constant expressions support reinterpret_cast. It is clear from the discussion that gcc devs have some clear use cases for this:

I believe I found a conforming usage of reinterpret_cast in constant
expressions useable in C++03:

//---------------- struct X {  X* operator&(); };

X x[2];

const bool p = (reinterpret_cast<X*>(&reinterpret_cast<char&>(x[1]))
- reinterpret_cast<X*>(&reinterpret_cast<char&>(x[0]))) == sizeof(X);

enum E { e = p }; // e should have a value equal to 1
//----------------

Basically this program demonstrates the technique, the C++11 library
function addressof is based on and thus excluding reinterpret_cast
unconditionally from constant expressions in the core language would render this useful program invalid and would make it impossible to
declare addressof as a constexpr function.

but were not able to get an exception carved for these use cases, see closed issues 1384:

Although reinterpret_cast was permitted in address constant
expressions in C++03, this restriction has been implemented in some
compilers and has not proved to break significant amounts of code. CWG
deemed that the complications of dealing with pointers whose tpes
changed (pointer arithmetic and dereference could not be permitted on
such pointers) outweighed the possible utility of relaxing the current
restriction.

BUT apparently gcc and clang support a little documented extension that allows constant folding of non-constant expressions using __builtin_constant_p (exp) and so the following expressions is accepted by both gcc and clang:

static constexpr const void* ptr = 
__builtin_constant_p( reinterpret_cast<const void*>(0x1) ) ?
reinterpret_cast<const void*>(0x1) : reinterpret_cast<const void*>(0x1) ;

Finding documentation for this is near impossible but this llvm commit is informative with the following snippets provide for some interesting reading:

support the gcc __builtin_constant_p() ? ... : ... folding hack in C++11

and:

// __builtin_constant_p ? : is magical, and is always a potential constant.

and:

// This macro forces its argument to be constant-folded, even if it's not
// otherwise a constant expression.
#define fold(x) (__builtin_constant_p(x) ? (x) : (x))

We can find a more formal explanation of this feature in the gcc-patches email: C constant expressions, VLAs etc. fixes which says:

Furthermore, the rules for __builtin_constant_p calls as conditional
expression condition in the implementation are more relaxed than those
in the formal model: the selected half of the conditional expression
is fully folded without regard to whether it is formally a constant
expression, since __builtin_constant_p tests a fully folded argument
itself.

Using Reinterpret_Cast in a Constexpr Function

Firstly, a compiler can execute a function at compile-time even if it's not constexpr, as long as it doesn't affect the visible behavior of the program. Conversely it can execute a constexpr function at runtime, as long as knowing its result is not required at compile-time.

Since you're saying you don't know how to test if your functions are callable at compile-time, it looks to me like you're only adding constexpr to make your code faster. You don't need to do it, and it probably won't change anything because of what I said above.

As for the tricks you used, they don't do anything useful.

constexpr doesn't mean that the function can always be executed at compile-time. It means that it can be executed at compile-time for some argument values (function or template arguments).

Example:

constexpr int foo(bool x) // The function compiles.
{
if (x)
return true;
else
return rand();
}

constexpr int a = foo(true); // Ok.
constexpr int b = foo(false); // Error.
int c = foo(false); // Ok.

Compilers are not required to strictly verify that at least one suitable argument exists (because it's impossible in general).

That's what happens in your code. The compiler rejects constexpr on a function when it's certain that no arguments make it callable at compile-time. When it's uncertain, it lets it slide. But when the function is actually called, it becomes clear that its result is not actually constexpr, and its constexprness is silently ignored (see example above).

Since there are no possible arguments that allow your functions to be executed at compile-time, your code is ill-formed NDR. NDR ("no diagnostic required") means that compilers are not required to notice this error, and different compilers can be more or less strict about it.

Here's the revelant parts of the standard:

[dcl.constexpr]/6 and /7

6 — For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.

7 — If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression.
If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required.

Or, in simple words:

reinterpret_cast fails constexpr function

Yes, use std::bit_cast, which is in header <bit>:

#include <bit>
#include <cstdint>
constexpr double pi = 3.14159265358979323846;
constexpr auto fraction = std::bit_cast<std::uint64_t>(pi) & 0x000F'FFFF'FFFF'FFFFull;

You'll need a compiler with C++20 support. Currently there aren't any, but from Clang 9 you can at least use the builtin that will be used to implement bit_cast in future:

#if __clang__
constexpr auto fraction = __builtin_bit_cast(std::uint64_t, pi) & 0x000F'FFFF'FFFF'FFFFull;
#endif

Example.

GCC constexpr allows add but not bitwise-or with address

You can’t use reinterpret_cast (or a C-style cast that performs one) in a constant expression at all. GCC either has a bug enforcing that, or is trying to be helpful to support some practical use case where + but not | is relevant.

Statically casting an integer to pointer type

The problem here is that although you are declaring a const void* the const qualifier doesn't apply to the pointer but to the address that this pointer points to.

This means that noop is not a static const member variable and as all non const static member variables needs to be defined and initialized in a single translation unit outside class's definition like the example below:

class C {
public:
static const void *noop;
};

const void* C::noop = (const void*) 0x1;

Live Demo

The following solution:

class C {
public:
static constexpr const void* noop = reinterpret_cast<const void*>(0x1);
};

Although, it compiles and works fine in GCC isn't valid C++ (e.g., it doesn't compile with either clang or VC++2013) because according to the standard § 5.19/2 Constant Expressions [expr.const] the result of a reinterpret_cast can't be a constant expression.

Determine `constexpr` execution - during compilation or at runtime?

Prior to C++20, this wasn't possible. C++20 then added std::is_constant_evaluated which is exactly for this use case:

constexpr int pow(int base, int exp) noexcept
{
if (std::is_constant_evaluated())
{
auto result = 1;

for (int i = 0; i < exp; i++)
result *= base;

return result;
}
else
{
return std::pow(base, exp);
}
}

Note that the if statement itself is not constexpr. If it were, the whole else arm would be removed from the function and it would always run the if arm, no matter if at compile time or runtime. With a normal if statement, you basically get two functions. One that runs at compile time:

constexpr int pow(int base, int exp) noexcept
{
auto result = 1;

for (int i = 0; i < exp; i++)
result *= base;

return result;
}

and one that gets compiled an runs at runtime:

constexpr int pow(int base, int exp) noexcept
{
return std::pow(base, exp);
}

The compiler can safely remove the if arm because it can prove that it isn't reachable at runtime. Pretty neat.

Calling a constexpr method through a reference - is the result a constant expression?

[expr.const]/2:

A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
([intro.execution]), would evaluate one of the following expressions:

  • [...]
  • 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;
  • [...]

Evaluating a.size() evaluates the id-expression a, which "refers to a variable...of reference type" and has no preceding initialization. It is therefore not a core constant expression and so not a constant expression.

Getting around the reinterpret cast limitation with constexpr

I can't see how a reinterpret cast in this or similar cases can be any
different from arithmetic operators

It isn't portable.

You are probably aware of the fact that your code causes undefined behavior, since you dereference a type punned pointer and thus break strict aliasing. Moreover, since C++14, operations that would invoke undefined behavior aren't even constant expressions anymore and should thus produce a compiler error.

What you are basically trying to do is alias the float object with an integral glvalue. The first step is to obtain that glvalue; the second to perform an lvalue-to-rvalue conversion.

In C++14, the first step is impossible to accomplish in constant expressions. reinterpret_cast is explicitly forbidden. And casts to and from void*, like static_cast<char const*>(static_cast<void const*>(&x)), don't work either (N3797, [expr.const]/2*):

— a conversion from type cv void * to a pointer-to-object type;

Keep in mind that a c-style cast like (char*) is reduced to either static_cast or reinterpret_cast whose limitations are listed above. (unsigned*)&x therefore reduces to reinterpret_cast<unsigned*>(&x) and doesn't work.

In C++11, the cast to void const* and then to char const* does not constitute a problem (according to standard; Clang still complains about the latter). The lvalue-to-rvalue conversion is one nonetheless:

an lvalue-to-rvalue conversion (4.1) unless it is applied to
— a
glvalue of integral or enumeration type that refers to a non-volatile
const object with a preceding initialization, initialized with a
constant expression, or
— a glvalue of literal type that refers to a
non-volatile object defined with constexpr, or that refers to a
sub-object of such an object, or
— a glvalue of literal type that
refers to a non-volatile temporary object whose lifetime has not
ended, initialized with a constant expression;

The first two bullets can't apply here; Neither has any char/unsigned/etc. object been initialized precedingly, nor did we define any such object with constexpr.

The third bullet doesn't apply either. If we write

char ch = *(char const*)(void const*)&x;

we don't create a char object in the initializer. We access the stored value of x through a glvalue of type char, and use that value to initialize ch.

Therefore I'd say that such aliasing isn't possible in constant expressions. You may get around this in some implementations with relaxed rules.


* The paragraph is a list that starts with something like

A conditional-expression is a core constant expression unless [...]

(The text differs from N3337 to N3797.)



Related Topics



Leave a reply



Submit