//// Copyright 2017 Peter Dimov Distributed under the Boost Software License, Version 1.0. See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt //// [#examples] # Examples :toc: :toc-title: :idprefix: ## Generating Test Cases Let's suppose that we have written a metafunction `result`: ``` template using promote = typename std::common_type::type; template using result = typename std::common_type, promote>::type; ``` that ought to represent the result of an arithmetic operation on the integer types `T` and `U`, for example `t + u`. We want to test whether `result` gives correct results for various combinations of `T` and `U`, so we write the function ``` template void test_result() { using T3 = decltype( T1() + T2() ); using T4 = result; std::cout << ( std::is_same::value? "[PASS]": "[FAIL]" ) << std::endl; } ``` and then need to call it a substantial number of times: int main() { test_result(); test_result(); test_result(); test_result(); // ... } Writing all those type combinations by hand is unwieldy, error prone, and worst of all, boring. This is how we can leverage Mp11 to automate the task: ``` #include #include #include #include #include using namespace boost::mp11; template std::string name() { return boost::core::demangle( typeid(T).name() ); } template using promote = typename std::common_type::type; template using result = typename std::common_type, promote>::type; template void test_result( mp_list const& ) { using T3 = decltype( T1() + T2() ); using T4 = result; std::cout << ( std::is_same::value? "[PASS] ": "[FAIL] " ) << name() << " + " << name() << " -> " << name() << ", result: " << name() << std::endl; } int main() { using L = std::tuple; tuple_for_each( mp_product(), [](auto&& x){ test_result(x); } ); } ``` How does it work? `mp_product` calls `F` where `T1` varies over the elements of `L1` and `T2` varies over the elements of `L2`, as if by executing two nested loops. It then returns a list of these results, of the same type as `L1`. In our case, both lists are the same `std::tuple`, and `F` is `mp_list`, so `mp_product` will get us `std::tuple, mp_list, mp_list, ..., mp_list, mp_list>`. We then default-construct this tuple and pass it to `tuple_for_each`. `tuple_for_each(tp, f)` calls `f` for every tuple element; we use a (C++14) lambda that calls `test_result`. In pure C++11, we can't use a lambda with an `auto&&` parameter, so we'll have to make `test_result` a function object with a templated `operator()` and pass that to `tuple_for_each` directly: ``` struct test_result { template void operator()( mp_list const& ) const { using T3 = decltype( T1() + T2() ); using T4 = result; std::cout << ( std::is_same::value? "[PASS] ": "[FAIL] " ) << name() << " + " << name() << " -> " << name() << ", result: " << name() << std::endl; } }; int main() { using L = std::tuple; tuple_for_each( mp_product(), test_result() ); } ``` ## Writing `common_type` specializations The standard trait `std::common_type`, used to obtain a type to which all of its arguments can convert without unnecessary loss of precision, can be user-specialized when its default implementation (based on the ternary `?:` operator) is unsuitable. Let's write a `common_type` specialization for two `std::tuple` arguments. For that, we need a metafunction that applies `std::common_type` to each pair of elements and gathers the results into a tuple: ``` template using common_type_t = typename std::common_type::type; // standard in C++14 template using common_tuple = mp_transform; ``` then specialize `common_type` to use it: ``` namespace std { template struct common_type, std::tuple>: mp_defer, std::tuple> { }; } // std ``` (There is no need to specialize `std::common_type` for more than two arguments - it takes care of synthesizing the appropriate semantics from the binary case.) The subtlety here is the use of `mp_defer`. We could have defined a nested `type` to `common_tuple, std::tuple>`, and it would still have worked in all valid cases. By letting `mp_defer` define `type`, though, we make our specialization _SFINAE-friendly_. That is, when our `common_tuple` causes a substitution failure instead of a hard error, `mp_defer` will not define a nested `type`, and `common_type_t`, which is defined as `typename common_type<...>::type`, will also cause a substitution failure. As another example, consider the hypothetical type `expected` that represents either a successful return with a value of `T`, or an unsucessful return with an error code of some type in the list `E...`. The common type of `expected` and `expected` is `expected, E1, E2, E3, E4, E5>`. That is, the possible return values are combined into their common type, and we take the union of the set of error types. Therefore, ``` template using common_expected = mp_rename>, common_type_t>, expected>; namespace std { template struct common_type, expected>: mp_defer, T2, mp_list> { }; } // std ``` Here we've taken a different tack; instead of passing the `expected` types to `common_expected`, we're passing the `T` types and lists of the `E` types. This makes our job easier. `mp_unique>` gives us the concatenation of `E1` and `E2` with the duplicates removed; we then add `common_type_t` to the front via `mp_push_front`; and finally, we `mp_rename` the resultant `mp_list` to `expected`. ## Fixing `tuple_cat` The article http://pdimov.com/cpp2/simple_cxx11_metaprogramming.html#[Simple C++11 metaprogramming] builds an implementation of the standard function `tuple_cat`, with the end result given below: ``` template using F = mp_iota>; template R tuple_cat_( mp_list, mp_list, Tp tp ) { return R{ std::get(std::get(tp))... }; } template, typename std::remove_reference::type...>> R tuple_cat( Tp &&... tp ) { std::size_t const N = sizeof...(Tp); // inner using list1 = mp_list::type, mp_list>...>; using list2 = mp_iota_c; using list3 = mp_transform; using inner = mp_apply; // outer using list4 = mp_transform; using outer = mp_apply; // return tuple_cat_( inner(), outer(), std::forward_as_tuple( std::forward(tp)... ) ); } ``` This function, however, is not entirely correct, in that it doesn't handle some cases properly. For example, trying to concatenate tuples containing move-only elements such as `unique_ptr` fails: ``` std::tuple> t1; std::tuple> t2; auto result = ::tuple_cat( std::move( t1 ), std::move( t2 ) ); ``` Trying to concatenate `const` tuples fails: ``` std::tuple const t1; std::tuple const t2; auto result = ::tuple_cat( t1, t2 ); ``` And finally, the standard `tuple_cat` is specified to work on arbitrary tuple-like types (that is, all types that support `tuple_size`, `tuple_element`, and `get`), while our implementation only works with `tuple` and `pair`. `std::array`, for example, fails: ``` std::array t1{ 1, 2 }; std::array t2{ 3.0f, 4.0f, 5.0f }; auto result = ::tuple_cat( t1, t2 ); ``` Let's fix these one by one. Support for move-only types is easy, if one knows where to look. The problem is that `Tp` that we're passing to the helper `tuple_cat_` is (correctly) `tuple&&, unique_ptr&&>`, but `std::get<0>(tp)` still returns `unique_ptr&`, because `tp` is an lvalue. This behavior is a bit surprising, but consistent with how rvalue reference members are treated by the language. Long story short, we need `std::move(tp)` in `tuple_cat_` to make `tp` an rvalue: template R tuple_cat_( mp_list, mp_list, Tp tp ) { return R{ std::get(std::get(std::move(tp)))... }; } Next, `const`-qualified tuples. The issue here is that we're stripping references from the input tuples, but not `const`. As a result, we're trying to manipulate types such as `tuple const` with Mp11 algorithms, and these types do not fit the list concept. We just need to strip qualifiers as well, by defining the useful `remove_cv_ref` primitive that is inexplicably missing from the standard library: template using remove_cv_ref = typename std::remove_cv< typename std::remove_reference::type>::type; and then by using `remove_cv_ref` in place of `typename std::remove_reference::type`: ``` template, remove_cv_ref...>> R tuple_cat( Tp &&... tp ) { std::size_t const N = sizeof...(Tp); // inner using list1 = mp_list, mp_list>...>; // ... ``` Finally, tuple-like types. We've so far exploited the fact that `std::pair` and `std::tuple` are valid Mp11 lists, but in general, arbitrary tuple-like types aren't, so we need to convert them into such. For that, we'll need to define a metafunction `from_tuple_like` that will take an arbitrary tuple-like type and will return, in our case, the corresponding `mp_list`. Technically, a more principled approach would've been to return `std::tuple`, but here `mp_list` will prove more convenient. What we need is, given a tuple-like type `Tp`, to obtain `mp_list::type, std::tuple_element<1, Tp>::type, ..., std::tuple_element::type>`, where `N` is `tuple_size::value`. Here's one way to do it: ``` template using tuple_element = typename std::tuple_element::type; template using from_tuple_like = mp_product, mp_iota>>; ``` (`mp_iota` is an algorithm that returns an `mp_list` with elements `mp_size_t<0>`, `mp_size_t<1>`, ..., `mp_size_t`.) Remember that `mp_product` performs the equivalent of two nested loops over the elements of `L1` and `L2`, applying `F` to the two variables and gathering the result. In our case `L1` consists of the single element `T`, so only the second loop (over `mp_iota`, where `N` is `tuple_size`), remains, and we get a list of the same type as `L1` (an `mp_list`) with contents `tuple_element>`, `tuple_element>`, ..., `tuple_element>`. For completeness's sake, here's another, more traditional way to achieve the same result: template using from_tuple_like = mp_transform_q, mp_iota>>; With all these fixes applied, our fully operational `tuple_cat` now looks like this: ``` template using F = mp_iota>; template R tuple_cat_( mp_list, mp_list, Tp tp ) { return R{ std::get(std::get(std::move(tp)))... }; } template using remove_cv_ref = typename std::remove_cv< typename std::remove_reference::type>::type; template using tuple_element = typename std::tuple_element::type; template using from_tuple_like = mp_product, mp_iota>>; template, from_tuple_like>...>> R tuple_cat( Tp &&... tp ) { std::size_t const N = sizeof...(Tp); // inner using list1 = mp_list>...>; using list2 = mp_iota_c; using list3 = mp_transform; using inner = mp_apply; // outer using list4 = mp_transform; using outer = mp_apply; // return tuple_cat_( inner(), outer(), std::forward_as_tuple( std::forward(tp)... ) ); } ``` ## Computing Return Types C++17 has a standard variant type, called `std::variant`. It also defines a function template `std::visit` that can be used to apply a function to the contained value of one or more variants. So for instance, if the variant `v1` contains `1`, and the variant `v2` contains `2.0f`, `std::visit(f, v1, v2)` will call `f(1, 2.0f)`. However, `std::visit` has one limitation: it cannot return a result unless all possible applications of the function have the same return type. If, for instance, `v1` and `v2` are both of type `std::variant`, std::visit( []( auto const& x, auto const& y ){ return x + y; }, v1, v2 ); will fail to compile because the result of `x + y` can be either `int` or `float` depending on what `v1` and `v2` hold. A type that can hold either `int` or `float` already exists, called, surprisingly enough, `std::variant`. Let's write our own function template `rvisit` that is the same as `visit` but returns a `variant`: ``` template auto rvisit( F&& f, V&&... v ) { using R = /*...*/; return std::visit( [&]( auto&&... x ) { return R( std::forward(f)( std::forward(x)... ) ); }, std::forward( v )... ); } ``` What this does is basically calls `std::visit` to do the work, but instead of passing it `f`, we pass a lambda that does the same as `f` except it converts the result to a common type `R`. `R` is supposed to be `std::variant<...>` where the ellipsis denotes the return types of calling `f` with all possible combinations of variant values. We'll first define a helper quoted metafunction `Qret` that returns the result of the application of `F` to arguments of type `T...`: template struct Qret { template using fn = decltype( std::declval()( std::declval()... ) ); }; (Unfortunately, we can't just define this metafunction inside `rvisit`; the language prohibits defining template aliases inside functions.) With `Qret` in hand, a `variant` of the possible return types is just a matter of applying it over the possible combinations of the variant values: using R = mp_product_q, std::remove_reference_t...>; Why does this work? `mp_product, L2, ..., Ln>` returns `L1, ...>`, where `Ui` traverse all possible combinations of list values. Since in our case all `Li` are `std::variant`, the result will also be `std::variant`. (`mp_product_q` is the same as `mp_product`, but for quoted metafunctions such as our `Qret`.) One more step remains. Suppose that, as above, we're passing two variants of type `std::variant` and `F` is `[]( auto const& x, auto const& y ){ return x + y; }`. This will generate `R` of length 9, one per each combination, but many of those elements will be the same, either `int` or `float`, and we need to filter out the duplicates. So, using R = mp_unique, std::remove_reference_t...>>; and we're done: ``` #include #include #include #include #include #include using namespace boost::mp11; template struct Qret { template using fn = decltype( std::declval()( std::declval()... ) ); }; template auto rvisit( F&& f, V&&... v ) { using R = mp_unique, std::remove_reference_t...>>; return std::visit( [&]( auto&&... x ) { return R( std::forward(f)( std::forward(x)... ) ); }, std::forward( v )... ); } template std::string name() { return boost::core::demangle( typeid(T).name() ); } int main() { std::variant v1( 1 ); std::cout << "(" << name() << ")v1: "; std::visit( []( auto const& x ) { std::cout << "(" << name() << ")" << x << std::endl; }, v1 ); std::variant v2( 2.0f ); std::cout << "(" << name() << ")v2: "; std::visit( []( auto const& x ) { std::cout << "(" << name() << ")" << x << std::endl; }, v2 ); auto v3 = rvisit( []( auto const& x, auto const& y ){ return x + y; }, v1, v2 ); std::cout << "(" << name() << ")v3: "; std::visit( []( auto const& x ) { std::cout << "(" << name() << ")" << x << std::endl; }, v3 ); } ```