aboutsummaryrefslogtreecommitdiff
path: root/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
diff options
context:
space:
mode:
authorAdrian Kummerlaender2014-08-10 13:09:42 +0200
committerAdrian Kummerlaender2014-08-10 13:09:42 +0200
commitf4043ec1e9f02efc7f653274b9e4e22ef2782a51 (patch)
tree7094d038dd30eef441b6895a2ac99daf5720e7d4 /articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
parent8d7f2c536f3c48d17b1de244fe6713f4d4499fd5 (diff)
downloadblog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar.gz
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar.bz2
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar.lz
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar.xz
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.tar.zst
blog_content-f4043ec1e9f02efc7f653274b9e4e22ef2782a51.zip
Switched content formatter to kramdown
* implemented language selection for automatic syntax highlighting ** language selection requires the language to be used to be passed as a class of the code element ** kramdown enables easy definition of this class attribute * kramdown offers more functionality such as table and class attribute support * updated all articles accordingly
Diffstat (limited to 'articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md')
-rw-r--r--articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md279
1 files changed, 150 insertions, 129 deletions
diff --git a/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md b/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
index 24c000e..a3af421 100644
--- a/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
+++ b/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
@@ -17,30 +17,33 @@ differences in endianness and In-place modification of the structure fields.
To be able to easily work with structure definitions using template metaprogramming I am relying on the standard libraries [_std::tuple_](http://en.cppreference.com/w/cpp/utility/tuple)
template.
- template<typename Tuple>
- class BinaryMapping {
- public:
- BinaryMapping(uint8_t* const buffer):
- buffer_(buffer) {
- TupleReader::read(this->tuple_, buffer);
- }
-
- template <size_t Index>
- decltype(*std::get<Index>(Tuple{})) get() {
- return *std::get<Index>(this->tuple_);
- }
-
- template <size_t Index,
- typename Value = decltype(*std::get<Index>(Tuple{}))>
- void set(const Value value) {
- *std::get<Index>(this->tuple_) = value;
- }
-
- private:
- uint8_t* const buffer_;
- Tuple tuple_;
-
- };
+~~~
+template<typename Tuple>
+class BinaryMapping {
+ public:
+ BinaryMapping(uint8_t* const buffer):
+ buffer_(buffer) {
+ TupleReader::read(this->tuple_, buffer);
+ }
+
+ template <size_t Index>
+ decltype(*std::get<Index>(Tuple{})) get() {
+ return *std::get<Index>(this->tuple_);
+ }
+
+ template <size_t Index,
+ typename Value = decltype(*std::get<Index>(Tuple{}))>
+ void set(const Value value) {
+ *std::get<Index>(this->tuple_) = value;
+ }
+
+ private:
+ uint8_t* const buffer_;
+ Tuple tuple_;
+
+};
+~~~
+{: .language-cpp}
This implementation of a template class _BinaryMapping_ provides _get_ and _set_ template methods for accessing values in a given binary buffer using the mapping provided by a given
Tuple template argument. The most notable element of this class is the usage of the _decltype_ keyword which was introduced in C++11. This keyword makes it easier to declare types
@@ -52,27 +55,30 @@ as its return type is also dependent on the template arguments.
As you may have noticed the above template class is not complete as I have not included a implementation of the _TupleReader::read_ method which does the actual work of mapping the binary
buffer as a tuple. This mapping is achieved by the following recursive template methods:
- struct TupleReader {
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index == std::tuple_size<Tuple>::value, void
- >::type read(Tuple&, uint8_t*) { }
-
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index < std::tuple_size<Tuple>::value, void
- >::type read(Tuple& tuple, uint8_t* buffer) {
- std::get<Index>(tuple) = reinterpret_cast<
- typename std::tuple_element<Index, Tuple>::type
- >(buffer+Offset);
-
- read<Tuple,
- Index + 1,
- Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
- tuple, buffer
- );
- }
- };
+~~~
+struct TupleReader {
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index == std::tuple_size<Tuple>::value, void
+ >::type read(Tuple&, uint8_t*) { }
+
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index < std::tuple_size<Tuple>::value, void
+ >::type read(Tuple& tuple, uint8_t* buffer) {
+ std::get<Index>(tuple) = reinterpret_cast<
+ typename std::tuple_element<Index, Tuple>::type
+ >(buffer+Offset);
+
+ read<Tuple,
+ Index + 1,
+ Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
+ tuple, buffer
+ );
+ }
+};
+~~~
+{: .language-cpp}
Template metaprogramming in C++ offers a Turing-complete language which is fully executed during compilation. This means that any problem we may solve during the runtime of a _normal_ program
may also be solved during compilation using template metaprogramming techniques. This kind of programming is comparable to functional programming as we have to rely on recursion and pattern
@@ -100,19 +106,22 @@ is always available as a template argument.
The classes _TupleReader_ and _BinaryMapping_ are enough to map a binary structure as a _std::tuple_ instantiation like in the following example where I define a _TestRecord_ tuple containing
a pointer to _uint64\_t_ and _uint16\_t_ integers:
- typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
+~~~
+typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
- uint8_t* buffer = reinterpret_cast<uint8_t*>(
- std::calloc(10, sizeof(uint8_t))
- );
+uint8_t* buffer = reinterpret_cast<uint8_t*>(
+ std::calloc(10, sizeof(uint8_t))
+);
- BinaryMapping<TestRecord> mapping(buffer);
+BinaryMapping<TestRecord> mapping(buffer);
- mapping.set<0>(42);
- mapping.set<1>(1337);
+mapping.set<0>(42);
+mapping.set<1>(1337);
- std::cout << mapping.get<0>() << std::endl;
- std::cout << mapping.get<1>() << std::endl;
+std::cout << mapping.get<0>() << std::endl;
+std::cout << mapping.get<1>() << std::endl;
+~~~
+{: .language-cpp}
### Endianness
@@ -120,75 +129,84 @@ As you may remember this does not take endianness into account as I defined as a
_BinaryMapping_ template class which worked, but led to problems as soon as I mixed calls to _get_ and _set_. The resulting problems could of course have been fixed but this would probably
have conflicted with In-place modifications of the buffer. Because of that I chose to implement endianness support in a separate set of templates.
- struct BigEndianSorter {
- template <class Key>
- static void write(uint8_t*, const Key&);
+~~~
+struct BigEndianSorter {
+ template <class Key>
+ static void write(uint8_t*, const Key&);
- template <typename Key>
- static Key read(uint8_t* buffer);
- };
+ template <typename Key>
+ static Key read(uint8_t* buffer);
+};
+~~~
+{: .language-cpp}
To be able to work with different byte orderings I abstracted the basic operations down to _read_ and _write_ template methods contained in a _struct_ so I would be able to provide separate
implementations of these methods for each type of endianness. The following template specialization of the _write_ method which does an In-place reordering of a _uint64\_t_ should be enough
to understand the basic principle:
- template <>
- void BigEndianSorter::write<uint64_t>(uint8_t* buffer, const uint64_t& number) {
- *reinterpret_cast<uint64_t*>(buffer) = htobe64(number);
- }
+~~~
+template <>
+void BigEndianSorter::write<uint64_t>(uint8_t* buffer, const uint64_t& number) {
+ *reinterpret_cast<uint64_t*>(buffer) = htobe64(number);
+}
+~~~
+{: .language-cpp}
As soon as I had the basic endianness conversion methods implemented in a manner which could be used to specialize other template classes I was able to build a generic implementation of a
serializer which respects the structure defined by a given _std::tuple_ instantiation:
- template <class ByteSorter>
- struct Serializer {
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index == std::tuple_size<Tuple>::value, void
- >::type serialize(uint8_t*) { }
-
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index < std::tuple_size<Tuple>::value, void
- >::type serialize(uint8_t* buffer) {
- ByteSorter::template write<typename std::remove_reference<
- decltype(*std::get<Index>(Tuple{}))
- >::type>(
- buffer+Offset,
- *reinterpret_cast<typename std::tuple_element<Index, Tuple>::type>(
- buffer+Offset
- )
- );
-
- serialize<Tuple,
- Index + 1,
- Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
- buffer
- );
- }
-
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index == std::tuple_size<Tuple>::value, void
- >::type deserialize(uint8_t*) { }
-
- template <typename Tuple, size_t Index = 0, off_t Offset = 0>
- static inline typename std::enable_if<
- Index < std::tuple_size<Tuple>::value, void
- >::type deserialize(uint8_t* buffer) {
+~~~
+template <class ByteSorter>
+struct Serializer {
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index == std::tuple_size<Tuple>::value, void
+ >::type serialize(uint8_t*) { }
+
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index < std::tuple_size<Tuple>::value, void
+ >::type serialize(uint8_t* buffer) {
+ ByteSorter::template write<typename std::remove_reference<
+ decltype(*std::get<Index>(Tuple{}))
+ >::type>(
+ buffer+Offset,
*reinterpret_cast<typename std::tuple_element<Index, Tuple>::type>(
buffer+Offset
- ) = *ByteSorter::template read<
- typename std::tuple_element<Index, Tuple>::type
- >(buffer+Offset);
-
- deserialize<Tuple,
- Index + 1,
- Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
- buffer
- );
- }
- };
+ )
+ );
+
+ serialize<Tuple,
+ Index + 1,
+ Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
+ buffer
+ );
+ }
+
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index == std::tuple_size<Tuple>::value, void
+ >::type deserialize(uint8_t*) { }
+
+ template <typename Tuple, size_t Index = 0, off_t Offset = 0>
+ static inline typename std::enable_if<
+ Index < std::tuple_size<Tuple>::value, void
+ >::type deserialize(uint8_t* buffer) {
+ *reinterpret_cast<typename std::tuple_element<Index, Tuple>::type>(
+ buffer+Offset
+ ) = *ByteSorter::template read<
+ typename std::tuple_element<Index, Tuple>::type
+ >(buffer+Offset);
+
+ deserialize<Tuple,
+ Index + 1,
+ Offset + sizeof(decltype(*std::get<Index>(Tuple{})))>(
+ buffer
+ );
+ }
+};
+~~~
+{: .language-cpp}
It should be evident that the way both the _serialize_ and _deserialize_ template methods are implemented is very similar to the _TupleReader_ implementation. In fact the only difference
is that no actual _std::tuple_ instantiation instance is touched and instead of setting pointers to the buffer we are only reordering the bytes of each section of the buffer corresponding to
@@ -198,34 +216,37 @@ a tuple element. This results in a complete In-place conversion between differen
At last I am now able to do everything I planned in the beginning in a very compact way using the _Serializer_, _TupleReader_ and _BinaryMapping_ templates. In practice this now looks like this:
- typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
+~~~
+typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
- uint8_t* buffer = reinterpret_cast<uint8_t*>(
- std::calloc(10, sizeof(uint8_t))
- );
+uint8_t* buffer = reinterpret_cast<uint8_t*>(
+ std::calloc(10, sizeof(uint8_t))
+);
- BinaryMapping<TestRecord> mapping(buffer);
+BinaryMapping<TestRecord> mapping(buffer);
- mapping.set<0>(42);
- mapping.set<1>(1001);
+mapping.set<0>(42);
+mapping.set<1>(1001);
- Serializer<BigEndianSorter>::serialize<TestRecord>(buffer);
+Serializer<BigEndianSorter>::serialize<TestRecord>(buffer);
- uint8_t* testBuffer = reinterpret_cast<uint8_t*>(
- std::calloc(10, sizeof(uint8_t))
- );
+uint8_t* testBuffer = reinterpret_cast<uint8_t*>(
+ std::calloc(10, sizeof(uint8_t))
+);
- std::memcpy(testBuffer, buffer, 10);
+std::memcpy(testBuffer, buffer, 10);
- Serializer<BigEndianSorter>::deserialize<TestRecord>(testBuffer);
+Serializer<BigEndianSorter>::deserialize<TestRecord>(testBuffer);
- BinaryMapping<TestRecord> testMapping(testBuffer);
+BinaryMapping<TestRecord> testMapping(testBuffer);
- std::cout << testMapping.get<0>() << std::endl;
- std::cout << testMapping.get<1>() << std::endl;
+std::cout << testMapping.get<0>() << std::endl;
+std::cout << testMapping.get<1>() << std::endl;
- std::free(buffer);
- std::free(testBuffer);
+std::free(buffer);
+std::free(testBuffer);
+~~~
+{: .language-cpp}
The above coding makes use of all features provided by the described templates by first setting two values using _BinaryMapping_ specialized on the _TestRecord_ tuple, serializing them using
_Serializer_ specialized on the _BigEndianSorter_, deserializing the buffer back to the host byte ordering and reading the values using another _BinaryMapping_.