cppnow-2016-metaprogramming-for-dummies



cppnow-2016-metaprogramming-for-dummies

0 5


cppnow-2016-metaprogramming-for-dummies

My "Metaprogramming for dummies" presentation for C++Now 2016

On Github ldionne / cppnow-2016-metaprogramming-for-dummies

Metaprogramming for dummies

Louis Dionne, C++Now 2016

Why do we need this talk?

Because metaprogramming is hard?

Or because it's taught the wrong way?

I think it's the latter

I'll do my best to break the mold

But please forget what you already know

Why you need metaprogramming

Generating JSON

struct Person {
    std::string name;
    int age;
};

int main() {
    Person joe{"Joe", 30};
    std::cout << to_json(joe);
}

In Python, we could write

def to_json(obj):
    members = []
    for member in obj.__dict__:
        members.append('"' + member + '" : ' +
                            str(getattr(obj, member)))
    return '{' + ', '.join(members) + '}'

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

print to_json(Person("John", 30))
Of course, this is a simplified version and it only handles objects.

In C++, not so

template <typename T>
std::string to_json(T const& obj) {
    std::vector<std::string> members;
    for (std::string const& member : obj.__members__()) // ???
        members.push_back('"' + member + "\" : " +
                                to_json(obj.__get__(member))); // ???

    return "{" + boost::algorithm::join(members, ", ") + "}";
}
We won't talk about reflection in this talk, because it's an introduction only.

Registering classes



How to allow registering many at once?

Classic solution



Calling register_ several times manually is not an option; assume you need
to know all the types that will be registered at once (you could need the
total size of the types, or something like that).

Drawbacks

  • Not straightforward
  • Not reusable

In Python, we can write

class A: pass
class B: pass
class C: pass

def register(classes):
    for c in classes:
        print "Registering " + c.__name__

register([A, B, C])
This works because types are first class citizens

What we would like to write in C++

void register_(std::vector<...> const& classes) {
    for (auto c : classes) {
        std::cout << "Registering " << c.name()
                  << ", which is of size " << sizeof(c) << std::endl;
        static_assert(sizeof(c) <= 1000, "");
    }
}

int main() {
    std::vector<...> classes{A, B, C};
    register_(classes);
}

Optimizing alignment



What we would like to write

Type packed_triple(std::vector<Type> types) {
    std::sort(types.begin(), types.end(), [](auto const& a,
                                             auto const& b) {
        return a.alignment() > b.alignment();
    });

    return Type{Triple<types[0], types[1], types[2]>};
}

int main() {
    std::vector<Type> types{A, B, C};
    Packed = packed_triple(types); // get the type
    Packed triple{...}; // define an object of that type
}
Notice how this sort of computation is completely compile-time. The inputs are types and the output is a type too.

Introducing heterogeneity

By now, I would expect the audience to understand why metaprogramming might be useful in the context of C++. Now, we'll try to develop our own basic solutions to solve the problems above.

Ever tried returning multiple values from a function?

double, double, double
from_spherical(double rho, double theta, double phi) {
    double x = rho * std::sin(theta) * std::cos(phi);
    double y = rho * std::sin(theta) * std::sin(phi);
    double z = rho * std::cos(theta);
    return (x, y, z);
}

double (x, y, z) = from_spherical(3, 16.5, 25.5);

Use std::tuple



Somewhat equivalent to

struct {
    double _0; double _1; double _2;
} xyz = from_spherical(3, 16.5, 25.5);

double x = xyz._0,
       y = xyz._1,
       z = xyz._2;

In essence it's like a struct

Yet it can be seen like a sequence!

A sequence that can hold different types



Somewhat equivalent to

struct {
    Fish _0; Cat _1; Dog _2; // notice the members have different types
} animals = my_animals();

Fish fish = animals._0;
Cat  cat  = animals._1;
Dog  dog  = animals._2;

Let's go back to class registration

void register_(std::vector<...> const& classes) {
    for (auto c : classes) {
        std::cout << "Registering " << c.name()
                  << ", which is of size " << sizeof(c) << std::endl;
        static_assert(sizeof(c) <= 1000, "");
    }
}

