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.