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::detailJust 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,301
 
  