You don't say whether you want the data to be stored in little-endian format (e.g. intel processors) or big-endian (network byte order).
Here's how I would tackle the problem.
I have provided both versions for comparison.
#include <cstdint>
#include <type_traits>
#include <cstddef>
#include <iterator>
struct little_endian {}; // low bytes first
struct big_endian {}; // high bytes first
template<class T>
auto integral_to_bytes(T value, unsigned char* target, little_endian)
-> std::enable_if_t<std::is_unsigned_v<T>>
{
for(auto count = sizeof(T) ; count-- ; )
{
*target++ = static_cast<unsigned char>(value & T(0xff));
value /= 0x100;
}
}
template<class T>
auto integral_to_bytes(T value, unsigned char* target, big_endian)
-> std::enable_if_t<std::is_unsigned_v<T>>
{
auto count = sizeof(T);
auto first = std::make_reverse_iterator(target + count);
while(count--)
{
*first++ = static_cast<unsigned char>(value & T(0xff));
value /= 0x100;
}
}
int main()
{
extern std::uint16_t get_some_value();
extern void foo(unsigned char*);
unsigned char buffer[6];
std::uint16_t some_value = get_some_value();
// little_endian
integral_to_bytes(some_value, buffer + 3, little_endian());
foo(buffer);
// big-endian
integral_to_bytes(some_value, buffer + 3, big_endian());
foo(buffer);
}
You can take a look at the resulting assembler here. You can see that either way, the compiler does a very good job of converting logical intent into very efficient code.
update: we can improve style without cost in emitted code. Modern c++ compilers are amazing:
#include <cstdint>
#include <type_traits>
#include <cstddef>
#include <iterator>
struct little_endian {}; // low bytes first
struct big_endian {}; // high bytes first
template<class T, class Iter>
void copy_bytes_le(T value, Iter first)
{
for(auto count = sizeof(T) ; count-- ; )
{
*first++ = static_cast<unsigned char>(value & T(0xff));
value /= 0x100;
}
}
template<class T, class Iter>
auto integral_to_bytes(T value, Iter target, little_endian)
-> std::enable_if_t<std::is_unsigned_v<T>>
{
copy_bytes_le(value, target);
}
template<class T, class Iter>
auto integral_to_bytes(T value, Iter target, big_endian)
-> std::enable_if_t<std::is_unsigned_v<T>>
{
copy_bytes_le(value,
std::make_reverse_iterator(target + sizeof(T)));
}
int main()
{
extern std::uint16_t get_some_value();
extern void foo(unsigned char*);
unsigned char buffer[6];
std::uint16_t some_value = get_some_value();
// little_endian
integral_to_bytes(some_value, buffer + 3, little_endian());
foo(buffer);
// big-endian
integral_to_bytes(some_value, buffer + 3, big_endian());
foo(buffer);
}