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

时间:2014-10-05 08:11:07

标签: c++ c++11 constexpr reinterpret-cast

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

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

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

有没有巧妙的方法来解决这个限制?

3 个答案:

答案 0 :(得分:12)

  

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

不便携。

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

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

在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*并不构成问题(根据标准; Clang仍抱怨后者)。尽管如此,左值到右值的转换是:

  

左值 - 右值转换(4.1),除非它适用于
- a   整数或枚举类型的glvalue,指的是非易失性   const对象,具有前面的初始化,用a初始化   常量表达式,或者 - 一个引用a的文字类型的glvalue   用constexpr定义的非易失性对象,或引用的   这样一个对象的子对象,或者 - 一个文字类型的glvalue   是指一生中没有的非易失性临时对象   结束,用常量表达式初始化;

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

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

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

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

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


*该段落是一个以

开头的列表
  

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

(文字与N3337至N3797不同。)

答案 1 :(得分:7)

获取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";
}

请参阅its live demo

答案 2 :(得分:1)

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

#include <bit>
#include <iomanip>
#include <iostream>

int main()
{
    constexpr float x=-235.23526f;
    constexpr auto integer=std::bit_cast<unsigned>(x);
    constexpr auto mantissa=integer&0x7fffff;
    static_assert(mantissa==0x6b3c3a);
    std::cout << std::hex << std::setfill('0')
              << std::setw(8) << mantissa << "\n";
}

its live demo