Template Metaprogramming
Why you must get it
This is not about...
- ... Functional programming
- ... -ftemplate-depth=∞
- ... Latest metaprogramming tricks (Really?)
But...
- Real (Manu's) world templates
- Okay, I confess, a bit of TMP madness
- What's (should be) next?
$ whoami
- Manu343726 @{twitter, reddit, github,...}
- Usually ranting about C++ on twitter. Being --pedantic as a way of life
- Full-time C++ for 4 years. Social life is overrated!
- No OOP, but my own metaprogramming library...
$ whoami
- biicode pulled me out of my cave! ... until I had to start working on Boost support...
- cmake kung-fu
- Leaving the cave: Blog posts on tmp. Exposure to sunlight (and C++ community)
- C++ courses in my university
- Now in By Tech working with embedded devices. Doing templates of course!
$ whoami
- I still don't know how to write slides
Templates and Metaprogramming
Templates
- Smart typed copy-paste system
- Let the compiler do the boring work for you
- Everything in a type-safe way
Metaprogramming
- Programs that generate programs. WHAT?
- const char[] stuff = { ... };( (void(*)())stuff)(); ? No, sorry
- Feel like a hacker
Template metaprogramming
- Hack the C++ type system to do useful work while compiling
"Programmer"
Organism that transforms coffee and pizza into lines of code
"C++ meta-programmer"
Mentally-diseased organism that transforms lines of code into time to go for a coffee
"C++ meta-programmer"
cmake --build .
(Don't) fear the templates
Use case 1: Array Views
Some context...
- TCP/IP-like raw communication protocol
Some context...
- Input: Receive frame, consume, discard
- Output: Build frame, send, discard
Goals
- Efficient stack processing
- Clean usage
Array View
class ArrayView
{
ArrayView(QByteArray& array);
ArrayView(const ArrayView& source,
std::size_t begin, std::size_t end);
~ArrayView() = default; // Does nothing
Byte operator[](std::size_t i) const;
Byte& operator[](std::size_t i);
ArrayView operator()(std::size_t begin,
std::size_t end) const;
template<typename T>
T read() const; // Magic goes here
template<typename T>
void write(const T& value); // More magic
private:
QByteArray* _array;
std::size_t _begin, _end;
};
Non-owning view of a slice of an existing array
ArrayView IO
- Serialization through read() and write() methods
- Can be implicit, add conversion and assignment operators
ArrayView IO
class ArrayView
{
public:
template<typename T>
T read() const {
QByteArray slice = _array->slice(_begin, _end);
QDataStream stream{_&slice};
T result;
stream >> result;
return result;
}
};
Transparent serialization by calling read()/write()
ArrayView IO
- Not that simple of course. Endianess, slicing, etc
- Note that QByteArray does COW implicitly!
ArrayView recursive slicing
- operator(): Take a slice out of the slice!
QByteArray array = readBytes();
ArrayView frame{array};
assert_equals(frame(0,2).read<quint16>(),
FRAME_HEADER);
ArrayView payload = frame(4, 32);
appLayer(payload);
ArrayView recursive slicing
class ArrayView
{
public:
ArrayView(const ArrayView& source,
std::size_t begin, std::size_t end)
: _array{source._array},
_begin{source._begin + begin},
_end{source._begin + end}
{}
ArrayView operator()(std::size_t begin, std::size_t end) const
{
return {*this, begin, end};
}
};
ArrayView: Bonus
- Wrap read()/write() with a proxy class
class ArrayView {
...
public:
template<typename T>
class ReaderWriter {
const T& operator=(const T& value) {
_view->write(value);
return value;
}
ArrayView* _view;
};
template<typename T>
ReaderWriter<T> readerWriter()
{
return {this};
}
};
ArrayView: Bonus
- Write a vieweable array class
- Don't forget to take care of array reasignment on the view!
class ViewableArray : public QByteArray, public ArrayView {
public:
ViewableArray(QByteArray& array);
...
};
ArrayView: Bonus
- Write a Frame class following your specs
class Frame : public ViewableArray {
public:
using ViewableArray::ViewableArray;
ReaderWriter<quint16> header()
ReaderWriter<quin32> source_address();
...
};
// Input
Frame frame = readBytes();
assert_equals(frame.header() == FRAME_HEADER);
// Output
Frame frame = buildRequest();
frame.source_address() = Me();
sendBytes(frame); Note sendBytes takes a QByteArray!
ArrayView
- Don't be afraid of templates! Use them to write zero-overhead syntax suggar
- More bonus: Each request frame is represented in the usual OO way as a class, but fields are ReaderWriters. OO like code, but no frame allocation/joining overhead.
- Come to By Tech and see!
Use case 2:
Named Tuples
Named Tuples
- I don't like std::get<>() at all.
- C structs are fixed, but we can programatically manipulate tuples with metaprogramming
Goals
- Human readable tuples please
- Customizable tuple layout
- Element access independent from layout
UX
auto tuple = make_tuple("pi"_, 3.141592654,
"e"_, 2.71828182846);
std::cout << "Pi: " << tuple["pi"_];
Use strings to name tuple elements
How?
- I like to abuse operator overloading a lot
- We have to map from strings to element index
constexpr hasing!
// Jonathan Müller, the constexpr hashing guy
namespace foonathan {
namespace string_id {
namespace detail {
using hash_type = std::uint64_t;
constexpr hash_type fnv_basis = 14695981039346656037ull;
constexpr hash_type fnv_prime = 109951162821ull;
// FNV-1a 64 bit hash
constexpr hash_type sid_hash(const char *str, hash_type hash = fnv_basis) noexcept
{
return *str ? sid_hash(str + 1, (hash ^ *str) * fnv_prime) : hash;
}
}
}
} // foonathan::string_id::detail
Just write a constexpr UDL that computes the hash of the literal
constexpr hasing!
template<const char* str>
using hash = std::integral_constant<
foonathan::string_id::detail::hash_type,
foonathan::string_id::detail::sid_hash(str)
>;
constepr auto operator"" _(const char* str, std::size_t length)
{
return hash<str>{};
}
Just write a constexpr UDL that computes the hash of the literal
Constexpr hashing
- That doesn't work, sorry
- Looking forward for variadic UDLs...
- Let's follow with macros...
Constexpr hashing
#define h(x) std::integral_constant< \
foonathan::string_id::detail::hash_type, \
foonathan::string_id::detail::sid_hash(x) \
>{}
int main()
{
auto t = make_tuple(h("pi"), 3.141592654,
h("e"), 2.71828182846);
std::cout << TUPLE_GET("e")(t) << std::endl;
std::cout << TUPLE_GET("pi")(t) << std::endl;
}
Mapping
- After hashing the strings, we have to map from hashes to tuple element indices
- Let's write a compile-time hashmap
Mapping
// Not mine, Louis Dionne deserves all merit
namespace detail {
template <typename x>
struct no_decay { using type = x; };
template <typename key, typename value>
struct pair { };
template <typename ...xs>
struct inherit : xs... { };
template <typename key, typename value>
static no_decay<value> lookup(pair<key, value>*);
template <typename key, typename ...pairs>
using at_key = typename
decltype(lookup<key>((inherit<pairs...>*)nullptr))
::type;
}
Use function lookup rules to map from input types to "return" types
Let's Tuple
- Our tuple will have both the elements and the keys, where keys are mapped onto the final element index
Let's Tuple
template<typename Indices, typename Types>
struct tuple;
template<typename... Indices, typename... Types>
struct tuple<std::tuple<Indices...>, std::tuple<Types...>> :
public std::tuple<Types...>
{
using tuple_t = std::tuple<Types...>;
template<typename key>
using element_index = detail::at_key<key, Indices...>;
template<typename Hash>
const auto& get() const {
return std::get<
element_index<Hash>::value
>(static_cast<const tuple_t&>(*this));
}
template<typename Hash>
const auto& operator[](Hash) const {
return get<Hash>();
}
template<typename... Ts>
tuple(Ts&&... elems) :
tuple_t{ std::forward<Ts>(elems)... } {}
};
Take types and indices, map access to std::tuple
Tuple Bonus
- I used std::tuple as backend just for convenience
- Customize mapping policy: Customizable tuple layout independent from member access
Tuple Bonus
- make_tuple() sugar: Take pairs of (hash, type)
Tuple Bonus
template<typename Hash, typename Type>
struct entry
{
using hash = Hash;
using type = Type;
};
template<typename... Entries>
struct tuple_builder
{
template<typename Seq>
struct build;
template<typename T, T... Seq>
struct build<std::integer_sequence<T, Seq...>>
{
using type = tuple<
std::tuple<detail::pair<
typename Entries::hash,
std::integral_constant<T,Seq>>...
>,
std::tuple<typename Entries::type...>
>;
};
using type = typename build<std::index_sequence_for<Entries...>>::type;
};
template<typename... Entries>
using build_tuple = typename tuple_builder<Entries...>::type;
Tuple bonus
- Just check my github gists
C++ Metaprogramming
What's next?
The perfect metaprogramming library
X Metaprogramming Library
- We need TMP libraries
- Standarizing boolean traits is not enough
- Support for "old" compilers
- For C++98/03, use Boost.MPL
xml
- C++11 only backend. Metafunction based
- When that works, write all the C++14/17 suggar you want on top of it
Thank you!
TMP: Why you must get it
By Manu Sánchez
TMP: Why you must get it
My meetingcpp 2015 talk. Real use cases of tmp and my thoughts on a real-world tmp library. A great example of how you shouldn't title your talks.
- 2,211