[/ / 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) /] [section Examples] [section Generating Test Cases] Let's suppose that we have written a metafunction `result`: template using promote = std::common_type_t; template using result = std::common_type_t, promote>; 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 #include using namespace boost::mp11; template std::string name() { return boost::core::demangle( typeid(T).name() ); } template using promote = std::common_type_t; template using result = std::common_type_t, promote>; 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; boost::tuple_for_each( mp_product(), [](auto&& x){ test_result(x); } ); } [endsect] [section 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 `variant`s. 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::template fn, 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`. 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::template fn, 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::template fn, 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 ); } [endsect] [endsect]