绕过 constexpr 的重新解释演员限制

Getting around the reinterpret cast limitation with constexpr

本文关键字:解释 constexpr 新解释 绕过      更新时间:2023-10-16

在 c++11 中,constexpr表达式不能包含重新解释强制转换。例如,如果想要操纵浮点数中的位,例如找到该数字的尾数:

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

上面的代码将无法constexpr。 从理论上讲,我看不出在这种情况下或类似情况下重新解释转换与算术运算符有何不同,但编译器(和标准(不允许这样做。

有什么聪明的方法可以绕过这个限制吗?

我看不出在这种情况下或类似情况下重新解释演员如何 不同于算术运算符

它不便携。

您可能知道您的代码会导致未定义的行为,因为您取消引用类型双关指针,从而破坏严格的别名。此外,从 C++14 开始,调用未定义行为的操作甚至不再是常量表达式,因此应该产生编译器错误。

您基本上要做的是用积分 glvalue 为float对象设置别名。第一步是获得该gl值;第二个执行左值到右值转换。

在 C++14 中,第一步不可能在常量表达式中完成。 reinterpret_cast是明确禁止的。和 void* 之间的转换,如static_cast<char const*>(static_cast<void const*>(&x)),也不起作用(N3797,[expr.const]/2*(:

— 从 CV void * 类型转换为指向对象的指针类型;

请记住,像 (char*) 这样的 c 样式转换被简化为static_castreinterpret_cast,其限制在上面列出。 因此,(unsigned*)&x减少到reinterpret_cast<unsigned*>(&x)并且不起作用。

在C++11中,石膏到void const*然后到char const*并不构成问题(根据标准;叮当仍然抱怨后者(。尽管如此,左值到重值的转换仍然是一种:

左值到右值的转换 (4.1(,除非它应用于
— a 整数或枚举类型的 glvalue ,指的是非易失性 具有前面初始化的 const 对象,使用 常量表达式,或
— 文本类型的 glvalue 引用 用 constexpr 定义的非易失性对象,或引用 此类对象的子对象,或
— 文字类型的 glvalue 指生存期未 结束,用常量表达式初始化;

前两个项目符号在这里不适用;之前也没有初始化任何 char/unsigned/etc. 对象,也没有使用 constexpr 定义任何此类对象。

第三个项目符号也不适用。如果我们写

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

我们不会在初始值设定项中创建 char 对象。我们通过 char 类型的 glvalue 访问 x 的存储值,并使用该值初始化ch

因此,我想说这种混叠在常量表达式中是不可能的。在某些具有宽松规则的实现中,您可能会解决此问题。


* 该段落是一个以类似内容开头的列表

条件表达式是核心常量表达式,除非 [...]

(文本从 N3337 到 N3797 不同。

您获取float数尾数的特定示例实际上非常简单,无需类型双关语即可实现数字,因此以constexpr的方式实现它。唯一的问题是当你想破解NaN时。

由于您已经依赖float是IEEE 754的binary32,因此我们可以假设相同,但以另一种方式 - 呈现结果。请参阅以下代码:

#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";
}

观看其现场演示。

从 C++20 开始,有一个标准的库解决方案:std::bit_cast(自 GCC 11 起在 GCC 中受支持(。下面是使用它的示例:

#include <bit>
#include <cstdint>
#include <iomanip>
#include <iostream>
int main()
{
    constexpr float x=-235.23526f;
    constexpr auto integer=std::bit_cast<std::uint32_t>(x);
    constexpr auto mantissa=integer&0x7fffff;
    static_assert(mantissa==0x6b3c3a);
    std::cout << std::hex << std::setfill('0')
              << std::setw(8) << mantissa << "n";
}

观看其现场演示。