Как я могу построить последовательность структур с целочисленными членами возрастающего значения во время компиляции в C ++ 11 - PullRequest
0 голосов
/ 27 июня 2018

Работая во встроенной среде, я неоднократно пишу код, который берет массив байтов из уровня протокола и превращает эти байты в представление класса C ++.

Пример массива байтов, который представляет uint32_t, за которым следует uint8_t, а затем uint16_t, может выглядеть следующим образом.

std::array<uint8_t, 7> bytes(0x01, 0x02, 0x03, 0x04, 0x10, 0x20, 0x30);

Где 0x01020304 - мой uin32_t, 0x10 - мой uint8_t, а 0x2030 - мой uint16_t.

У меня также есть переменная функция func(), которую я хочу вызвать со значениями, проанализированными из полезной нагрузки.

Для этого я вручную определяю промежуточный объект:

// Declaring the Object
struct MY_TYPE
{
   uint32_t val1;
   uint8_t val2;
   uint16_t val3;
} __attribute__((__packed__));

// Processing the Bytes 
auto & object(reinterpret_cast<MY_TYPE *>(&bytes));

func(object.val1, object.val2, object.val3) 

То, что я хочу сделать, - это реализовать класс переменных, чтобы мне не нужно было повторно реализовывать MY_TYPE для каждой комбинации типов.

Вот что я изначально попробовал:

template <typename... Types>
struct GENERIC_CLASS
{
   template <typename ReturnType, std::size_t ArraySize>
   ReturnType getValueFromArray(std::array<uint8_t, ArraySize> const & array, 
                                uint32_t & index); 

   // Note, not valid c++ since the size of the array (N) isn't 
   // specified. This has been omitted for simplicity. 
   void process(std::array<uin8_t, N> const & array)
   {
      auto currentIndex(u0);

      // Assumes this class has a specialization 
      // for getValueFromArray for all of the types in Types. 

      // This code doesn't work because there is no sequence point 
      // between each call to getValueFromArray, so the 
      // currentIndex can be incremented in a non-deterministic way. 
      func(this->getValueFromArray<Types>(array, currentIndex)...);
   }
};

Мне удалось обойти эту проблему, введя новый класс:

template <typename T, std::size_t position>
struct Param
{
   using type = T;
   static constexpr std::size_t offset = position;
};

Таким образом, вместо сохранения currentIndex во время выполнения, я могу указать смещение каждого аргумента в коде, например:

GENERIC_CLASS<Param<uint32_t, 0>, Param<uint8_t, 4>, Param<uint16_t, 5>>

Вышеуказанное может привести к ошибкам, так как смещения могут быть неправильными. Есть ли какой-нибудь способ для генерации моей последовательности Params из набора типов параметров путем накопления размеров?

В качестве альтернативы, есть ли обходной путь для проблемы точки последовательности, о которой я упоминал выше?

Ответы [ 4 ]

0 голосов
/ 27 июня 2018

Предлагаю следующее решение

#include <array>
#include <tuple>
#include <iostream>

template <typename ... Ts>
class process
 {
   private:

      template <typename T>
      static T getVal (std::uint8_t const * a)
       {
         T ret { *a++ };

         for ( auto i = 1U ; i < sizeof(T) ; ++i )
          {
            ret <<= 8;
            ret  += *a++;
          }

         return ret;
       }

      static std::size_t postIncr (std::size_t & pos, std::size_t add)
       {
         std::size_t ret { pos };

         pos += add;

         return ret;
       }

   public: 
      template <std::size_t N>
      static std::tuple<Ts...> func (std::array<std::uint8_t, N> const & a)
       {
         std::size_t  pos { 0U };

         return { getVal<Ts>(a.data()+postIncr(pos, sizeof(Ts)))... };
       }
 };

int main ()
 { 
   std::array<std::uint8_t, 7U>
      bytes{{0x01U, 0x02U, 0x03U, 0x04U, 0x10U, 0x20U, 0x30U}};

   auto tpl
    { process<std::uint32_t, std::uint8_t, std::uint16_t>::func(bytes) };

   std::cout << "- 0x" << std::hex << std::get<0>(tpl) << std::endl;
   std::cout << "- 0x" << int(std::get<1>(tpl)) << std::endl;
   std::cout << "- 0x" << std::get<2>(tpl) << std::endl;
 }

