【问题标题】:Function taking a variable list of arguments and converting them to a series of bytes?函数获取参数的变量列表并将它们转换为一系列字节?
【发布时间】:2018-01-28 19:13:57
【问题描述】:

我想创建一个函数,比如pack(),它接受一个可变参数列表并将它们转换为一系列字节,例如std::vector

给定char c = 0x10, int x = 4, char *s = "AAA",然后是pack(),应该表现得像:

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

(这里我假设小端字节序)

我将如何编写这样的功能?

我一直在考虑 C 的 va_list 或 C++ 的模板机制,但我无法实现。

编写此类功能的“最佳”方式是什么?任何代码 sn-ps 演示了一种合适的技术?

【问题讨论】:

  • 打开您的 C++ 书籍到描述可变参数模板的章节,然后开始阅读。这是 C++ 中最复杂的部分之一,无法在 stackoverflow.com 答案中的一两段中完全解决。简短的回答是:学习如何使用可变参数模板。
  • 但是给定这样的模板,我将如何获得参数的类型,以便我可以使用例如char * 指针来提取字节?
  • 你可以实现一个流式操作符并重载它。
  • 这应该在前面提到的 C++ 章节中解释,应该有很多实现递归模板函数的例子,这些函数将它们的参数收集到某种容器中。
  • 请注意,可变参数模板只出现在 C++11 中,所以这本书应该是相当现代的。

标签: c++


【解决方案1】:

你可以这样做:

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;
}

对于 C++11,您必须将最后一个函数修改为:

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;
}

【讨论】:

  • 谢谢,我真的很喜欢这个。有什么方法可以修改它,使它可以用 C++11 编译吗?
  • 加分可以bytes.reserve((sizeof(args) + ...));
  • @HolyBlackCat:不一定准确:sizeof(std::string)
  • @jarod42 它不一定是完美的。此外,可以编写一个byteof 函数来使其完美。我,我会用字节接收器模式解决这个问题,然后写入保留并以字节接收器的形式推回。作为奖励,容器的递归字节接收可能会失败。
【解决方案2】:
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;
}

【讨论】:

    【解决方案3】:

    让我分享我的解决方案。与之前提出的一次相比,它的优势在于它适用于所有类型:基本类型、静态数组、自定义对象、容器(向量、列表、字符串...)、C 字符串(文字和动态分配)。

    如果您想限制这些类型(例如,不允许打包指针),您可以随时添加更多 SFINAE :) 或者只是一个 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;
    }
    

    测试:

    #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;
    }
    

    输出:

    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
    

    【讨论】:

    • char* const &amp; c_str 中的“&”是否表示引用?
    • Ah) 因为我的主模板采用const T&amp; value。因此,如果我只写 char* 这是一个无效的专业化(只能是一个重载)。但是,如果我将其设为重载,则重载优先级开始播放并在传递字符数组chars[] 时,将调用此重载。但是,它仅适用于 c 字符串,所以我会得到错误的结果。
    • 如果你不打算传递任何重的、不可迭代的对象,那么你可以将 const T&amp; value 替换为 T value 然后 char*const char* (没有那个奇怪的 @987654333 @) 可以正常工作。
    • 啊,谢谢,- 如果我需要在多个源文件中使用模板怎么办?那我需要内联实现吗?我需要在与声明相同的标题中定义模板,对吧?
    • 现在,您可以将所有这些代码放在单独的标头中,它可以用作仅标头的实现。 (不要忘记包括警卫或#pragma once
    猜你喜欢
    • 2021-12-17
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2016-01-05
    相关资源
    最近更新 更多