【问题标题】:Parsing data via variadic templates通过可变参数模板解析数据
【发布时间】:2021-08-03 11:34:49
【问题描述】:

我有一些代码与一些硬件进行一些串行通信,然后读回一些数据(如std::string)。

  std::string data = cmd_exe("gmov");
  char cmdReadback[4];
  uint32_t Speed;
  uint8_t uSpeed;
  uint16_t Accel;
  uint16_t Decel;
  uint32_t AntiplaySpeed;
  uint8_t uAntiplaySpeed; 
  cpMemReadback(cmdReadback, data, 0);
  cpMemReadback(Speed, data, 4);
  cpMemReadback(uSpeed, data, 8);
  cpMemReadback(Accel, data, 9);
  cpMemReadback(Decel, data, 11);
  cpMemReadback(AntiplaySpeed, data, 13);
  cpMemReadback(uAntiplaySpeed, data, 17);

cpMemReadback 很简单:

static constexpr auto cpMemReadback = [](auto& reg, std::string& s, size_t ix) {
  std::memcpy(&reg, s.data()+ix, sizeof(reg));
};

这段代码中还有一些丑陋的东西——但最让我烦恼的是cpMemReadback 的无用调用,然后是对索引的跟踪。我想写:

parse_data(data, cmdReadback, Speed, uSpeed, ...);

我知道我想使用可变参数模板,我大概需要一个模板参数来跟踪索引,然后是我的变量的参数包,然后递归地执行代码,增加我的索引跟踪模板参数。我实在是太笨了,连代码都写不出来。

【问题讨论】:

  • 我会尝试 std::tie 那些参数,然后 std::apply 一个可变参数 lambda

标签: c++ c++17 variadic-templates


【解决方案1】:

据我了解,您可能有:

template <typename... Ts>
void parseData(const std::string& data, Ts&... args)
{
    [[maybe_unused]]std::size_t idx = 0;
    ((cpMemReadback(args, data, idx), idx += sizeof (Ts)), ...);
}

然后替换

cpMemReadback(cmdReadback, data, 0);
cpMemReadback(Speed, data, 4);
cpMemReadback(uSpeed, data, 8);
cpMemReadback(Accel, data, 9);
cpMemReadback(Decel, data, 11);
cpMemReadback(AntiplaySpeed, data, 13);
cpMemReadback(uAntiplaySpeed, data, 17);

通过

parse_data(data,
           cmdReadback,
           Speed,
           uSpeed,
           Accel,
           Decel,
           AntiplaySpeed,
           uAntiplaySpeed);