Если вы получаете (как я получаю) раздражающее предупреждение от ret <<= 8; от getVal() (когда T равно std::uint8_t; предупреждение «число смещений> = ширина типа»), вы можете создать вторую getVal для std::uint8_t (без петли и без смещения), включение только при sizeof(T) == 1 и включение первого только при sizeof(T) > 1.

В противном случае вы можете заменить ret <<= 8; парой ret <<= 4;.

0 голосов
/ 27 июня 2018

Кажется, вы хотите что-то вроде:

template <typename ...Ts>
void f(Ts... args)
{
    const int dummy[] = {0, ((std::cout << std::hex << args << std::endl), 0)...};
    static_cast<void>(dummy); // Avoid warning for unused variable
}

template <std::size_t N, typename Tuple, std::size_t ...Is>
void process(std::array<std::uint8_t, N> const& array, Tuple tuple, std::index_sequence<Is...>)
{
    int i = 0;
    const int dummy[] = {((memcpy(&std::get<Is>(tuple), array.data() + i, sizeof(std::tuple_element_t<Is, Tuple>)), i += sizeof(std::tuple_element_t<Is, Tuple>)), 0)...};
    static_cast<void>(dummy); // Avoid warning for unused variable
    f(std::get<Is>(tuple)...);
}

template <std::size_t N, typename ... Ts>
void process(std::array<std::uint8_t, N> const& array, std::tuple<Ts...> tuple)
{
    process(array, tuple, std::index_sequence_for<Ts...>{});
}

index_sequence может быть реализовано на C ++ 11.

Демо

0 голосов
/ 27 июня 2018

Каждый класс нуждается в реализации as_tuple, возвращающей связь членов.

class Object
{
    // Private members
    uint32_t val1;
    uint8_t val2;
    uint16_t val3;
public:
    auto as_tuple()
    {
        return std::tie(val1, val2, val3);
    }
    // Use Object
    friend std::ostream& operator<<(std::ostream& os, Object& o)
    {
        return os << o.val1 << ' ' << int(o.val2) << ' ' << o.val3;
    }
};

Затем вы можете скопировать в каждого участника

namespace detail
{
    // To verify the size of the input array matches the sum of the member's sizes
    template <std::size_t...> struct sum;
    template <> struct sum<> { static constexpr std::size_t value = 0; };
    template <std::size_t I, std::size_t... Is> struct sum<I, Is...> { static constexpr std::size_t value = I + sum<Is...>::value; };

    template <std::size_t... Is> constexpr std::size_t sum_v = sum<Is...>::value;

    // Remove the front element of a tuple
    template<typename T, typename Seq>
    struct tuple_cdr_impl;

    template<typename T, std::size_t I0, std::size_t... I>
    struct tuple_cdr_impl<T, std::index_sequence<I0, I...>>
    {
        using type = std::tuple<typename std::tuple_element<I, T>::type...>;
    };

    template<typename T>
    struct tuple_cdr
    : tuple_cdr_impl<T, std::make_index_sequence<std::tuple_size<T>::value>>
    { };

    template<typename T>
    using tuple_cdr_t = typename tuple_cdr<T>::type;

    template<typename T, std::size_t I0, std::size_t... I>
    tuple_cdr_t<typename std::remove_reference_t<T>>
    cdr_impl(T&& t, std::index_sequence<I0, I...>)
    {
        return std::tie(std::get<I>(t)...);
    }

    template<typename T>
    tuple_cdr_t<typename std::remove_reference_t<T>>
    cdr(T&& t)
    {
        return cdr_impl(std::forward<T>(t),
                        std::make_index_sequence<std::tuple_size<typename std::remove_reference_t<T>>::value>{});
    }

    // copy from bytes into a reference
    void fromBytesImpl(const unsigned char *, std::tuple<>)
    {
    }

