Does a function take a variable list of arguments and convert them to a series of bytes?

I want to create a function, say pack() , which takes a variable list of arguments and converts them into a sequence of bytes, for example, std::vector .

Given char c = 0x10, int x = 4, char *s = "AAA" , then pack() should behave like:

pack(c, x, s) = 0x10, 0x04, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41 .

(here I assume the byte order of the lower order)

How do I program such a function?

I was thinking about the C va_list or C ++ template mechanisms, but it's hard for me to implement this.

What is the β€œbest” way to program such a function? Any code snippets demonstrating the right technique?

+1
source share
3 answers

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 
+1
source

You can do:

 void pack_in_vector(std::vector<std::uint8_t>& v, char c) { v.push_back(c); } void pack_in_vector(std::vector<std::uint8_t>& v, int n) { v.push_back(n & 0xFF); v.push_back((n >> 8) & 0xFF); v.push_back((n >> 16) & 0xFF); v.push_back((n >> 24) & 0xFF); } void pack_in_vector(std::vector<std::uint8_t>& v, const std::string& s) { for (c : s) { v.push_back(c); } } template <typename ... Ts> std::vector<std::uint8_t> pack(const Ts&... args) { std::vector<std::uint8_t> bytes; (pack_in_vector(bytes, args), ...); // Folding expression requires C++17 return bytes; } 

For C ++ 11, you need to change the last function to:

 template <typename ... Ts> std::vector<std::uint8_t> pack(const Ts&... args) { std::vector<std::uint8_t> bytes; int dummy[] = {0, (pack_in_vector(bytes, args), 0)...}; static_cast<void>(dummy); // avoid warning for unused variable return bytes; } 
+4
source
 template<class F> void range_to_bytes( F&& f, char const* begin, char const* end ){ for(auto*it=begin; it != end; ++it) f(*it); } template<class F> void to_bytes( F&& f, char c ){ range_to_bytes(f, &c, &c+1); } template<class F> void to_bytes( F&& f, int i ){ range_to_bytes(f, (const char*)(&i), (const char*)(&i+1)); } template<class F> void to_bytes( F&& f, char const* str ){ range_to_bytes(f, str, str+strlen(str)); } template<class F, class...Ts> void to_bytes( F&& f, Ts const&... ts ){ using discard=int[]; (void)discard{0,(void( to_bytes(f, ts) ),0)...} } template<class...Ts> std::vector<char> to_vector_bytes( Ts const&... ts ){ std::size_t count = 0; to_bytes([&](char){++count;}, ts...); std::vector<char> r; r.reserve(count); to_bytes([&](char c){r.push_back(c);}, ts...); return r; } 
+1
source

Source: https://habr.com/ru/post/1271078/


All Articles