int main() {
    std::vector<...> classes{A, B, C};
    register_(classes);
}

Would that work?

void register_(std::vector<std::type_info> const& classes) {
    for (auto c : classes) {
        std::cout << "Registering " << c.name()
                  << ", which is of size " << sizeof(c) << std::endl;
        static_assert(sizeof(c) <= 1000, "");
    }
}

int main() {
    std::vector<std::type_info> classes{typeid(A), typeid(B),
                                        typeid(C)};
    register_(classes);
}
1. We need the size of each type, not that of std::type_info itself. 2. We can't store std::type_info in a vector, because not Copyable 3. Even if type_info provided a size() method, it would only be available at runtime.

Solution: a static type_info?

template <typename T>
struct static_type_info {
    using type = T;
};

void register_(std::vector<...> const& classes) {
    for (auto c : classes) {
        using T = typename decltype(c)::type;
        std::cout << "Registering " << typeid(T).name()
                  << ", which is of size " << sizeof(T) << std::endl;
        static_assert(sizeof(T) <= 1000, "");
    }
}

int main() {
    std::vector<...> classes{static_type_info<A>{},
                             static_type_info<B>{},
                             static_type_info<C>{}};
    register_(classes);
}
We're close, but for this to work we'd need to be able to store objects of different types in the vector, which we can't do.

Solution: std::tuple!

template <typename ...TypeInfos>
void register_(std::tuple<TypeInfos...> const& classes) {
    for (auto c : classes) {
        using T = typename decltype(c)::type;
        std::cout << "Registering " << typeid(T).name()
                  << ", which is of size " << sizeof(T) << std::endl;
        static_assert(sizeof(T) <= 1000, "");
    }
}

int main() {
    std::tuple<static_type_info<A>,
               static_type_info<B>,
               static_type_info<C>> classes;
    register_(classes);
}

Now, we only need to iterate on a std::tuple

Fortunately, there's Hana



This is equivalent to

template <typename ...TypeInfos>
void register_(std::tuple<TypeInfos...> const& classes) {
    auto f = [](auto c) {
        using T = typename decltype(c)::type;
        std::cout << "Registering " << typeid(C).name()
                  << ", which is of size " << sizeof(C) << std::endl;
        static_assert(sizeof(C) <= 1000, "");
    };

    f(std::get<0>(classes));
    f(std::get<1>(classes));
    f(std::get<2>(classes));
    // ...
}
Recapitulating: 1. We create a static type info struct to hold type information at compile-time 2. We create a tuple of these type infos 3. We manipulate the elements of the tuple as normal objects to achieve our goal

Hana provides this static_type_info



Heterogeneous data structures and algorithms

Now, I would expect the audience to understand the basics of tuples, and most importantly why tuples can form the basis for metaprogramming.

Tuple



Equivalent to std::vector for the heterogeneous world.
The simplest and most useful container.

Indexing



We use the _c UDL to create a compile-time number, which is required by Hana.
We won't get into the details of the interface, but compile-time numbers are
required by most functions in Hana.

Inserting



Removing



Iterating



Transforming



Filtering out



Sorting



Partitioning



Searching



Counting



Reversing



Unpacking



Equivalent to



Let's go back to alignment optimization



Solution: use a tuple!



A more terse version



Map



We use the _s UDL to create a compile-time strings. This is similar to
compile-time numbers used before. Note that this is a nonstandard extension.

Accessing keys



Querying



Inserting



Removing



An example: an event system



TODO: This example does not showcase the heterogeneity of map values properly.








Applications are endless

We saw

  • JSON
  • Class registration
  • Alignment optimization
  • Event systems

But there's also

  • Dimensional analysis
  • Serialization
  • Automatic paralellization
  • Domain specific languages
  • Expression templates

Hana is in Boost

Use it!

Help us improve it

Thank you

http://ldionne.com http://github.com/ldionne http://github.com/boostorg/hana

1
Metaprogramming for dummies Louis Dionne, C++Now 2016