From f4043ec1e9f02efc7f653274b9e4e22ef2782a51 Mon Sep 17 00:00:00 2001 From: Adrian Kummerlaender Date: Sun, 10 Aug 2014 13:09:42 +0200 Subject: 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 --- ...res_as_tuples_using_template_metaprogramming.md | 279 +++++++++++---------- 1 file changed, 150 insertions(+), 129 deletions(-) (limited to 'articles/2013-11-03_mapping_binary_structures_as_tuples_using_template_metaprogramming.md') 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 - class BinaryMapping { - public: - BinaryMapping(uint8_t* const buffer): - buffer_(buffer) { - TupleReader::read(this->tuple_, buffer); - } - - template - decltype(*std::get(Tuple{})) get() { - return *std::get(this->tuple_); - } - - template (Tuple{}))> - void set(const Value value) { - *std::get(this->tuple_) = value; - } - - private: - uint8_t* const buffer_; - Tuple tuple_; - - }; +~~~ +template +class BinaryMapping { + public: + BinaryMapping(uint8_t* const buffer): + buffer_(buffer) { + TupleReader::read(this->tuple_, buffer); + } + + template + decltype(*std::get(Tuple{})) get() { + return *std::get(this->tuple_); + } + + template (Tuple{}))> + void set(const Value value) { + *std::get(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 - static inline typename std::enable_if< - Index == std::tuple_size::value, void - >::type read(Tuple&, uint8_t*) { } - - template - static inline typename std::enable_if< - Index < std::tuple_size::value, void - >::type read(Tuple& tuple, uint8_t* buffer) { - std::get(tuple) = reinterpret_cast< - typename std::tuple_element::type - >(buffer+Offset); - - read(Tuple{})))>( - tuple, buffer - ); - } - }; +~~~ +struct TupleReader { + template + static inline typename std::enable_if< + Index == std::tuple_size::value, void + >::type read(Tuple&, uint8_t*) { } + + template + static inline typename std::enable_if< + Index < std::tuple_size::value, void + >::type read(Tuple& tuple, uint8_t* buffer) { + std::get(tuple) = reinterpret_cast< + typename std::tuple_element::type + >(buffer+Offset); + + read(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 TestRecord; +~~~ +typedef std::tuple TestRecord; - uint8_t* buffer = reinterpret_cast( - std::calloc(10, sizeof(uint8_t)) - ); +uint8_t* buffer = reinterpret_cast( + std::calloc(10, sizeof(uint8_t)) +); - BinaryMapping mapping(buffer); +BinaryMapping 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 - static void write(uint8_t*, const Key&); +~~~ +struct BigEndianSorter { + template + static void write(uint8_t*, const Key&); - template - static Key read(uint8_t* buffer); - }; + template + 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(uint8_t* buffer, const uint64_t& number) { - *reinterpret_cast(buffer) = htobe64(number); - } +~~~ +template <> +void BigEndianSorter::write(uint8_t* buffer, const uint64_t& number) { + *reinterpret_cast(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 - struct Serializer { - template - static inline typename std::enable_if< - Index == std::tuple_size::value, void - >::type serialize(uint8_t*) { } - - template - static inline typename std::enable_if< - Index < std::tuple_size::value, void - >::type serialize(uint8_t* buffer) { - ByteSorter::template write(Tuple{})) - >::type>( - buffer+Offset, - *reinterpret_cast::type>( - buffer+Offset - ) - ); - - serialize(Tuple{})))>( - buffer - ); - } - - template - static inline typename std::enable_if< - Index == std::tuple_size::value, void - >::type deserialize(uint8_t*) { } - - template - static inline typename std::enable_if< - Index < std::tuple_size::value, void - >::type deserialize(uint8_t* buffer) { +~~~ +template +struct Serializer { + template + static inline typename std::enable_if< + Index == std::tuple_size::value, void + >::type serialize(uint8_t*) { } + + template + static inline typename std::enable_if< + Index < std::tuple_size::value, void + >::type serialize(uint8_t* buffer) { + ByteSorter::template write(Tuple{})) + >::type>( + buffer+Offset, *reinterpret_cast::type>( buffer+Offset - ) = *ByteSorter::template read< - typename std::tuple_element::type - >(buffer+Offset); - - deserialize(Tuple{})))>( - buffer - ); - } - }; + ) + ); + + serialize(Tuple{})))>( + buffer + ); + } + + template + static inline typename std::enable_if< + Index == std::tuple_size::value, void + >::type deserialize(uint8_t*) { } + + template + static inline typename std::enable_if< + Index < std::tuple_size::value, void + >::type deserialize(uint8_t* buffer) { + *reinterpret_cast::type>( + buffer+Offset + ) = *ByteSorter::template read< + typename std::tuple_element::type + >(buffer+Offset); + + deserialize(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 TestRecord; +~~~ +typedef std::tuple TestRecord; - uint8_t* buffer = reinterpret_cast( - std::calloc(10, sizeof(uint8_t)) - ); +uint8_t* buffer = reinterpret_cast( + std::calloc(10, sizeof(uint8_t)) +); - BinaryMapping mapping(buffer); +BinaryMapping mapping(buffer); - mapping.set<0>(42); - mapping.set<1>(1001); +mapping.set<0>(42); +mapping.set<1>(1001); - Serializer::serialize(buffer); +Serializer::serialize(buffer); - uint8_t* testBuffer = reinterpret_cast( - std::calloc(10, sizeof(uint8_t)) - ); +uint8_t* testBuffer = reinterpret_cast( + std::calloc(10, sizeof(uint8_t)) +); - std::memcpy(testBuffer, buffer, 10); +std::memcpy(testBuffer, buffer, 10); - Serializer::deserialize(testBuffer); +Serializer::deserialize(testBuffer); - BinaryMapping testMapping(testBuffer); +BinaryMapping 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_. -- cgit v1.2.3