【讨论】:

    【解决方案2】:

    我知道我想使用可变参数模板,我大概需要一个模板参数来跟踪索引,然后是我的变量的参数包,然后递归地执行代码,增加我的索引跟踪模板参数。

    这是解决此问题的一种完全有效的方法,尽管在模板参数中跟踪 idx 有点矫枉过正。将其作为常规参数也可以。

    你的想法是这样的:

    template<std::size_t idx=0, typename First, typename... Rest>
    void parse_data(const std::string& data, First& first, Rest&... rest) {
        // Handle the first element in the list
        cpMemReadback(first, data, idx);
    
        // Recurse with the rest if there are any
        if constexpr(sizeof...(Rest) > 0) {
          parse_data<idx + sizeof(First)>(data, rest...);
        }
    }
    

    与其他答案的折叠表达式 + 逗号运算符方法相比,唯一的缺点是它理论上会导致更深的堆栈和稍大的可执行文件大小。

    我说理论上是因为任何现代编译器几乎肯定会在非 0 优化级别上优化这些。因此,只有当您在具有严格内存限制的架构上以调试模式运行此代码时,这才是真正需要考虑的因素。

    您可以在-O1 中找到差异的演示和缺乏:https://gcc.godbolt.org/z/xbn35ecc8

    【讨论】:

      【解决方案3】:

      刚刚解决了同样的问题。使用串行通信与机器人一起工作。有很多很多的消息格式,所以我需要一种方法来将每条消息映射到消息中的类型。然后根据类型将消息uint8_t字节转换为类型。

      它使用tuple 类型列出消息中的预期类型。每种类型都会调整消息中的位置指针。该代码演示了在循环或单个片段中处理传入消息。

      它适用于 C++17 和 C++20。这是我的“让它工作的代码”,所以有一些一般的输出可以用来观察正在发生的事情。我对真实代码做了一些改进,但留给读者作为练习。

      直播版here.

      #include <iostream>
      #include <string>
      #include <string_view>
      #include <tuple>
      
      using Msg = std::basic_string<std::uint8_t>;
      //==========================================================================================================================================
      template <class... Ts>
      struct overloaded : Ts... {
          using Ts::operator( )...;
      };
      
      #if (_cplusplus != 202002L)    // check for C++17 or C++20
      // Deduction guide, google `CTAD for aggregates` for more info
      template <class... Ts>
      overloaded(Ts...) -> overloaded<Ts...>;    // not needed before C++20
      #endif
      
      auto emit = overloaded {
          [](const auto& b) {
              std::cout << "From auto: " << std::boolalpha << b << std::noboolalpha << '\n';
          },
      
          [](const char& b) {
              std::cout << "From char: ";
              if (std::isprint(b)) {
                  std::cout << std::showbase << b;
              } else if (b != 0) {
                  std::cout << '\\' << (uint16_t(b) & 0xFF);
              } else {
                  std::cout << '\\' << 0;    // showbase does not output 0x for zero
              }
              std::cout << '\n';
          },
      
          [](const uint8_t& b) {
              std::cout << "From uint8_t: " << std::showbase << (uint16_t)b << '\n';
          },
      
          [](const int8_t& a) {
              std::cout << "From int8_t: " << int16_t(a) << '\n';
          },
      
          [](const uint16_t& a) {
              std::cout << "From uint16_t: " << std::showbase << a << '\n';
          },
      };
      //----------------------------------------------------------------------------------------------------------------
      template <class... Ts>
      struct msg_proc : Ts... {
          using Ts::operator( )...;
      };
      
      #if (__cplusplus != 202002L)
      // Deduction guide, google `CTAD for aggregates` for more info
      template <class... Ts>
      msg_proc(Ts...) -> msg_proc<Ts...>;    // not needed from C++20
      
      #endif
      
      auto load = msg_proc {
          [](Msg const& msg, int16_t& pos, bool& value) {
              value = (msg[pos++] != 0);
          },
      
          [](Msg const& msg, int16_t& pos, char& value) {
              value = msg[pos++];
          },
      
          [](Msg const& msg, int16_t& pos, uint8_t& value) {
              value = msg[pos++];
          },
      
          [](Msg const& msg, int16_t& pos, int8_t& value) {
              value = msg[pos++];
          },
      
          [](Msg const& msg, int16_t& pos, int16_t& value) {
              value = (msg[pos] << 8) + msg[pos + 1];
              pos += 2;
          },
      
          [](Msg const& msg, int16_t& pos, uint16_t& value) {
              value = (msg[pos] << 8) + msg[pos + 1];
              pos += 2;
          },
      
      };
      //----------------------------------------------------------------------------------------------------------------
      template <typename... Ts>
      void emitter(std::tuple<Ts...> tup) {
          std::apply([](const auto&... e) {
              (emit(e), ...);
          },
              tup);
      }
      //----------------------------------------------------------------------------------------------------------------
      template <typename T>
      void emitter(T const& value) {
          emit(value);
      }
      //----------------------------------------------------------------------------------------------------------------
      template <typename... Ts>
      auto converter(Msg const& msg) {
          std::tuple<Ts...> tup { };
          int16_t pos { };
      
          std::apply(
              [&](auto&... e) {
                  (load(msg, pos, e), ...);
              },
              tup);
          std::cout << "pos " << pos << '\n';
          return tup;
      }
      //----------------------------------------------------------------------------------------------------------------
      int main( ) {
          Msg msg { 0x01, 1, 0x57, 0x58, 0x12, 0x34, 0xDE, 0xAD };
      
          auto& b_c = converter<bool, char, uint8_t, int8_t, int16_t, uint16_t>;
          auto res = b_c(msg);
      
          emitter(res);
          std::cout << '\n';
      
          emitter(std::get<0>(res));
          emitter(std::get<1>(res));
          emitter(std::get<2>(res));
          emitter(std::get<3>(res));
          std::cout << '\n';
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-04-23
        • 2016-12-01
        • 2013-10-09
        • 2015-06-29
        • 1970-01-01
        • 2017-01-08
        • 1970-01-01
        • 2020-10-29
        • 1970-01-01
        相关资源
        最近更新 更多