Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting around the reinterpret cast limitation with constexpr

In c++11, a constexpr expression cannot contain reinterpret casts. So for instance, if one wanted to manipulate the bits in a floating point number, say to find the mantissa of the number:

constexpr unsigned int mantissa(float x) {      return ((*(unsigned int*)&x << 9) >> 9);  }; 

The above code would fail to be constexpr. In theory, I can't see how a reinterpret cast in this or similar cases can be any different from arithmetic operators, but the complier (and the standard) don't allow it.

Is there any clever way of getting around this limitation?

like image 626
nbubis Avatar asked Oct 05 '14 08:10

nbubis


People also ask

What is the point of constexpr?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.

What is the purpose of Reinterpret_cast and how does it differ from a regular cast?

reinterpret_cast is a type of casting operator used in C++. It is used to convert a pointer of some data type into a pointer of another data type, even if the data types before and after conversion are different. It does not check if the pointer type and data pointed by the pointer is same or not.

Is reinterpret cast compile-time?

The dynamic cast is the only that needs to be "calculated" in run-time. All other casts are calculated in compile-time. The machine code for a static_cast is a fixed function based on the type you are casting FROM and TO. For reinterpret_cast , the machine code can be resolved in compile-time as well.

Why is Reinterpret_cast not constexpr?

There are very few valid uses of reinterpret_cast , most of them result in UB. Plus it is practically impossible to check if the use is valid. So reinterpret_cast is not allowed during compilation, i.e. it is not allowed in constexpr.


2 Answers

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.)

like image 167
Columbo Avatar answered Oct 11 '22 09:10

Columbo


Your particular example of getting a mantissa of a float number is actually quite simple to implement for numbers without type punning at all, and thus implement it in a constexpr fashion. The only problem would be when you want to hack on NaNs.

Since you already rely on float being the binary32 from IEEE 754, we can assume the same, but in another way — to present results. See the following code:

#include <limits> constexpr float abs(float x) { return x<0 ? -x : x; }  constexpr int exponent(float x) {     return abs(x)>=2 ? exponent(x/2)+1 :            abs(x)<1  ? exponent(x*2)-1 : 0; }  constexpr float scalbn(float value, int exponent) {     return exponent==0 ? value : exponent>0 ? scalbn(value*2,exponent-1) :                                               scalbn(value/2,exponent+1); }  constexpr unsigned mantissa(float x) {     return abs(x)<std::numeric_limits<float>::infinity() ?                 // remove hidden 1 and bias the exponent to get integer                 scalbn(scalbn(abs(x),-exponent(x))-1,23) : 0; }  #include <iostream> #include <iomanip> #include <cstring>  int main() {     constexpr float x=-235.23526f;     std::cout << std::hex << std::setfill('0');     // Show non-constexpr result to compare with     unsigned val; std::memcpy(&val,&x,sizeof val);     std::cout << std::setw(8) << (val&0x7fffff) << "\n";     // Now the sought-for constexpr result     constexpr auto constexprMantissa=mantissa(x);     std::cout << std::setw(8) << constexprMantissa << "\n"; } 

See its live demo.

like image 44
Ruslan Avatar answered Oct 11 '22 08:10

Ruslan