通信では一般にchar配列として送受信が行われる.
ここで,パラメータはコマンドごとにことなるケースを想定している.
例えばCommand Aでは char, int, float[4]のバイト列を送り,
Command Bでは int, int, intというフォーマットになっているとしよう.
パラメータの型や数のよらず同じ関数でデータ→バイナリ配列,また,その逆をできるようにしたい.
任意の型が任意の数続くという仕様に対してC++11で採択された可変テンプレート引数(Vardiadic Template)を用いる.
ここで,パラメータはコマンドごとにことなるケースを想定している.
例えばCommand Aでは char, int, float[4]のバイト列を送り,
Command Bでは int, int, intというフォーマットになっているとしよう.
パラメータの型や数のよらず同じ関数でデータ→バイナリ配列,また,その逆をできるようにしたい.
struct CommandA
{
char data1;
int data2;
std::array<float, 4> data3;
};
struct CommandB
{
int data1;
int data2;
int data3;
};
void func()
{
// 目標とするインターフェイス
CommandA a = {'A', 1234, {0.1, 0.2, 0.3, 0.4}}
std::vector<char> binary_data_A = to_binary(a.data1, a.data2, a.data3, a.data4);
CommandB b = { 10, 100, 1000 };
std::vector<char> binary_data_B = to_binary(b.data1, b.data2, b.data3);
std::tuple<char, int, std::array<float, 4>> a2 = from_binary<char, int, std::array<float, 4>>(binary_data_A);
std::tuple<int, int, int> b2 = from_binary<int, int, int>(binary_data_B);
}
任意の型が任意の数続くという仕様に対してC++11で採択された可変テンプレート引数(Vardiadic Template)を用いる.
- 本クラスは構造体アラインメントを考慮していない.
- 本クラスは環境に依存したエンディアンとなる.
template<template <typename T, typename A = std::allocator<T>> class Container = std::vector>
class to_binary {
public:
typedef typename Container<char> container_type;
template <typename ... types>
static container_type convert(types ... others) {
to_binary converter;
converter.to_binary_impl(others...);
return converter.res;
}
private:
// ... の中身がなくなるまで,テンプレートパラメータパックを展開しながら再帰
template<typename T, typename ... types>
typename std::enable_if<(sizeof ...(types) > 0)>::type to_binary_impl(T val, types ... others) {
// 先頭の一つをTで受けて展開
char* pointer = reinterpret_cast<char*>(&val);
res.insert(res.end(), pointer, pointer + sizeof(T));
to_binary_impl(others...);
}
// SFINAEにより ... の中身が0になるとこちらに来る.最後の一個を展開して終了
template<typename T>
void to_binary_impl(T val) {
char* pointer = reinterpret_cast<char*>(&val);
res.insert(res.end(), pointer, pointer + sizeof(T));
}
container_type res;
};
// test
int main()
{
uint8_t a = 0x12;
uint16_t b = 0x3456;
std::array<uint32_t, 3> c = { 0x789abcde, 0xf0123456, 0xFFFFFFFF };
// value -> char array
auto binary = to_binary<>::convert(/* uint8_t */ a, /* uint16_t */ b, /* std::array<uint32_t, 3> */ c);
std::array<char, 1 + 2 + 12> expected = {
0x12,
0x56, 0x34,
0xde, 0xbc, 0x9a, 0x78,
0x56, 0x34, 0x12, 0xf0,
0xff, 0xff, 0xff, 0xff
};
assert(expected.size() == binary.size());
for (int i = 0; i < expected.size(); ++i)
{
assert(binary[i] == expected[i]);
}
return 0;
}
テンプレート引数で指定した任意の数の型を持つtupleにバイナリ配列を展開する.
template<template <typename T, typename A = std::allocator<T>> class Container = std::vector>
class from_binary
{
public:
typedef Container<char> container_type;
typedef typename container_type::const_iterator const_iterator;
template <typename ... types>
static std::tuple<types...> convert(container_type const& binary_data)
{
return exec<types...>(binary_data.begin());
}
private:
template <typename T, typename ... types>
static typename std::enable_if<(sizeof...(types) > 0), std::tuple<T, types...>>::type exec(const_iterator& first)
{
T val;
std::copy(first, first + sizeof(T), reinterpret_cast<char*>(&val));
first += sizeof(T);
return std::tuple_cat(std::tuple<T>(std::move(val)), exec<types...>(first));
}
template <typename T>
static std::tuple<T> exec(const_iterator& first)
{
T val;
std::copy(first, first + sizeof(T), reinterpret_cast<char*>(&val));
first += sizeof(T);
return std::tuple<T>(std::move(val));
}
};
// test
int main()
{
std::vector<char> binary{
0x12, 0x56, 0x34,(char)0xde, (char)0xbc, (char)0x9a, 0x78, 0x56, 0x34, 0x12, (char)0xf0, (char)0xff, (char)0xff, (char)0xff, (char)0xff
};
// char array -> value
auto tuple = from_binary<>::convert<uint8_t, uint16_t, std::array<uint32_t, 3>>(binary);
assert(std::get<0>(tuple) == 0x12);
assert(std::get<1>(tuple) == 0x3465);
assert(std::get<2>(tuple)[0] == 0x789abcde);
assert(std::get<2>(tuple)[1] == 0xf0123456);
assert(std::get<2>(tuple)[2] == 0xffffffff);
}

コメントをかく