I am currently writing this pack
template to pack all the values (raw / fundamental + arrays of such, especially c-strings and std::string
) as a helper for my rqueue
- record queue currently used for debug/trace log (with remote acces, future plan includes other variable-size records gathered from small devices - PLCs).
Interface - template<class... Elements> struct pack
static constexpr size_t count
: Number of stored elementstemplate<size_t I=0> using type
: Type of stored element at indexstatic constexpr bool trivial
: Trivial pack can safely be copied by memcpyT value
: First value (if not empty)pack<Next...> next
: Next values (if any)template<size_t I> type<I>& get()
: Get stored element at indextemplate<size_t I> const type<I>& get() const
: Get stored element at index (const version)size_t length(bool last = true) const
:
Length is the size in bytes of trivial/flat representation.
Designed to compute the storage space size to store all contained strings in continuous buffer
last
: query length without (true) or with (false)'\0'
terminator for stringssize_t flatten(void *dst, size_t max, bool last = true) const
:
Flatten will store the data in continous buffer.
return
: number of bytes used (compare it withlength()
to see if all the data was successfully stored)
dst
: destination buffer pointer
max
: size of destination buffer
last
: query length without (true) or with (false)'\0'
terminator for stringstemplate<typename Byte = byte, size_t N> size_t flatten(Byte (&dst)[N], bool last = true) const
: Flatten for fixed-size byte/char array
For Doxygen in code:
#ifdef FIRDA_DOXYGEN_INVOKED_
namespace firda {
/// Data Pack designed for direct data storage,
/// transfer and variable-sized records.
//:
/// Can embed fundamental types and arrays of fundamental types,
/// especially c-strings (`char[N]`).
/// Supports `std::string` as well, but `trivial` will be `false`
/// and `flatten()` has to be used for continuous storage.
template<class... Elements> struct pack
{
/// Number of stored elements
static constexpr size_t count;
/// Type of stored element at index
template<size_t I=0> using type;
/// Trivial pack can safely be copied by memcpy
static constexpr bool trivial;
/// First value (if not empty)
T value;
/// Next values (if any)
pack<Next...> next;
/// Get stored element at index
template<size_t I> type<I>& get();
/// Get stored element at index (const version)
template<size_t I> const type<I>& get() const;
/// Length is the size in bytes of trivial/flat representation.
//:
/// Designed to compute the storage space size
/// to store all contained strings in continuous buffer
///\param last query length without (true) or with (false)
/// \c '\0' terminator for strings
size_t length(bool last = true) const;
/// Flatten will store the data in continous buffer
//:
///\return number of bytes used (compare it with length()
/// to see if all the data was successfully stored)
///\param dst destination buffer pointer
///\param max size of destination buffer
///\param last query length without (true) or with (false)
/// \c '\0' terminator for strings
size_t flatten(void *dst, size_t max, bool last = true) const;
/// Flatten for fixed-size byte/char array
///\see flatten(void*,size_t,bool)
template<typename Byte = byte, size_t N>
size_t flatten(Byte (&dst)[N], bool last = true) const;
};
}
#else
Important doxygen config settings:
PREDEFINED = FIRDA_DOXYGEN_INVOKED_ MULTILINE_CPP_IS_BRIEF = YES
IdeOne Example
// c-string vs. c++string
auto hello = make_pack("hello");
auto world = make_pack(" world!"_s);
cout << sizeof(hello) << '/' << hello.length()
<< '+' << sizeof(world) << '/' << world.length()
<< ": " << hello << world << endl;
// flattening test
auto again = make_pack("hello again :)"_s);
char aflat[again.length(false)];
again.flatten(aflat, sizeof(aflat), false);
cout << again << endl;
cout << aflat << endl;
// complex flattening test
auto pack = make_pack("pi", ' ', "= "_s, 3.14159f);
assert(pack.length() == 11);
char flat[12];
pack.flatten(flat, false);
cout << sizeof(pack) << ": " << pack << endl;
cout << sizeof(flat) << ": " << flat << flat[3] << flat+4
<< pack.get<3>() << " (" << *(float*)(flat+7) << ")" << endl;
for (auto b : flat) cout << hex << setfill('0') << setw(2) << (word)b;
cout << dec << endl;
Output:
6/6+4/7: hello world! hello again :) hello again :) 12: pi = 3.14159 12: pi = 3.14159 (3.14159) 706900203d2000ffd00f494008
The Code
//######################################################## detail forward
namespace firda { namespace detail_ { namespace pack {
template<class...> struct data;
}}}
//################################################################## pack
namespace firda {
template<class... T> class pack
: public detail_::pack::data<T...>
{
typedef detail_::pack::data<T...> base;
public:
using base::base;
using base::count;
using base::trivial;
using base::length;
using base::flatten;
template<typename Byte = byte, size_t N> enable_if_t<
sizeof(Byte) == 1 && is_integral<Byte>::value,
size_t> flatten(Byte (&dst)[N], bool last = true) const
{
return flatten(dst, N, last);
}
};
}
#endif
//############################################################# make_pack
namespace firda {
/// Create pack<...> from arguments
template<class... Args> inline
pack<remove_cref_t<Args>...>
make_pack(Args&&... args)
{
return pack<remove_cref_t<Args>...>(
forward<Args>(args)...);
}
//================================================================ output
/// No output for empty pack
inline ostream& operator << (ostream& s, const pack<>&)
{
return s;
}
/// Output one packed value
template<class T> inline ostream&
operator << (ostream& s, const pack<T>& p)
{
return s << p.value;
}
/// Output more packed values
template<class T, class... N> inline ostream&
operator << (ostream& s, const pack<T,N...>& p)
{
return s << p.value << p.next;
}
}
//################################################################ detail
#ifndef FIRDA_DOXYGEN_INVOKED_
namespace firda { namespace detail_ { namespace pack {
#pragma pack(push,1)
// empty data + fallback ------------------------------------------------
template<class... E> struct data
{
static_assert(sizeof...(E) == 0, "Unmatched argument data");
static constexpr size_t count = 0;
static constexpr bool trivial = true;
size_t length(bool=true)
{
return 0;
}
size_t flatten(void *dst, size_t max, bool=true)
{
return 0;
}
protected:
~data() = default;
};
// trivial element specialization ---------------------------------------
template<class T> struct data<T>
{
static constexpr size_t count = 1;
template<size_t I=0> using type = enable_if_t<I==0,T>;
static constexpr bool trivial = is_trivial<T>::value;
static_assert(trivial, "Non-trivial element");
T value;
data() = default;
data(const T& value): value(value) {}
data(T&& value): value(forward<T>(value)) {}
template<size_t I> type<I>& get()
{
static_assert(I == 0, "Index out of range");
return value;
}
template<size_t I> const type<I>& get() const
{
static_assert(I == 0, "Index out of range");
return value;
}
size_t length(bool=true) const
{
return sizeof(value);
}
size_t flatten(void *dst, size_t max, bool=true) const
{
size_t sz = sizeof(value);
if (sz > max) return 0;
memcpy(dst, &value, sz);
return sz;
}
protected:
~data() = default;
};
// array specialization -------------------------------------------------
template<class T, size_t N> struct data<T[N]>
{
static constexpr size_t count = 1;
template<size_t I=0> using type = enable_if_t<I==0,T[N]>;
static constexpr bool trivial = is_trivial<T>::value;
static_assert(trivial, "Non-trivial array");
T value[N];
data() = default;
data(const T value[N])
{
copy(value, value+N, this->value);
}
template<size_t I> type<I>& get()
{
static_assert(I == 0, "Index out of range");
return value;
}
template<size_t I> const type<I>& get() const
{
static_assert(I == 0, "Index out of range");
return value;
}
size_t length(bool=true) const
{
return sizeof(value);
}
size_t flatten(void *dst, size_t max, bool=true) const
{
size_t sz = sizeof(value);
if (sz > max) return 0;
memcpy(dst, value, sz);
return sz;
}
protected:
~data() = default;
};
// string specialization ------------------------------------------------
template<> struct data<string>
{
static constexpr size_t count = 1;
template<size_t I=0> using type = enable_if_t<I==0,string>;
static constexpr bool trivial = false;
string value;
data() = default;
data(const string& value): value(value) {}
data(string&& value): value(forward<string>(value)) {}
template<size_t I> type<I>& get()
{
static_assert(I == 0, "Index out of range");
return value;
}
template<size_t I> const type<I>& get() const
{
static_assert(I == 0, "Index out of range");
return value;
}
size_t length(bool last=true) const
{
return value.length() + (size_t)!last;
}
size_t flatten(void *dst, size_t max, bool last=true) const
{
size_t sz = value.length() + (size_t)!last;
if (sz > max) return 0;
memcpy(dst, value.c_str(), sz);
return sz;
}
protected:
~data() = default;
};
// recursive specialization ---------------------------------------------
template<class T, class... Next> struct data<T, Next...> : data<T>
{
static constexpr size_t count = 1 + sizeof...(Next);
template<size_t I=0> using type = conditional_t<I==0,
T, typename data<Next...>::template type<I==0?0:I-1>>;
static constexpr size_t trivial
= data<T>::trivial && data<Next...>::trivial;
using data<T>::value;
firda::pack<Next...> next;
data() = default;
template<class First, class... Args>
data(First&& value, Args&&... args)
: data<T>(forward<First>(value))
, next(forward<Args>(args)...) {}
using data<T>::get;
template<size_t I> enable_if_t<I!=0, type<I>&> get()
{
static_assert(I < count, "Index out of range");
return next.template get<I-1>();
}
template<size_t I> enable_if_t<I!=0, const type<I>&> get() const
{
static_assert(I < count, "Index out of range");
return next.template get<I-1>();
}
size_t length(bool last=true) const
{
return data<T>::length(false) + next.length(last);
}
size_t flatten(void *dst, size_t max, bool last=true) const
{
size_t sz = data<T>::flatten(dst, max, false);
return sz == 0 ? 0 : next
.flatten(((byte*)dst) + sz, max - sz, last);
}
protected:
~data() = default;
};
#pragma pack(pop)
}}}
#endif