text: /**
   *  @file datastream.hpp
   *  @copyright defined in eos/LICENSE
   */
  #pragma once
  #include "check.hpp"
  #include "varint.hpp"
  
  #include <list>
  #include <queue>
  #include <vector>
  #include <array>
  #include <set>
  #include <map>
  #include <string>
  #include <optional>
  #include <variant>
  
  #include <boost/fusion/algorithm/iteration/for_each.hpp>
  #include <boost/fusion/include/for_each.hpp>
  #include <boost/fusion/adapted/std_tuple.hpp>
  #include <boost/fusion/include/std_tuple.hpp>
  
  #include <boost/mp11/tuple.hpp>
  #include <boost/pfr.hpp>
  
  namespace eosio {
  
  /**
   * @defgroup datastream Data Stream
   * @ingroup core
   * @brief Defines data stream for reading and writing data in the form of bytes
   */
  
  /**
   *  A data stream for reading and writing data in the form of bytes
   *
   *  @tparam T - Type of the datastream buffer
   */
  template<typename T>
  class datastream {
     public:
        /**
         * Construct a new datastream object
         *
         * @details Construct a new datastream object given the size of the buffer and start position of the buffer
         * @param start - The start position of the buffer
         * @param s - The size of the buffer
         */
        datastream( T start, size_t s )
        :_start(start),_pos(start),_end(start+s){}
  
       /**
        *  Skips a specified number of bytes from this stream
        *
        *  @param s - The number of bytes to skip
        */
        inline void skip( size_t s ){ _pos += s; }
  
       /**
        *  Reads a specified number of bytes from the stream into a buffer
        *
        *  @param d - The pointer to the destination buffer
        *  @param s - the number of bytes to read
        *  @return true
        */
        inline bool read( char* d, size_t s ) {
          eosio::check( size_t(_end - _pos) >= (size_t)s, "read" );
          memcpy( d, _pos, s );
          _pos += s;
          return true;
        }
  
       /**
        *  Writes a specified number of bytes into the stream from a buffer
        *
        *  @param d - The pointer to the source buffer
        *  @param s - The number of bytes to write
        *  @return true
        */
        inline bool write( const char* d, size_t s ) {
          eosio::check( _end - _pos >= (int32_t)s, "write" );
          memcpy( (void*)_pos, d, s );
          _pos += s;
          return true;
        }
  
       /**
        *  Writes a byte into the stream
        *
        *  @brief Writes a byte into the stream
        *  @param c byte to write
        *  @return true
        */
        inline bool put(char c) {
          eosio::check( _pos < _end, "put" );
          *_pos = c;
          ++_pos;
          return true;
        }
  
       /**
        *  Reads a byte from the stream
        *
        *  @brief Reads a byte from the stream
        *  @param c - The reference to destination byte
        *  @return true
        */
        inline bool get( unsigned char& c ) { return get( *(char*)&c ); }
  
       /**
        *  Reads a byte from the stream
        *
        *  @brief Reads a byte from the stream
        *  @param c - The reference to destination byte
        *  @return true
        */
        inline bool get( char& c )
        {
          eosio::check( _pos < _end, "get" );
          c = *_pos;
          ++_pos;
          return true;
        }
  
       /**
        *  Retrieves the current position of the stream
        *
        *  @brief Retrieves the current position of the stream
        *  @return T - The current position of the stream
        */
        T pos()const { return _pos; }
        inline bool valid()const { return _pos <= _end && _pos >= _start;  }
  
       /**
        *  Sets the position within the current stream
        *
        *  @brief Sets the position within the current stream
        *  @param p - The offset relative to the origin
        *  @return true if p is within the range
        *  @return false if p is not within the rawnge
        */
        inline bool seekp(size_t p) { _pos = _start + p; return _pos <= _end; }
  
       /**
        *  Gets the position within the current stream
        *
        *  @brief Gets the position within the current stream
        *  @return p - The position within the current stream
        */
        inline size_t tellp()const      { return size_t(_pos - _start); }
  
       /**
        *  Returns the number of remaining bytes that can be read/skipped
        *
        *  @brief Returns the number of remaining bytes that can be read/skipped
        *  @return size_t - The number of remaining bytes
        */
        inline size_t remaining()const  { return _end - _pos; }
      private:
        /**
         * The start position of the buffer
         *
         * @brief The start position of the buffer
         */
        T _start;
        /**
         * The current position of the buffer
         *
         * @brief The current position of the buffer
         */
        T _pos;
        /**
         * The end position of the buffer
         *
         * @brief The end position of the buffer
         */
        T _end;
  };
  
  /**
   * Specialization of datastream used to help determine the final size of a serialized value
   */
  template<>
  class datastream<size_t> {
     public:
        /**
         * Construct a new specialized datastream object given the initial size
         *
         * @param init_size - The initial size
         */
       datastream( size_t init_size = 0):_size(init_size){}
  
       /**
        *  Increment the size by s. This behaves the same as write( const char* ,size_t s ).
        *
        *  @param s - The amount of size to increase
        *  @return true
        */
       inline bool     skip( size_t s )                 { _size += s; return true;  }
  
       /**
        *  Increment the size by s. This behaves the same as skip( size_t s )
        *
        *  @param s - The amount of size to increase
        *  @return true
        */
       inline bool     write( const char* ,size_t s )  { _size += s; return true;  }
  
       /**
        *  Increment the size by one
        *
        *  @return true
        */
       inline bool     put(char )                      { ++_size; return  true;    }
  
       /**
        *  Check validity. It's always valid
        *
        *  @return true
        */
       inline bool     valid()const                     { return true;              }
  
       /**
        * Set new size
        *
        * @brief Set new size
        * @param p - The new size
        * @return true
        */
       inline bool     seekp(size_t p)                  { _size = p;  return true;  }
  
       /**
        * Get the size
        *
        * @return size_t - The size
        */
       inline size_t   tellp()const                     { return _size;             }
  
       /**
        * Always returns 0
        *
        * @return size_t - 0
        */
       inline size_t   remaining()const                 { return 0;                 }
    private:
       /**
        * The size used to determine the final size of a serialized value.
        */
       size_t _size;
  };
  
  /**
   *  Serialize an std::list into a stream
   *
   *  @param ds - The stream to write
   *  @param opt - The value to serialize
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator<<(datastream<Stream>& ds, const std::list<T>& l) {
     ds << unsigned_int( l.size() );
     for ( const auto& elem : l )
        ds << elem;
    return ds;
  }
  
  /**
   *  Deserialize an std::list from a stream
   *
   *  @param ds - The stream to read
   *  @param opt - The destination for deserialized value
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator>>(datastream<Stream>& ds, std::list<T>& l) {
     unsigned_int s;
     ds >> s;
     l.resize(s.value);
     for( auto& i : l )
        ds >> i;
     return ds;
  }
  
  /**
   *  Serialize an std::deque into a stream
   *
   *  @param ds - The stream to write
   *  @param opt - The value to serialize
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator<<(datastream<Stream>& ds, const std::deque<T>& d) {
     ds << unsigned_int( d.size() );
     for ( const auto& elem : d )
        ds << elem;
    return ds;
  }
  
  /**
   *  Deserialize an std::deque from a stream
   *
   *  @param ds - The stream to read
   *  @param opt - The destination for deserialized value
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator>>(datastream<Stream>& ds, std::deque<T>& d) {
     unsigned_int s;
     ds >> s;
     d.resize(s.value);
     for( auto& i : d )
        ds >> i;
     return ds;
  }
  
  /**
   *  Serialize an std::variant into a stream
   *
   *  @param ds - The stream to write
   *  @param opt - The value to serialize
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename... Ts>
  inline datastream<Stream>& operator<<(datastream<Stream>& ds, const std::variant<Ts...>& var) {
    unsigned_int index = var.index();
    ds << index;
    std::visit([&ds](auto& val){ ds << val; }, var);
    return ds;
  }
  
  template<int I, typename Stream, typename... Ts>
  void deserialize(datastream<Stream>& ds, std::variant<Ts...>& var, int i) {
     if constexpr (I < std::variant_size_v<std::variant<Ts...>>) {
        if (i == I) {
           std::variant_alternative_t<I, std::variant<Ts...>> tmp;
           ds >> tmp;
           var = std::move(tmp);
        } else {
           deserialize<I+1>(ds,var,i);
        }
     } else {
        eosio::check(false, "invalid variant index");
     }
  }
  
  /**
   *  Deserialize an std::variant from a stream
   *
   *  @param ds - The stream to read
   *  @param opt - The destination for deserialized value
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename... Ts>
  inline datastream<Stream>& operator>>(datastream<Stream>& ds, std::variant<Ts...>& var) {
    unsigned_int index;
    ds >> index;
    deserialize<0>(ds,var,index);
    return ds;
  }
  
  /**
   *  Serialize an std::pair
   *
   *  @param ds - The stream to write
   *  @param t - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam Args - Type of the objects contained in the tuple
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T1, typename T2>
  DataStream& operator<<( DataStream& ds, const std::pair<T1, T2>& t ) {
     ds << std::get<0>(t);
     ds << std::get<1>(t);
     return ds;
  }
  
  /**
   *  Deserialize an std::pair
   *
   *  @param ds - The stream to read
   *  @param t - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam Args - Type of the objects contained in the tuple
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T1, typename T2>
  DataStream& operator>>( DataStream& ds, std::pair<T1, T2>& t ) {
     T1 t1;
     T2 t2;
     ds >> t1;
     ds >> t2;
     t = std::pair<T1, T2>{t1, t2};
     return ds;
  }
  
  /**
   *  Serialize an optional into a stream
   *
   *  @param ds - The stream to write
   *  @param opt - The value to serialize
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator<<(datastream<Stream>& ds, const std::optional<T>& opt) {
    char valid = opt.has_value();
    ds << valid;
    if (valid)
       ds << *opt;
    return ds;
  }
  
  /**
   *  Deserialize an optional from a stream
   *
   *  @param ds - The stream to read
   *  @param opt - The destination for deserialized value
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream, typename T>
  inline datastream<Stream>& operator>>(datastream<Stream>& ds, std::optional<T>& opt) {
    char valid = 0;
    ds >> valid;
    if (valid) {
       T val;
       ds >> val;
       opt = val;
    }
    return ds;
  }
  
  
  /**
   *  Serialize a bool into a stream
   *
   *  @param ds - The stream to read
   *  @param d - The value to serialize
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream>
  inline datastream<Stream>& operator<<(datastream<Stream>& ds, const bool& d) {
    return ds << uint8_t(d);
  }
  
  /**
   *  Deserialize a bool from a stream
   *
   *  @brief Deserialize a bool
   *  @param ds - The stream to read
   *  @param d - The destination for deserialized value
   *  @tparam Stream - Type of datastream buffer
   *  @return datastream<Stream>& - Reference to the datastream
   */
  template<typename Stream>
  inline datastream<Stream>& operator>>(datastream<Stream>& ds, bool& d) {
    uint8_t t;
    ds >> t;
    d = t;
    return ds;
  }
  
  /**
   *  Serialize a string into a stream
   *
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream>
  DataStream& operator << ( DataStream& ds, const std::string& v ) {
     ds << unsigned_int( v.size() );
     if (v.size())
        ds.write(v.data(), v.size());
     return ds;
  }
  
  /**
   *  Deserialize a string from a stream
   *
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream>
  DataStream& operator >> ( DataStream& ds, std::string& v ) {
     std::vector<char> tmp;
     ds >> tmp;
     if( tmp.size() )
        v = std::string(tmp.data(),tmp.data()+tmp.size());
     else
        v = std::string();
     return ds;
  }
  
  /**
   *  Serialize a fixed size std::array
   *
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the array
   *  @tparam N - Size of the array
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N>
  DataStream& operator << ( DataStream& ds, const std::array<T,N>& v ) {
     for( const auto& i : v )
        ds << i;
     return ds;
  }
  
  
  /**
   *  Deserialize a fixed size std::array
   *
   *  @brief Deserialize a fixed size std::array
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the array
   *  @tparam N - Size of the array
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N>
  DataStream& operator >> ( DataStream& ds, std::array<T,N>& v ) {
     for( auto& i : v )
        ds >> i;
     return ds;
  }
  
  namespace _datastream_detail {
     /**
      * Check if type T is a pointer
      *
      * @brief Check if type T is a pointer
      * @tparam T - The type to be checked
      * @return true if T is a pointer
      * @return false otherwise
      */
     template<typename T>
     constexpr bool is_pointer() {
        return std::is_pointer<T>::value ||
               std::is_null_pointer<T>::value ||
               std::is_member_pointer<T>::value;
     }
  
     /**
      * Check if type T is a primitive type
      *
      * @brief Check if type T is a primitive type
      * @tparam T - The type to be checked
      * @return true if T is a primitive type
      * @return false otherwise
      */
     template<typename T>
     constexpr bool is_primitive() {
        return std::is_arithmetic<T>::value ||
               std::is_enum<T>::value;
     }
  }
  
  /**
   *  Pointer should not be serialized, so this function will always throws an error
   *
   *  @brief Deserialize a a pointer
   *  @param ds - The stream to read
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the pointer
   *  @return DataStream& - Reference to the datastream
   *  @post Throw an exception if it is a pointer
   */
  template<typename DataStream, typename T, std::enable_if_t<_datastream_detail::is_pointer<T>()>* = nullptr>
  DataStream& operator >> ( DataStream& ds, T ) {
     static_assert(!_datastream_detail::is_pointer<T>(), "Pointers should not be serialized" );
     return ds;
  }
  
  /**
   *  Serialize a fixed size C array of non-primitive and non-pointer type
   *
   *  @brief Serialize a fixed size C array of non-primitive and non-pointer type
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the pointer
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N,
           std::enable_if_t<!_datastream_detail::is_primitive<T>() &&
                            !_datastream_detail::is_pointer<T>()>* = nullptr>
  DataStream& operator << ( DataStream& ds, const T (&v)[N] ) {
     ds << unsigned_int( N );
     for( uint32_t i = 0; i < N; ++i )
        ds << v[i];
     return ds;
  }
  
  /**
   *  Serialize a fixed size C array of primitive type
   *
   *  @brief Serialize a fixed size C array of primitive type
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the pointer
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N,
           std::enable_if_t<_datastream_detail::is_primitive<T>()>* = nullptr>
  DataStream& operator << ( DataStream& ds, const T (&v)[N] ) {
     ds << unsigned_int( N );
     ds.write((char*)&v[0], sizeof(v));
     return ds;
  }
  
  /**
   *  Deserialize a fixed size C array of non-primitive and non-pointer type
   *
   *  @brief Deserialize a fixed size C array of non-primitive and non-pointer type
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam T - Type of the object contained in the array
   *  @tparam N - Size of the array
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N,
           std::enable_if_t<!_datastream_detail::is_primitive<T>() &&
                            !_datastream_detail::is_pointer<T>()>* = nullptr>
  DataStream& operator >> ( DataStream& ds, T (&v)[N] ) {
     unsigned_int s;
     ds >> s;
     eosio::check( N == s.value, "T[] size and unpacked size don't match");
     for( uint32_t i = 0; i < N; ++i )
        ds >> v[i];
     return ds;
  }
  
  /**
   *  Deserialize a fixed size C array of primitive type
   *
   *  @brief Deserialize a fixed size C array of primitive type
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam T - Type of the object contained in the array
   *  @tparam N - Size of the array
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::size_t N,
           std::enable_if_t<_datastream_detail::is_primitive<T>()>* = nullptr>
  DataStream& operator >> ( DataStream& ds, T (&v)[N] ) {
     unsigned_int s;
     ds >> s;
     eosio::check( N == s.value, "T[] size and unpacked size don't match");
     ds.read((char*)&v[0], sizeof(v));
     return ds;
  }
  
  /**
   *  Serialize a vector of char
   *
   *  @brief Serialize a vector of char
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream>
  DataStream& operator << ( DataStream& ds, const std::vector<char>& v ) {
     ds << unsigned_int( v.size() );
     ds.write( v.data(), v.size() );
     return ds;
  }
  
  /**
   *  Serialize a vector
   *
   *  @brief Serialize a vector
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the vector
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T>
  DataStream& operator << ( DataStream& ds, const std::vector<T>& v ) {
     ds << unsigned_int( v.size() );
     for( const auto& i : v )
        ds << i;
     return ds;
  }
  
  /**
   *  Deserialize a vector of char
   *
   *  @brief Deserialize a vector of char
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream>
  DataStream& operator >> ( DataStream& ds, std::vector<char>& v ) {
     unsigned_int s;
     ds >> s;
     v.resize( s.value );
     ds.read( v.data(), v.size() );
     return ds;
  }
  
  /**
   *  Deserialize a vector
   *
   *  @brief Deserialize a vector
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the vector
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T>
  DataStream& operator >> ( DataStream& ds, std::vector<T>& v ) {
     unsigned_int s;
     ds >> s;
     v.resize(s.value);
     for( auto& i : v )
        ds >> i;
     return ds;
  }
  
  /**
   *  Serialize a set
   *
   *  @brief Serialize a set
   *  @param ds - The stream to write
   *  @param s - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the set
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T>
  DataStream& operator << ( DataStream& ds, const std::set<T>& s ) {
     ds << unsigned_int( s.size() );
     for( const auto& i : s ) {
        ds << i;
     }
     return ds;
  }
  
  
  /**
   *  Deserialize a set
   *
   *  @brief Deserialize a set
   *  @param ds - The stream to read
   *  @param s - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the object contained in the set
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T>
  DataStream& operator >> ( DataStream& ds, std::set<T>& s ) {
     s.clear();
     unsigned_int sz; ds >> sz;
  
     for( uint32_t i = 0; i < sz.value; ++i ) {
        T v;
        ds >> v;
        s.emplace( std::move(v) );
     }
     return ds;
  }
  
  /**
   *  Serialize a map
   *
   *  @brief Serialize a map
   *  @param ds - The stream to write
   *  @param m - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam K - Type of the key contained in the map
   *  @tparam V - Type of the value contained in the map
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename K, typename V>
  DataStream& operator << ( DataStream& ds, const std::map<K,V>& m ) {
     ds << unsigned_int( m.size() );
     for( const auto& i : m ) {
        ds << i.first << i.second;
     }
     return ds;
  }
  
  /**
   *  Deserialize a map
   *
   *  @brief Deserialize a map
   *  @param ds - The stream to read
   *  @param m - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam K - Type of the key contained in the map
   *  @tparam V - Type of the value contained in the map
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename K, typename V>
  DataStream& operator >> ( DataStream& ds, std::map<K,V>& m ) {
     m.clear();
     unsigned_int s; ds >> s;
  
     for (uint32_t i = 0; i < s.value; ++i) {
        K k; V v;
        ds >> k >> v;
        m.emplace( std::move(k), std::move(v) );
     }
     return ds;
  }
  
  /**
   *  Serialize a tuple
   *
   *  @brief Serialize a tuple
   *  @param ds - The stream to write
   *  @param t - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam Args - Type of the objects contained in the tuple
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename... Args>
  DataStream& operator<<( DataStream& ds, const std::tuple<Args...>& t ) {
     boost::fusion::for_each( t, [&]( const auto& i ) {
         ds << i;
     });
     return ds;
  }
  
  /**
   *  Deserialize a tuple
   *
   *  @brief Deserialize a tuple
   *  @param ds - The stream to read
   *  @param t - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam Args - Type of the objects contained in the tuple
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename... Args>
  DataStream& operator>>( DataStream& ds, std::tuple<Args...>& t ) {
     boost::fusion::for_each( t, [&]( auto& i ) {
         ds >> i;
     });
     return ds;
  }
  
  /**
   *  Serialize a class
   *
   *  @brief Serialize a class
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of class
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::enable_if_t<std::is_class<T>::value>* = nullptr>
  DataStream& operator<<( DataStream& ds, const T& v ) {
     boost::pfr::for_each_field(v, [&](const auto& field) {
        ds << field;
     });
     return ds;
  }
  
  /**
   *  Deserialize a class
   *
   *  @brief Deserialize a class
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of class
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::enable_if_t<std::is_class<T>::value>* = nullptr>
  DataStream& operator>>( DataStream& ds, T& v ) {
     boost::pfr::for_each_field(v, [&](auto& field) {
        ds >> field;
     });
     return ds;
  }
  
  /**
   *  Serialize a primitive type
   *
   *  @brief Serialize a primitive type
   *  @param ds - The stream to write
   *  @param v - The value to serialize
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the primitive type
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::enable_if_t<_datastream_detail::is_primitive<T>()>* = nullptr>
  DataStream& operator<<( DataStream& ds, const T& v ) {
     ds.write( (const char*)&v, sizeof(T) );
     return ds;
  }
  
  /**
   *  Deserialize a primitive type
   *
   *  @brief Deserialize a primitive type
   *  @param ds - The stream to read
   *  @param v - The destination for deserialized value
   *  @tparam DataStream - Type of datastream
   *  @tparam T - Type of the primitive type
   *  @return DataStream& - Reference to the datastream
   */
  template<typename DataStream, typename T, std::enable_if_t<_datastream_detail::is_primitive<T>()>* = nullptr>
  DataStream& operator>>( DataStream& ds, T& v ) {
     ds.read( (char*)&v, sizeof(T) );
     return ds;
  }
  
  /**
   * Unpack data inside a fixed size buffer as T
   *
   * @ingroup datastream
   * @brief Unpack data inside a fixed size buffer as T
   * @tparam T - Type of the unpacked data
   * @param buffer - Pointer to the buffer
   * @param len - Length of the buffer
   * @return T - The unpacked data
   */
  template<typename T>
  T unpack( const char* buffer, size_t len ) {
     T result;
     datastream<const char*> ds(buffer,len);
     ds >> result;
     return result;
  }
  
  /**
   * Unpack data inside a variable size buffer as T
   *
   * @ingroup datastream
   * @brief Unpack data inside a variable size buffer as T
   * @tparam T - Type of the unpacked data
   * @param bytes - Buffer
   * @return T - The unpacked data
   */
  template<typename T>
  T unpack( const std::vector<char>& bytes ) {
     return unpack<T>( bytes.data(), bytes.size() );
  }
  
  /**
   * Get the size of the packed data
   *
   * @ingroup datastream
   * @brief Get the size of the packed data
   * @tparam T - Type of the data to be packed
   * @param value - Data to be packed
   * @return size_t - Size of the packed data
   */
  template<typename T>
  size_t pack_size( const T& value ) {
    datastream<size_t> ps;
    ps << value;
    return ps.tellp();
  }
  
  /**
   * Get packed data
   *
   * @ingroup datastream
   * @brief Get packed data
   * @tparam T - Type of the data to be packed
   * @param value - Data to be packed
   * @return bytes - The packed data
   */
  template<typename T>
  std::vector<char> pack( const T& value ) {
    std::vector<char> result;
    result.resize(pack_size(value));
  
    datastream<char*> ds( result.data(), result.size() );
    ds << value;
    return result;
  }
  }
  