Let me share my solution. Its advantage over the previously proposed one is that it works for all types: fundamental types, static arrays, user objects, containers (vector, list, string ...), C-strings (both literals and dynamically allocated) .
If you want to restrict these types (for example, not allow pointers to packaging), you can always add more SFINAE :) Or just 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