将std::vector<uint8_t>转换为紧缩的std::vector<uint64_t>

5

我正在寻找一种高效且无未定义行为的方法,将std::vector<uint8_t>转换为std::vector<uint64_t>,使得 std::vector<uint64_t> 中的每个元素都包含来自 std::vector<uint8_t> 的8个元素的信息。剩余的元素应该填充为零,但这可以稍后完成。

目前我想到的最佳方法是:

std::vector<uint64_t> foo(std::vector<uint8_t> const & v8) {
    std::vector<uint64_t> v64;
    v64.reserve((v8.size() + 7) / 8);
    size_t i = 0;
    uint64_t tmp;
    for(; i + 8 < v8.size(); i += 8) {
        memcpy(&tmp, v8.data() + i, 8);
        v64.push_back(tmp);
    }
    tmp = 0; // fill remainder with 0s.
    memcpy(&tmp, v8.data() + i, v8.size() - i);
    v64.push_back(tmp);
    return v64;
}


但我希望有一种更干净/更好的方法。 编辑1:该解决方案存在字节顺序问题。@VainMain指出了这一点。 可以在 memcpy 之后进行字节交换来修复它。

1
你可能想了解字节序:http://zh.wikipedia.org/wiki/字节序。 - VainMan
@VainMan 这里有关系吗?我基本上是想将一个字节数组重新解释为具有完全相同字节模式的 uint64_t 数组。或者你是在说这对于任何标准解决方案都会有问题? - Noah
1
当一个由 {1, 2} 组成的两字节数组转换为 uint16_t 时,你期望得到 (1 << 8) | 2 还是 (2 << 8) | 1 - VainMan
是的。我应该(但我没有,我的错)指出问题是你将如何处理转换后的结果,而不是正确地转换回字节数组。我认为对结果进行的操作,除了非移位/尾随/前导位运算之外,都会涉及字节序问题。 - VainMan
1
@user3124812,是的,但据我所知,那将违反严格别名规则。 - Noah
显示剩余5条评论
1个回答

2
如果您想使用range-v3库,可以使用ranges::view::chunk(或者很快可能会有的C++23标准库实现等价物)。这将特别让您从计算存储打包值所需的向量大小中解脱出来。
#include <array>
#include <cstddef>
#include <span>
#include <vector>

#include <range/v3/all.hpp>

std::vector<std::uint64_t> pack(const std::span<const std::uint8_t> values)
{
    const auto chunked_view = ranges::view::chunk(values, 8);

    std::vector<std::uint64_t> packed(ranges::size(chunked_view));
    ranges::transform(chunked_view, packed.begin(), [](const auto& word) {

        std::array<std::uint8_t, 8> buf{0}; // init with all 0's
        ranges::copy(word, buf.begin());

        std::uint64_t packed_word;
        std::memcpy(&packed_word, buf.data(), 8);
        return packed_word;
    });
    
    return packed;
}

示例 (在godbolt.org上查看)

int main()
{
    std::array<uint8_t, 9> values;
    std::iota(values.begin(), values.end(), std::uint8_t{0});

    for (auto t : pack(values))
        std::cout << std::hex << t << std::endl;
    // prints
    // 706050403020100
    // 8
    return 0;
}

为什么要从span复制到数组,然后再复制到memcpy中?是为了处理长度不能被8整除的情况吗?我会拒绝这样的输入。 - Caleth
@Caleth 是的,这正是原因(“剩余元素应该填充为零[但可以稍后完成]”)。如果没有数组,就需要直接通过memcpy从word复制(然后需要将其作为连续范围),或者需要将packed_word重新解释为std::uint8_t*以将其传递给memcpy。选择后一种方法时,与带有数组的版本相比,gcc二进制文件不会发生任何变化。 - Quxflux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接