在 C++ 中获取类型的底层位的通用且可移植的方法是什么?
What is a generic and portable way to get at underlying bits of a type in C++?
我想采用不大于指针的任意类型并获取其位表示形式。我尝试使用 std::bit_cast
,但这需要两种类型的大小相同。但是,由于通用性,我不知道输入类型的大小。我写了这个函数:
template<class To, class From>
To sloppy_bit_cast(From x) {
To temp{0};
std::memcpy(&temp, &x, std::min(sizeof(From), sizeof(To)));
return temp;
}
它在我的电脑上工作,但我担心 memcpy
调用会根据字节序有不同的行为。我还需要担心其他可移植性陷阱吗? std::bitset
的转换函数是否已经可移植地解决了这个问题?
编辑:具体来说,我想从最低有效位复制到最低有效位。
如果您有兴趣,这里是目前的全部代码(我还没有测试过,但我的 linter 接受了它,至少感觉是对的)。我不是要您阅读或评论它(尽管我很乐意听到反馈)。但我很高兴你们都帮助我解决了这个问题,所以我想分享。
我正在玩省略断言,不仅是为了了解我在那里的一般想法,而且我认为我可以解决这个问题来约束
#include <array>
#include <cstring>
#include <cstdint>
template<class T>
constexpr T narrow_cast(auto x) noexcept{
return static_cast<T>(x);
}
template<class To, class From>
constexpr To bit_cast(From x) noexcept {
static_assert(sizeof(To) == sizeof(From));
To temp;
std::memcpy(&temp, &x, sizeof(To));
return temp;
}
template<class T>
constexpr auto bytes_of(T x) noexcept {
return bit_cast<std::array<std::byte, sizeof x>>(x);
}
template<class T>
constexpr T to_integer(auto x) noexcept{
return static_cast<T>(x);
}
template<class UInt, class T>
constexpr auto as_UInt(T x) noexcept {
auto const bytes = bytes_of(x);
UInt acc{};
//endian agnostic
for (auto i = 0u; i < bytes.size(); ++i) {
auto const this_byte = to_integer<UInt>(bytes[i]);
acc |= (this_byte << (i * 8u));
}
return acc;
}
template<class To, class UInt>
constexpr auto from_UInt(UInt from) noexcept {
std::array<std::byte,sizeof(To)> bytes;
//endian agnostic?
for (auto i = 0u; i < bytes.size(); ++i){
bytes[i] = narrow_cast<std::byte>((from >> (i * 8u)) & 0xFFu);
}
return bit_cast<To>(bytes);
}
template<class T>
constexpr auto as_uintptr_t(T x) noexcept {
return as_UInt<uintptr_t>(x);
}
template<class To>
constexpr auto from_uintptr_t(uintptr_t x) noexcept {
return from_UInt<To>(x);
}
template<class X, class Y, int low_bit_count_ = sizeof(Y) * 8>
class uintptr_pair {
static_assert(sizeof(X) <= sizeof(uintptr_t));
static_assert(sizeof(Y) <= sizeof(uintptr_t));
public:
static constexpr auto low_bit_count = low_bit_count_;
static constexpr auto high_bit_count = sizeof(uintptr_t)*8 - low_bit_count;
constexpr uintptr_pair() = default;
constexpr uintptr_pair(X x, Y y) noexcept
: x_{as_uintptr_t(x)}, y_{as_uintptr_t(y)} {}
constexpr X x() const noexcept { return from_uintptr_t<X>(x_); }
constexpr Y y() const noexcept { return from_uintptr_t<Y>(y_); }
private:
uintptr_t x_ : high_bit_count;
uintptr_t y_ : low_bit_count;
};
constexpr auto test() {
uintptr_pair<int, int> p{3, 4};
return std::pair{p.x(),p.y()};
}
您可以轻而易举地安排正确的内存量:
template<class T>
auto get_bytes(const T &t) {
std::array<std::byte,sizeof t> ret;
std::memcpy(ret.data(),&t,sizeof t);
return ret;
}
即使 T
不可平凡复制,这仍然有效,尽管您无法将字节放回 in a T
。 (std::bit_cast<std::array<…>>
可能 可能 有效,但不能真正保证 class 的大小是正确的。)
如果您希望结果为 整数 ,您可以至少选择一个字节,然后通过 |
和 [=15 自己填充它=](它产生一个值以独立于字节序等问题进行分析)或 memcpy
从您如上所述填充的数组中进入它。同样,如果 T
的字节数较少,理论上不能保证行为,但在实践中肯定会起作用。
我想采用不大于指针的任意类型并获取其位表示形式。我尝试使用 std::bit_cast
,但这需要两种类型的大小相同。但是,由于通用性,我不知道输入类型的大小。我写了这个函数:
template<class To, class From>
To sloppy_bit_cast(From x) {
To temp{0};
std::memcpy(&temp, &x, std::min(sizeof(From), sizeof(To)));
return temp;
}
它在我的电脑上工作,但我担心 memcpy
调用会根据字节序有不同的行为。我还需要担心其他可移植性陷阱吗? std::bitset
的转换函数是否已经可移植地解决了这个问题?
编辑:具体来说,我想从最低有效位复制到最低有效位。
如果您有兴趣,这里是目前的全部代码(我还没有测试过,但我的 linter 接受了它,至少感觉是对的)。我不是要您阅读或评论它(尽管我很乐意听到反馈)。但我很高兴你们都帮助我解决了这个问题,所以我想分享。 我正在玩省略断言,不仅是为了了解我在那里的一般想法,而且我认为我可以解决这个问题来约束
#include <array>
#include <cstring>
#include <cstdint>
template<class T>
constexpr T narrow_cast(auto x) noexcept{
return static_cast<T>(x);
}
template<class To, class From>
constexpr To bit_cast(From x) noexcept {
static_assert(sizeof(To) == sizeof(From));
To temp;
std::memcpy(&temp, &x, sizeof(To));
return temp;
}
template<class T>
constexpr auto bytes_of(T x) noexcept {
return bit_cast<std::array<std::byte, sizeof x>>(x);
}
template<class T>
constexpr T to_integer(auto x) noexcept{
return static_cast<T>(x);
}
template<class UInt, class T>
constexpr auto as_UInt(T x) noexcept {
auto const bytes = bytes_of(x);
UInt acc{};
//endian agnostic
for (auto i = 0u; i < bytes.size(); ++i) {
auto const this_byte = to_integer<UInt>(bytes[i]);
acc |= (this_byte << (i * 8u));
}
return acc;
}
template<class To, class UInt>
constexpr auto from_UInt(UInt from) noexcept {
std::array<std::byte,sizeof(To)> bytes;
//endian agnostic?
for (auto i = 0u; i < bytes.size(); ++i){
bytes[i] = narrow_cast<std::byte>((from >> (i * 8u)) & 0xFFu);
}
return bit_cast<To>(bytes);
}
template<class T>
constexpr auto as_uintptr_t(T x) noexcept {
return as_UInt<uintptr_t>(x);
}
template<class To>
constexpr auto from_uintptr_t(uintptr_t x) noexcept {
return from_UInt<To>(x);
}
template<class X, class Y, int low_bit_count_ = sizeof(Y) * 8>
class uintptr_pair {
static_assert(sizeof(X) <= sizeof(uintptr_t));
static_assert(sizeof(Y) <= sizeof(uintptr_t));
public:
static constexpr auto low_bit_count = low_bit_count_;
static constexpr auto high_bit_count = sizeof(uintptr_t)*8 - low_bit_count;
constexpr uintptr_pair() = default;
constexpr uintptr_pair(X x, Y y) noexcept
: x_{as_uintptr_t(x)}, y_{as_uintptr_t(y)} {}
constexpr X x() const noexcept { return from_uintptr_t<X>(x_); }
constexpr Y y() const noexcept { return from_uintptr_t<Y>(y_); }
private:
uintptr_t x_ : high_bit_count;
uintptr_t y_ : low_bit_count;
};
constexpr auto test() {
uintptr_pair<int, int> p{3, 4};
return std::pair{p.x(),p.y()};
}
您可以轻而易举地安排正确的内存量:
template<class T>
auto get_bytes(const T &t) {
std::array<std::byte,sizeof t> ret;
std::memcpy(ret.data(),&t,sizeof t);
return ret;
}
即使 T
不可平凡复制,这仍然有效,尽管您无法将字节放回 in a T
。 (std::bit_cast<std::array<…>>
可能 可能 有效,但不能真正保证 class 的大小是正确的。)
如果您希望结果为 整数 ,您可以至少选择一个字节,然后通过 |
和 [=15 自己填充它=](它产生一个值以独立于字节序等问题进行分析)或 memcpy
从您如上所述填充的数组中进入它。同样,如果 T
的字节数较少,理论上不能保证行为,但在实践中肯定会起作用。