使用constexpr解决重新解释的强制转换限制

nbu*_*bis 25 c++ reinterpret-cast constexpr c++11

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

constexpr unsigned int mantissa(float x) { 
    return ((*(unsigned int*)&x << 9) >> 9); 
};
Run Code Online (Sandbox Code Playgroud)

上面的代码将无法实现constexpr.从理论上讲,我无法看到在这个或类似情况下重新解释如何与算术运算符有任何不同,但编译器(和标准)不允许它.

是否有任何巧妙的方法来解决这个限制?

Col*_*mbo 14

我无法看到在这个或类似情况下重新解释如何与算术运算符有任何不同

它不便携.

您可能已经意识到您的代码导致未定义的行为这一事实,因为您取消引用类型的惩罚指针,从而打破严格的别名.此外,由于C++ 14,将调用未定义行为的操作不再是常量表达式,因此应该产生编译器错误.

您基本上要做的是float使用积分glvalue 将对象别名.第一步是获得那个glvalue; 第二个执行左值到右值的转换.

在C++ 14中,第一步不可能在常量表达式中完成.reinterpret_cast被明确禁止.而蒙上往返void*一样static_cast<char const*>(static_cast<void const*>(&x)),不工作,要么(N3797,[expr.const/2*):

- 从类型cv void *到指向对象类型的转换;

请记住,像C风格的演员阵容(char*)减少到上面列出的任何一个static_cast或其reinterpret_cast限制.(unsigned*)&x因此减少到reinterpret_cast<unsigned*>(&x)和不起作用.

在C++ 11中,强制转换为void const*然后char const*不构成问题(根据标准; Clang仍抱怨后者).尽管如此,左值到右值的转换是一个:

一个左值到右值的转换(4.1),除非它被应用于
- 一个整数或枚举类型的glvalue,它引用一个带有前面初始化的非易失性const对象,用一个常量表达式初始化,或者
- 一个文字类型的glvalue引用非易失性对象constexpr,或引用此类对象的子对象,或
- 文字类型的glvalue引用其生命周期尚未结束的非易失性临时对象,用常量初始化表达;

前两颗子弹不适用于此; 没有任何char/ unsigned/ etc.对象之前已初始化,我们也没有定义任何此类对象constexpr.

第三个子弹也不适用.如果我们写

char ch = *(char const*)(void const*)&x;
Run Code Online (Sandbox Code Playgroud)

我们不在char初始化器中创建对象.我们x通过glvalue类型访问存储的值char,并使用该值进行初始化ch.

因此,我会说在常量表达式中不可能出现这样的混叠.在一些实现中,您可以使用宽松的规则解决这个问题.


*段落是一个以类似的东西开头的列表

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

(该文本与N3337至N3797不同.)


Rus*_*lan 10

获取float数字尾数的特定示例实际上对于没有类型惩罚的数字实现起来非常简单,因此以某种constexpr方式实现它.唯一的问题是你想要攻击NaNs.

既然你已经依靠float作为binary32从IEEE 754,我们可以假设相同,但以另一种方式-来显示结果.请参阅以下代码:

#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";
}
Run Code Online (Sandbox Code Playgroud)

查看其现场演示.


Rus*_*lan 9

从 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";
}
Run Code Online (Sandbox Code Playgroud)

查看其现场演示