1

Given the following array: std::array<char, 10> stuff I'd like to convert the last 4 characters to the corresponding int32 value.

I tried to chain OR operations on the last items but doesn't seem to be the right way:

int a = int(stuff[6] | stuff[7] | stuff[8] | stuff[9])

Is there an elegant way to solve this?

Stack Danny
  • 7,754
  • 2
  • 26
  • 55
Kyu96
  • 1,159
  • 2
  • 17
  • 35

2 Answers2

5

What you tried to do has elegance that comes across in not needing an endianness check in order to work properly. What you missed was some shifting to indicate significance in the final value:

int a = stuff[6] << 24 | stuff[7] << 16 | stuff[8] << 8 | stuff[9];

This alone does not care about endianness because from the language's perspective, it is based on values rather than bytes. You determine which values are most significant.

That said, this also assumes an 8-bit byte and at least 4-byte int. If you want elegance of use, you can get it with a safe and general abstraction:

#include <array>
#include <climits>
#include <cstddef>

namespace detail {
    // Could be replaced by an inline lambda-template in C++20.
    template<typename T, std::size_t N, std::size_t... Is>
    constexpr T pack_into_impl(const std::array<std::byte, N>& bytes, std::index_sequence<Is...>) {
        // Build final value from right to left to make the math more clear 
        // and to use the least significant bytes available when N < sizeof(T).
        // e.g., bytes[3] << 0 | bytes[2] << 8 | bytes[1] << 16 | bytes[0] << 24
        return ((static_cast<int>(bytes[N-Is-1]) << (CHAR_BIT * Is)) | ...);
    }
}

// Takes bytes to pack from most significant to least significant.
// N.B. this is not a production-ready doc comment for this function.
template<typename T, std::size_t N>
constexpr T pack_into(std::array<std::byte, N> bytes) {
    static_assert(sizeof(T) >= N, "Destination type is too small for this many bytes");
    return detail::pack_into_impl<T>(bytes, std::make_index_sequence<N>{});
}

// Convenience overload.
template<typename T, typename... Bytes>
constexpr T pack_into(Bytes... bytes) {
    // Check that each Bytes type can be static_cast to std::byte.
    // Maybe check that values fit within a byte.
    return pack_into<T>(std::array{static_cast<std::byte>(bytes)...});
}

int main() {
    static_assert(pack_into<int>(0x12, 0x34, 0x56, 0x78) == 0x12345678);
    static_assert(pack_into<int>(0x01, 0x02) == 0x0102);
    // pack_into<int>(0x01, 0x02, 0x03, 0x04, 0x05); // static_assert
}

Some of this can be cleaned up in C++20 by using concepts and a []<std::size_t... Is> lambda, but you get the idea. Naturally, you're also free to transform the API to make the size unknown at compile-time for convenience and live with a possible runtime check when too many bytes are given. It depends on your use case.

chris
  • 60,560
  • 13
  • 143
  • 205
2

Believe it or not, even though this is C++, memcpy() is the recommended way to do this kind of thing:

int32_t a;
memcpy(&a, stuff.data() + 6, 4);

It avoids strict aliasing violations, and compilers will optimize the memcpy call away.

Be aware of endianess differences if the data you're loading was created on a different machine with a different CPU architecture.

Justin
  • 24,288
  • 12
  • 92
  • 142
Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • 2
    Do be aware that this produces a different result depending on the endianness of the target. – Justin Apr 19 '19 at 23:43
  • 1
    @Nikos C. Have you seen [this](https://stackoverflow.com/a/9980859/9226753)? Clearly not the recommended way using `memcpy` over `std::copy`. – serkan.tuerker Apr 20 '19 at 00:44
  • 1
    @kanstar `std::copy` is a type-safe alternative to `memcpy`. However, there's nothing type-safe about copying bytes from `char` into an `int32_t`, so `std::copy` doesn't offer anything here. – Nikos C. Apr 20 '19 at 00:49
  • 1
    @NikosC. I still think we should teach `c++` when people are asking `c++` questions. Otherwise people will always go back to habits which are 30 years old. And you said it yourself, `std::copy` offers type-safety. Maybe in this example it doesn't matter that much, but in future, when people read your accepted answer, they will use `memcpy` instead of `std::copy`. – serkan.tuerker Apr 20 '19 at 00:59
  • 1
    @kanstar There's nothing wrong with `memcpy()` for low-level operations like this one. I am not going to give an answer that assumes programmers are not capable of judgement and learning. Programmers are usually smart people, and I treat them as such. – Nikos C. Apr 20 '19 at 14:02
  • @kanstar Using `std::copy` for this would be UB. [`std::copy`](https://en.cppreference.com/w/cpp/algorithm/copy) is not guaranteed to compile to a `memcpy`. Furthermore, `std::copy` is semantically wrong for this case. `std::copy` copies a sequence of `T`s, but in this case, we are not copying a sequence, but type punning by moving bytes around. – Justin Apr 20 '19 at 22:47