hana-cppcon-2015



hana-cppcon-2015

0 5


hana-cppcon-2015

Presentation on Hana and metaprogramming for CppCon 2015

On Github ldionne / hana-cppcon-2015

----
==================== ## We must rethink metaprogramming ==================== ## But how? ### Here's my take Note: Hopefully, the audience should be convinced by now that there's a better way to write metaprograms. My goal is now to convince them that Hana is that way. ============================================================================== ### Heard of `integral_constant`? ```c++ template struct integral_constant { static constexpr T value = v; using value_type = T; using type = integral_constant; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } }; ``` Note: This section introduces extensions to `std::integral_constant` that follow the general philosophy of the paradigm shift. I am preparing the grounds for the next section, which will introduce the type/value unification per-se. ==================== ### Compile-time arithmetic: classic approach ```c++ template struct plus { using type = integral_constant; }; static_assert(std::is_same, integral_constant>::type, integral_constant >::value, ""); ``` ---- ### That's ok, but... ---- ### What if? ```c++ template constexpr auto operator+(integral_constant, integral_constant) { return integral_constant{}; } template constexpr auto operator==(integral_constant, integral_constant) { return integral_constant{}; } // ... ``` ---- ### Tadam! ```c++ static_assert(decltype( integral_constant{} + integral_constant{} == integral_constant{} )::value, ""); ``` ---- ### (or simply) ```c++ static_assert(integral_constant{} + integral_constant{} == integral_constant{} , ""); ``` ---- ### Pass me the sugar, please ```c++ template constexpr integral_constant int_c{}; static_assert(int_c + int_c == int_c, ""); ``` ---- ### More sugar ```c++ template constexpr auto operator"" _c() { // parse the characters and return an integral_constant } static_assert(1_c + 4_c == 5_c, ""); ``` ==================== ### Euclidean distance $$ \mathrm{distance}\left((x_1, y_1), (x_2, y_2)\right) := \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} $$ ---- ### Compile-time arithmetic: then ```cpp template struct distance { using xs = typename minus::type; using ys = typename minus::type; using type = typename sqrt::type, typename multiplies::type >::type >::type; }; static_assert(equal_to, int_>, point, int_>>::type, int_ >::value, ""); ``` ---- ### Compile-time arithmetic: now ```cpp template constexpr auto distance(P1 p1, P2 p2) { auto xs = p1.x - p2.x; auto ys = p1.y - p2.y; return sqrt(xs*xs + ys*ys); } static_assert(distance(point(3_c, 5_c), point(7_c, 2_c)) == 5_c, ""); ``` ---- ### But runtime arithmetic works too ```cpp auto p1 = point(3, 5); // dynamic values now auto p2 = point(7, 2); // assert(distance(p1, p2) == 5); // same function works! ``` ==================== ## That's not all ==================== ### Loop unrolling ```c++ template struct integral_constant { // ... template void times(F f) const { f(); f(); ... f(); // n times } }; ``` ---- ### Sceptical? ```cpp __attribute__((noinline)) void f() { } int main() { int_c.times(f); } ``` Assembly with -O3 ``` _main: ; snip pushq %rbp movq %rsp, %rbp callq __Z1fv callq __Z1fv callq __Z1fv callq __Z1fv callq __Z1fv xorl %eax, %eax popq %rbp retq ``` ==================== ### Tuple access ```c++ template struct tuple { // ... template constexpr decltype(auto) operator[](N const&) { return std::get<:value>(*this); } }; ``` ---- ### Compare ```c++ tuple values = {1, 'x', 3.4f}; char a = std::get(values); char b = values[1_c]; ``` ==================== ### Why stop here? - `std::ratio` - `std::integer_sequence` ============================================================================== ### Heard of ``? ```c++ template struct add_pointer { using type = T*; }; using IntPtr = add_pointer::type; ``` Note: This section introduces the type/value unification. ==================== ### Let's try something ```cpp template struct type { }; template constexpr type add_pointer(type const&) { return {}; } template constexpr std::false_type is_pointer(type const&) { return {}; } template constexpr std::true_type is_pointer(type const&) { return {}; } ``` Note: Make explicit the link between this and what we did in the previous section with integral constants. ---- ### Tadam! ```cpp type t{}; auto p = add_pointer(t); static_assert(is_pointer(p), ""); ``` ---- ### Sugar ```cpp template constexpr type type_c{}; auto t = type_c; auto p = add_pointer(t); static_assert(is_pointer(p), ""); ``` ==================== ## But what does that buy us? ==================== ### Types are now first class citizens! ```cpp auto xs = make_tuple(type_c, type_c, type_c); auto c = xs[1_c]; // sugar: auto ys = tuple_t; ``` ==================== ### Full language can be used __Before__ ```cpp using ts = vector; using us = copy_if, std::is_reference<_1>>>::type; ``` ---- __After__ ```cpp auto ts = make_tuple(type_c, type_c, type_c); auto us = filter(ts, [](auto t) { return is_pointer(t) || is_reference(t); }); ``` ==================== ### Only one library is required __Before__ ```cpp // types (MPL) using ts = mpl::vector; using us = mpl::copy_if, std::is_reference<_1>>>::type; // values (Fusion) auto vs = fusion::make_vector(1, 'c', nullptr, 3.5); auto ws = fusion::filter_if<:is_integral>>(vs); ``` ---- __After__ ```cpp // types auto ts = tuple_t; auto us = filter(ts, [](auto t) { return is_pointer(t) || is_reference(t); }); // values auto vs = make_tuple(1, 'c', nullptr, 3.5); auto ws = filter(vs, [](auto t) { return is_integral(t); }); ``` ==================== ### Unified syntax means more reuse #### (Amphibious EDSL using Boost.Proto) ```cpp auto expr = (_1 - _2) / _2; // compile-time computations static_assert(decltype(evaluate(expr, 6_c, 2_c))::value == 2, ""); // runtime computations int i = 6, j = 2; assert(evaluate(expr, i, j) == 2); ``` ==================== ### Unified syntax means more consistency __Before__ ```cpp auto map = make_map( "char", "int", "long", "float", "double", "void" ); std::string i = at_key(map); assert(i == "int"); ``` ---- __After__ ```cpp auto map = make_map( make_pair(type_c, "char"), make_pair(type_c, "int"), make_pair(type_c, "long"), make_pair(type_c, "float"), make_pair(type_c, "double") ); std::string i = map[type_c]; assert(i == "int"); ``` ==================== ### Case study: switch for `boost::any` ```cpp boost::any a = 3; std::string result = switch_<:string>(a)( case_([](int i) { return std::to_string(i); }) , case_([](double d) { return std::to_string(d); }) , empty([] { return "empty"; }) , default_([] { return "default"; }) ); assert(result == "3"); ``` ---- ### First ```cpp template auto case_ = [](auto f) { return std::make_pair(hana::type_c, f); }; struct default_t; auto default_ = case_; auto empty = case_; ``` ---- ### The beast ```cpp template auto switch_(Any& a) { return [&a](auto ...c) -> Result { auto cases = hana::make_tuple(c...); auto default_ = hana::find_if(cases, [](auto const& c) { return c.first == hana::type_c; }); static_assert(!hana::is_nothing(default_), "switch is missing a default_ case"); auto rest = hana::filter(cases, [](auto const& c) { return c.first != hana::type_c; }); return hana::unpack(rest, [&](auto& ...rest) { return impl(a, a.type(), default_->second, rest...); }); }; } ``` ---- ### Processing cases ```cpp template Result impl(Any& a, std::type_index const& t, Default& default_, Case& case_, Rest& ...rest) { using T = typename decltype(case_.first)::type; if (t == typeid(T)) { return hana::if_(hana::type_c == hana::type_c, [](auto& c, auto& a) { return c.second(); }, [](auto& c, auto& a) { return c.second(*boost::unsafe_any_cast(&a)); } )(case_, a); } else return impl(a, t, default_, rest...); } ``` ---- ### Base case ```cpp template Result impl(Any&, std::type_index const& t, Default& default_) { return default_(); } ``` ---- ### About 70 LOC ---- ### And your coworkers could understand ### (mostly) ============================================================================== ## My proposal, your deliverance: Hana - Heterogeneous + type level computations - 80+ algorithms - 8 heterogeneous containers - Improved compile-times ==================== ### Working on C++14 compilers - Clang >= 3.5 - GCC 5 on the way ==================== ### Newly accepted in Boost! #### Soon part of the distribution ==================== ## Embrace the future ## Embrace Hana ==================== # Thank you http://ldionne.com http://github.com/ldionne ============================================================================== ### Bonus: Implementing heterogeneous algorithms ==================== ### `transform` ```cpp template auto transform_impl(std::tuple const& tuple, F const& f, std::index_sequence) { return std::make_tuple(f(std::get(tuple))...); } template auto transform(std::tuple const& tuple, F const& f) { return transform_impl(tuple, f, std::make_index_sequence{}); } ``` ==================== ## Easy so far ### Brace yourselves ==================== ### Here comes `filter` ```cpp template auto filter_impl(Tuple const& tuple, Predicate predicate, std::index_sequence) { using Indices = filter_indices(tuple)))::value... >; return slice(tuple, Indices{}); } template auto filter(std::tuple const& tuple, Predicate predicate) { return filter_impl(tuple, predicate, std::make_index_sequence{}); } ``` ---- ```cpp template auto slice(Tuple const& tuple, std::index_sequence) { return std::make_tuple(std::get(tuple)...); } ``` ---- ```cpp template struct filter_indices_helper { // ... }; template using filter_indices = typename filter_indices_helper:: template as_index_sequence; ``` ---- ```cpp static constexpr bool results_array[sizeof...(results)] = {results...}; static constexpr std::size_t N = count(results_array, results_array + sizeof...(results), true); static constexpr array<:size_t n> compute_indices() { array<:size_t n> indices{}; std::size_t* out = &indices[0]; for (std::size_t i = 0; i computed_indices = compute_indices(); ``` ---- ```cpp template > struct as_index_sequence; template <:size_t> struct as_index_sequence<:index_sequence>> : std::index_sequence { }; ```