Let me share my solution. Its advantage over the previously proposed once is that it work for all types: fundamental types, static arrays, custom objects, containers (vector, list, string...), C-strings (both literal and dynamically allocated).
If you want to limit those types (say, not to allow packing pointers) you can always add more SFINAE :) Or just a static_assert
...
// byte_pack.h
#include <vector>
#include <type_traits>
// a small trait to check if it is possible to iterate over T
template<typename T, typename = void>
constexpr bool is_iterable = false;
template<typename T>
constexpr bool is_iterable<T, decltype(
std::begin(std::declval<T&>()) != std::end(std::declval<T&>()), void())> = true;
typedef std::vector<std::uint8_t> byte_pack; // vector of bytes itself
template<typename T, std::enable_if_t<(!is_iterable<T>)>* = nullptr>
void pack(byte_pack& bytes, const T& value) // for not iteratable values (int, double, custom objects, etc.)
{
typedef const std::uint8_t byte_array[sizeof value];
for(auto& byte : reinterpret_cast<byte_array&>(value)) {
bytes.push_back(byte);
}
}
template<typename T, std::enable_if_t<is_iterable<T>>* = nullptr>
void pack(byte_pack& bytes, const T& values) // for iteratable values (string, vector, etc.)
{
for(const auto& value : values) {
pack(bytes, value);
}
}
template<>
inline void pack(byte_pack& bytes, const char* const & c_str) // for C-strings
{
for(auto i = 0; c_str[i]; ++i) {
bytes.push_back(c_str[i]);
}
}
template<>
inline void pack(byte_pack& bytes, char* const & c_str) { // for C-strings
pack(bytes, static_cast<const char*>(c_str));
}
template<typename T, size_t N>
void pack(byte_pack& bytes, const T (&values) [N]) // for static arrays
{
for(auto i = 0u; i < N; ++i) {
pack(bytes, values[i]);
}
}
// finally a variadic overload
template<typename... Args>
byte_pack pack(const Args&... args)
{
byte_pack bytes;
int dummy[] = { 0, (pack(bytes, args), 0) ... };
return bytes;
}
Tests:
#include "byte_pack.h"
void cout_bytes(const std::vector<std::uint8_t>& bytes)
{
for(unsigned byte : bytes) {
std::cout << "0x" << std::setfill('0') << std::setw(2) << std::hex
<< byte << " ";
}
std::cout << std::endl;
}
int main()
{
// your example
char c = 0x10; int x = 4; const char* s = "AAA";
cout_bytes(pack(c, x, s));
// static arrays and iterateble objects
char matrix1[2][2] = { {0x01, 0x01}, {0xff, 0xff} };
std::vector<std::vector<char>> matrix2 = { {(char) 0x01, (char) 0x01}, {(char) 0xff, (char) 0xff} };
cout_bytes(pack(matrix1, matrix2));
// strings
char* str2 = new char[4] { "AAA" };
std::string str1 = "AAA";
cout_bytes(pack(str1, str2));
// custom objects (remember about alignment!)
struct { char a = 0x01; short b = 0xff; } object1;
struct { short a = 0x01ff; char b = 0x01; } object2;
cout_bytes(pack(object1, object2));
return 0;
}
Output:
0x10 0x04 0x00 0x00 0x00 0x41 0x41 0x41
0x01 0x01 0xff 0xff 0x01 0x01 0xff 0xff
0x41 0x41 0x41 0x41 0x41 0x41
0x01 0x00 0xff 0x00 0xff 0x01 0x01 0x00