aboutsummaryrefslogtreecommitdiff
path: root/articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md
diff options
context:
space:
mode:
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.md35
1 files changed, 14 insertions, 21 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 5c82f05..e2b4784 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,7 +17,7 @@ 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 library's [_std::tuple_](http://en.cppreference.com/w/cpp/utility/tuple)
template.
-~~~
+```cpp
template<typename Tuple>
class BinaryMapping {
public:
@@ -42,8 +42,7 @@ class BinaryMapping {
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
@@ -55,7 +54,7 @@ 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:
-~~~
+```cpp
struct TupleReader {
template <typename Tuple, size_t Index = 0, off_t Offset = 0>
static inline typename std::enable_if<
@@ -77,8 +76,7 @@ struct TupleReader {
);
}
};
-~~~
-{: .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
@@ -106,7 +104,7 @@ 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:
-~~~
+```cpp
typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
uint8_t* buffer = reinterpret_cast<uint8_t*>(
@@ -120,8 +118,7 @@ mapping.set<1>(1337);
std::cout << mapping.get<0>() << std::endl;
std::cout << mapping.get<1>() << std::endl;
-~~~
-{: .language-cpp}
+```
## Endianness
@@ -129,7 +126,7 @@ 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.
-~~~
+```cpp
struct BigEndianSorter {
template <class Key>
static void write(uint8_t*, const Key&);
@@ -137,25 +134,23 @@ struct BigEndianSorter {
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:
-~~~
+```cpp
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:
-~~~
+```cpp
template <class ByteSorter>
struct Serializer {
template <typename Tuple, size_t Index = 0, off_t Offset = 0>
@@ -205,8 +200,7 @@ struct Serializer {
);
}
};
-~~~
-{: .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
@@ -216,7 +210,7 @@ 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:
-~~~
+```cpp
typedef std::tuple<uint64_t*, uint16_t*> TestRecord;
uint8_t* buffer = reinterpret_cast<uint8_t*>(
@@ -245,8 +239,7 @@ std::cout << testMapping.get<1>() << std::endl;
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_.