    template <typename T, typename... Ts>
    void fromBytesImpl(const unsigned char * arr, std::tuple<T&, Ts&...> tup)
    {
        auto dest = reinterpret_cast<unsigned char *>(&std::get<0>(tup));
        std::copy_n(arr, sizeof(T), dest);
        fromBytesImpl(arr + sizeof(T), cdr(tup));
    }
}

// Bringing it all together
template <typename... Ts>
void fromBytes(const std::array<unsigned char, detail::sum_v<sizeof(Ts)...>> & arr, std::tuple<Ts&...> tup)
{
    detail::fromBytesImpl(arr.data(), tup);
}

Это тогда просто в использовании

int main()
{
    Object obj;
    std::array<unsigned char, 7> bytes = {0x01, 0x02, 0x03, 0x04, 0x10, 0x20, 0x30};
    fromBytes(bytes, obj.as_tuple());

    std::cout << std::hex << obj << std::endl;
}

Посмотреть вживую

0 голосов
/ 27 июня 2018

Как на счет этого ...

#include <iostream>
#include <type_traits>
#include <cstring>
#include <typeinfo>

using namespace std;

template <size_t O,size_t I,size_t C,typename...>
struct byte_offset_impl;

template <size_t O,size_t I,size_t C,typename T,typename... Ts>
struct byte_offset_impl< O, I, C,T,Ts...> : 
    std::conditional<I == C,
      std::integral_constant<size_t,O>,
      byte_offset_impl<O + sizeof(T),I,C+1,Ts...>    
    >::type
{};


template <typename T>
struct type_c 
{ using type = T; };

template <size_t I,size_t C,typename...>
struct type_at_impl;

template <size_t I,size_t C,typename T,typename... Ts>
struct type_at_impl<I, C,T,Ts...> : 
    std::conditional<I == C,
      type_c<T>,
      type_at_impl<I,C+1,Ts...>  
    >::type
{};


template <size_t I,typename... Ts>
constexpr size_t byte_offset = byte_offset_impl<0,I,0,Ts...>::value;

template <size_t I,typename... Ts>
using type_at = typename type_at_impl<I,0,Ts...>::type;

template <typename...Ts>
struct generic_class
{
    generic_class(char* byteptr) : byteptr_(byteptr)
    {};


    template <size_t I>
    auto get() -> type_at<I, Ts...>& // needed for c++11
    {
        using result_type = type_at<I, Ts...>;
        return *reinterpret_cast<result_type*>(&byteptr_[byte_offset<I, Ts...>]);

    }

    template <size_t I>
    auto get_v() -> type_at<I, Ts...> // needed for c++11
    {
        using result_type = type_at<I, Ts...>;
        result_type result;

        std::memcpy(&result, &byteptr_[byte_offset<I, Ts...>], sizeof(result_type));

        return result;
    }
    char* byteptr_;
};


int main() {

    char bytes[sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint16_t)];

    uint32_t u32 = 1561;
    uint8_t u8 = 22;
    uint16_t u16 = 99;

    char* bytesp = bytes;
    std::memcpy(bytesp, &u32, sizeof(uint32_t)); bytesp += sizeof(uint32_t);
    std::memcpy(bytesp, &u8, sizeof(uint8_t)); bytesp += sizeof(uint8_t);
    std::memcpy(bytesp, &u16, sizeof(uint16_t));

    generic_class<uint32_t, uint8_t, uint16_t> gen(bytes);
    std::cout << (uint32_t)gen.get<0>() << std::endl;
    std::cout << (uint32_t)gen.get<1>() << std::endl;
    std::cout << (uint32_t)gen.get<2>() << std::endl;

    std::cout << (uint32_t)gen.get_v<0>() << std::endl;
    std::cout << (uint32_t)gen.get_v<1>() << std::endl;
    std::cout << (uint32_t)gen.get_v<2>() << std::endl;
}

Демо

В зависимости от вашей платформы реализация get<I> может оказаться неподходящей, поскольку доступ может быть не выровненным. альтернатива get_v<I> скопирует данные из вашего массива в элемент правильного типа с дополнительным преимуществом, которое будет копироваться только при обращении к данным.

...