改变类型而不改变位
Changing type without changing bits
我想将堆栈变量 reinterpret cast
转换为相同字节大小的无符号整数类型。例如,我可能想获取 double
值并将其转换为 uint64_t
,但要注意这些位未被修改。我想以一种通用的方式来做。
如果我要处理指针,我会使用 reinterpret_cast<uint64_t*>(double_ptr)
。
我想出了一个解决方案,它在 reinterpret_cast
上使用了一个肮脏的 hack,并且很有效,但是它需要大量的元编程才能获得相当简单的结果。
问题:有更好的方法吗?我确信有,而且我正在使它变得比需要的更复杂。
我确实考虑过使用 T
类型和适当大小 int_t
的模板化联合,但这似乎更加骇人听闻,并且似乎在玩未定义的行为。
edit 我知道标准没有指定 double 应该是 64 位,正如评论中指出的那样。但是通过通用方法,我将能够获得与 double 大小相同的无符号整数类型,无论它有多大。
#include <iostream>
template <typename T, std::size_t S>
struct helper {};
template <typename T>
struct helper<T, 1> {
using type = uint8_t;
};
template <typename T>
struct helper<T, 2> {
using type = uint16_t;
};
template <typename T>
struct helper<T, 4> {
using type = uint32_t;
};
template <typename T>
struct helper<T, 8> {
using type = uint64_t;
};
template <typename T>
using int_type = typename helper<T, sizeof(T)>::type;
template <typename T>
int_type<T> caster(T value) {
int_type<T> v;
*reinterpret_cast<T*>(&v) = value;
return v;
}
int main(void) {
{
auto val = caster(0.);
static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(0.f);
static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(-0.);
static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(-0.f);
static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
return 0;
}
用 gcc 编译上面的代码得到:
> g++ --version
g++ (GCC) 4.8.2 20131016 (Cray Inc.)
> g++ -std=c++11 test.cpp && ./a.out
64 0
32 0
64 9223372036854775808
32 2147483648
在 std::conditional_t
和 std::enable_if_t
之间 我相信您可以将所有 helper
和 int_type
定义压缩到一个自给自足的 caster
函数中:
template <typename T>
auto caster(T value){return reinterpret_cast<std::conditional_t<sizeof(T) == sizeof(uint8_t),
uint8_t,
conditional_t<sizeof(T) == sizeof(uint16_t),
uint16_t,
conditional_t<sizeof(T) == sizeof(uint32_t),
uint32_t,
enable_if_t<sizeof(T) == sizeof(uint64_t),
uint64_t>>>>&>(value);}
我已经验证这适用于 gcc 4.9.2 和 Visual Studio 2015,如果你只有 C++11 支持,尽管你仍然可以把它变成一个自给自足的 caster
函数:
template <typename T>
typename std::conditional<sizeof(T) == sizeof(uint8_t),
uint8_t,
typename conditional<sizeof(T) == sizeof(uint16_t),
uint16_t,
typename conditional<sizeof(T) == sizeof(uint32_t),
uint32_t,
typename enable_if<sizeof(T) == sizeof(uint64_t),
uint64_t>::type>::type>::type>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}
这将选择与您传递给它的类型具有相同 sizeof
的 uint*
并使用它。
我有一个 std::enable_if
over here 的解释,可能对你有帮助。
显然这仅适用于大小为 8、16、32 或 64 位的类型,但如果您想扩展它以处理其他内容,只需添加另一个 conditional_t
!
如果您只要传递 8、16、32 或 64 位类型,您可以在模板中减少保护:
template <typename T>
auto caster(T value){return reinterpret_cast<std::tuple_element_t<size_t(log2(sizeof(T))), std::tuple<uint8_t,
uint16_t,
uint32_t,
uint64_t>>&>(value);}
这适用于 C++14,C++11 等价物是:
template <typename T>
typename std::tuple_element<size_t(log2(sizeof(T))), std::tuple<uint8_t,
uint16_t,
uint32_t,
uint64_t>>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}
这比 conditional_t
/enable_if_t
模板更宽松,因为我索引 std::tupple
的方式。 size_t
是整数类型,因此任何小于 128 位 的任何大小的类型都将 转换为有效的 std::tuple
索引。因此,例如,3 位大小的 struct
将被转换为 uint16_t
,而预期的结果可能是编译失败。
如果您不希望由于违反别名限制 (C++11 3.10/10) 而出现未定义的行为,那么您需要以字符形式访问对象表示:
template <typename T>
int_type<T> caster(const T& value) {
int_type<T> v;
static_assert(sizeof(value) == sizeof(v), "");
std::copy_n(reinterpret_cast<const char*>(&value),
sizeof(T),
reinterpret_cast<char*>(&v));
return v;
}
高质量的编译器会优化复制。例如,这个程序:
int main() {
return caster(3.14f);
}
effectively optimizes to return 1078523331;
on Intel processors.
我想将堆栈变量 reinterpret cast
转换为相同字节大小的无符号整数类型。例如,我可能想获取 double
值并将其转换为 uint64_t
,但要注意这些位未被修改。我想以一种通用的方式来做。
如果我要处理指针,我会使用 reinterpret_cast<uint64_t*>(double_ptr)
。
我想出了一个解决方案,它在 reinterpret_cast
上使用了一个肮脏的 hack,并且很有效,但是它需要大量的元编程才能获得相当简单的结果。
问题:有更好的方法吗?我确信有,而且我正在使它变得比需要的更复杂。
我确实考虑过使用 T
类型和适当大小 int_t
的模板化联合,但这似乎更加骇人听闻,并且似乎在玩未定义的行为。
edit 我知道标准没有指定 double 应该是 64 位,正如评论中指出的那样。但是通过通用方法,我将能够获得与 double 大小相同的无符号整数类型,无论它有多大。
#include <iostream>
template <typename T, std::size_t S>
struct helper {};
template <typename T>
struct helper<T, 1> {
using type = uint8_t;
};
template <typename T>
struct helper<T, 2> {
using type = uint16_t;
};
template <typename T>
struct helper<T, 4> {
using type = uint32_t;
};
template <typename T>
struct helper<T, 8> {
using type = uint64_t;
};
template <typename T>
using int_type = typename helper<T, sizeof(T)>::type;
template <typename T>
int_type<T> caster(T value) {
int_type<T> v;
*reinterpret_cast<T*>(&v) = value;
return v;
}
int main(void) {
{
auto val = caster(0.);
static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(0.f);
static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(-0.);
static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
{
auto val = caster(-0.f);
static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
std::cout << sizeof(val)*8 << " " << val << std::endl;
}
return 0;
}
用 gcc 编译上面的代码得到:
> g++ --version
g++ (GCC) 4.8.2 20131016 (Cray Inc.)
> g++ -std=c++11 test.cpp && ./a.out
64 0
32 0
64 9223372036854775808
32 2147483648
在 std::conditional_t
和 std::enable_if_t
之间 我相信您可以将所有 helper
和 int_type
定义压缩到一个自给自足的 caster
函数中:
template <typename T>
auto caster(T value){return reinterpret_cast<std::conditional_t<sizeof(T) == sizeof(uint8_t),
uint8_t,
conditional_t<sizeof(T) == sizeof(uint16_t),
uint16_t,
conditional_t<sizeof(T) == sizeof(uint32_t),
uint32_t,
enable_if_t<sizeof(T) == sizeof(uint64_t),
uint64_t>>>>&>(value);}
我已经验证这适用于 gcc 4.9.2 和 Visual Studio 2015,如果你只有 C++11 支持,尽管你仍然可以把它变成一个自给自足的 caster
函数:
template <typename T>
typename std::conditional<sizeof(T) == sizeof(uint8_t),
uint8_t,
typename conditional<sizeof(T) == sizeof(uint16_t),
uint16_t,
typename conditional<sizeof(T) == sizeof(uint32_t),
uint32_t,
typename enable_if<sizeof(T) == sizeof(uint64_t),
uint64_t>::type>::type>::type>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}
这将选择与您传递给它的类型具有相同 sizeof
的 uint*
并使用它。
我有一个 std::enable_if
over here 的解释,可能对你有帮助。
显然这仅适用于大小为 8、16、32 或 64 位的类型,但如果您想扩展它以处理其他内容,只需添加另一个 conditional_t
!
如果您只要传递 8、16、32 或 64 位类型,您可以在模板中减少保护:
template <typename T>
auto caster(T value){return reinterpret_cast<std::tuple_element_t<size_t(log2(sizeof(T))), std::tuple<uint8_t,
uint16_t,
uint32_t,
uint64_t>>&>(value);}
这适用于 C++14,C++11 等价物是:
template <typename T>
typename std::tuple_element<size_t(log2(sizeof(T))), std::tuple<uint8_t,
uint16_t,
uint32_t,
uint64_t>>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}
这比 conditional_t
/enable_if_t
模板更宽松,因为我索引 std::tupple
的方式。 size_t
是整数类型,因此任何小于 128 位 的任何大小的类型都将 转换为有效的 std::tuple
索引。因此,例如,3 位大小的 struct
将被转换为 uint16_t
,而预期的结果可能是编译失败。
如果您不希望由于违反别名限制 (C++11 3.10/10) 而出现未定义的行为,那么您需要以字符形式访问对象表示:
template <typename T>
int_type<T> caster(const T& value) {
int_type<T> v;
static_assert(sizeof(value) == sizeof(v), "");
std::copy_n(reinterpret_cast<const char*>(&value),
sizeof(T),
reinterpret_cast<char*>(&v));
return v;
}
高质量的编译器会优化复制。例如,这个程序:
int main() {
return caster(3.14f);
}
effectively optimizes to return 1078523331;
on Intel processors.