绕过 constexpr 的重新解释演员限制
Getting around the reinterpret cast limitation with constexpr
在 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_cast
或reinterpret_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";
}
观看其现场演示。
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 请解释"函数1(p1,p2,p3);"的输出
- 多成员Constexpr结构初始化
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 条件constexpr函数
- constexpr 函数中的非文字(通过 std::is_constant_evaluated)
- Visual C++ constexpr Hints
- 被解释为低级别const的const对象的地址
- 如何确认我的constexpr表达式实际上已经在编译时执行
- 计算每个节点的树高,帮助我解释这个代码解决方案
- MSVC将仅移动结构参数解释为指针
- 为什么constexpr的性能比正常表达式差
- 是否可以使用if constexpr删除控制流语句
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 内联程序集printf将整数解释为地址
- 为什么std::isnan 不是 constexpr?
- Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 更多constexpr容器是否需要mark_immutable_if_consexpr
- 绕过 constexpr 的重新解释演员限制