没有兼容的方式来转换相同大小的签名/无符号

edA*_*a-y 7 c++ language-lawyer c++11

我担心我可能会遗漏一些微不足道的东西,但是如果你想保留原始的无符号值,似乎没有实际的安全方式来转换为签名类型.

在reinterpret_cast上,5.2.10没有列出整数到整数的转换,因此没有定义(而static_cast定义没有额外的转换).关于积分转换4.7.3基本上说大型无符号的转换将是实现定义的(因此不可移植).

这似乎是有限的,因为我们知道,例如,uint64_t在任何硬件上,应该可以安全地转换为a int64_t和back而不会改变值.另外,标准布局类型的规则实际上保证了安全转换,如果我们memcpy在两种类型之间而不是分配.

我对么?有没有合理的理由为什么不能reinterpret_cast在整数类型之间有足够的大小?


澄清:肯定无符号的签名版本不保证值,但它只是我考虑的往返(unsigned => signed => unsigned)


更新:仔细查看答案并交叉检查标准,我相信memcpy实际上并没有保证工作,因为它没有说明这两种类型是布局兼容的,也不是char类型.进一步更新,深入研究C-standard这个memcpy应该可以工作,因为目标的大小足够大并且它复制了字节.


答案:似乎没有技术上的理由说明为什么不允许reinterpret_cast执行此转换.对于这些固定大小的整数类型,a memcpy保证可以工作,实际上只要中间可以表示所有的位模式,任何中间类型都可以使用(浮点数可能很危险,因为可能存在陷阱模式).通常,您不能在任何标准布局类型之间进行memcpy,它们必须是兼容的或char类型.这里的注明是特殊的,因为它们有额外的保证.

bam*_*s53 2

正如您所指出的,memcpy 是安全的:

uint64_t a = 1ull<<63;
int64_t b;
memcpy(&b,&a,sizeof a);
Run Code Online (Sandbox Code Playgroud)

值 b 仍然是实现定义的,因为 C++ 不需要二进制补码表示,但将其转换回来将为您提供原始值。

正如 Bo Persson 指出的那样,int64_t 将是二进制补码。因此,memcpy 应该产生一个有符号值,该值的简单整数转换回无符号类型被明确定义为原始无符号值。

uint64_t c = b;
assert( a == c );
Run Code Online (Sandbox Code Playgroud)

另外,您可以实现自己的“signed_cast”以使转换变得容易(我不利用二进制补码,因为它们不限于 intN_t 类型):

template<typename T>
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value,T>::type
signed_cast(typename std::make_unsigned<T>::type v) {
    T s;
    std::memcpy(&s,&v,sizeof v);
    return s;
}

template<typename T>
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value,T>::type
signed_cast(typename std::make_signed<T>::type v) {
    T s;
    std::memcpy(&s,&v,sizeof v);
    return s;
}
Run Code Online (Sandbox Code Playgroud)

  • C++ 标准引用了 C99 标准的 7.18,其中规定“typedef 名称 `intN_t` 指定宽度为 N、无填充位和二进制补码表示形式的有符号整数类型。” (7.18.1.1)。 (4认同)