From 077d46820fc1df948af3fbe2b57af3818f84248f Mon Sep 17 00:00:00 2001
From: Peter Dimov Table of Contents
-
<boost/mp11/integral.hpp>
mp_bool<B>mp_list<T...>
is the built-in list type, but std::tuple<T...>,
std::pair<T1, T2> and std::variant<T...>
- are also perfectly legitimate list types, although of course std:pair<T1,
+ are also perfectly legitimate list types, although of course std::pair<T1,
T2>,
due to having exactly two elements, is not resizeable and will consequently
not work with algorithms that need to add or remove elements.
@@ -184,10 +191,11 @@
Another distinguishing feature of this approach is that lists (L<T...>) have the same form as metafunctions
(F<T...>)
and can therefore be used as such. For example, applying std::add_pointer_t
- to the list std::tuple<int, float> by way of mp_transform<std::tuple<int, float>, std::add_pointer_t> gives us std::tuple<int*, float*>, but we can also apply mp_list
+ to the list std::tuple<int, float> by way of mp_transform<std::add_pointer_t,
+ std::tuple<int, float>> gives us std::tuple<int*, float*>, but we can also apply mp_list
to the same tuple:
using R = mp_transform<std::tuple<int, float>, mp_list>; +using R = mp_transform<mp_list, std::tuple<int, float>>;and get
std::tuple<mp_list<int>, mp_list<float>>. @@ -195,6 +203,271 @@+++Definitions +
+ A list is a — possibly but not necessarily variadic — template + class whose parameters are all types, for example
+mp_list<char[], + void>, +mp_list<>, +std::tuple<int, float, char>, +std::pair<int, float>,std::shared_ptr<X>. ++ A metafunction is a class template or a template alias + whose parameters are all types, for example
+std::add_pointer_t, +std::is_const,mp_second, +mp_push_front,mp_list,std::tuple, +std::pair,std::shared_ptr, + or +template<class...> using F1 = void; +template<class T> using F2 = T*; +template<class... T> using F3 = std::integral_constant<std::size_t, sizeof...(T)>; +++ A quoted metafunction is a class with a public metafunction + called
+invoke, for example +struct Q1 { template<class...> using invoke = void; }; +struct Q2 { template<class T> using invoke = T*; }; +struct Q3 { template<class... T> using invoke = std::integral_constant<std::size_t, sizeof...(T)>; }; +++ An integral constant type is a class with a public member +
+valuethat is an integral constant + in the C++ sense. For example,std::integral_constant<int, + 7>, + or +struct N { static int constexpr value = 2; }; ++++++Examples +
+ +++ Let's suppose that we have written a metafunction
+result<T, + U>: +template<class T> using promote = std::common_type_t<T, int>; +template<class T, class U> using result = std::common_type_t<promote<T>, promote<U>>; +++ that ought to represent the result of an arithmetic operation on the integer + types
+TandU, for examplet + + u. + We want to test whetherresult<T, + U>+ gives correct results for various combinations ofT+ andU, so we write the function +template<class T1, class T2> void test_result() +{ + using T3 = decltype( T1() + T2() ); + using T4 = result<T1, T2>; + + std::cout << ( std::is_same<T3, T4>::value? "[PASS]": "[FAIL]" ) << std::endl; +} +++ and then need to call it a substantial number of times: +
+int main() +{ + test_result<char, char>(); + test_result<char, short>(); + test_result<char, int>(); + test_result<char, unsigned>(); + // ... +} +++ 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 <boost/mp11.hpp> +#include <boost/tuple_for_each.hpp> +#include <boost/core/demangle.hpp> +#include <type_traits> +#include <iostream> +#include <typeinfo> + +using namespace boost::mp11; + +template<class T> std::string name() +{ + return boost::core::demangle( typeid(T).name() ); +} + +template<class T> using promote = std::common_type_t<T, int>; +template<class T, class U> using result = std::common_type_t<promote<T>, promote<U>>; + +template<class T1, class T2> void test_result( mp_list<T1, T2> const& ) +{ + using T3 = decltype( T1() + T2() ); + using T4 = result<T1, T2>; + + std::cout << ( std::is_same<T3, T4>::value? "[PASS] ": "[FAIL] " ) << name<T1>() << " + " << name<T2>() << " -> " << name<T3>() << ", result: " << name<T4>() << " " << std::endl; +} + +int main() +{ + using L = std::tuple<char, short, int, unsigned, long, unsigned long>; + boost::tuple_for_each( mp_product<mp_list, L, L>(), [](auto&& x){ test_result(x); } ); +} +++ +++ C++17 has a standard variant type, called
+std::variant. + It also defines a function templatestd::visit+ that can be used to apply a function to the contained value of one or more +variants. So for instance, + if thevariantv1contains1, + and thevariantv2contains2.0f, +std::visit(f, v1, v2)+ will callf(1, 2.0f). ++ However,
+std::visithas one limitation: it cannot return + a result unless all possible applications of the function have the same return + type. If, for instance,v1+ andv2are both of type +std::variant<short, int, float>, +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 eitherintorfloatdepending on whatv1+ andv2hold. ++ A type that can hold either
+int+ orfloatalready exists, called, + surprisingly enough,std::variant<int, + float>. + Let's write our own function templatervisit+ that is the same asvisit+ but returns avariant: +template<class F, class... V> auto rvisit( F&& f, V&&... v ) +{ + using R = /*...*/; + return std::visit( [&]( auto&&... x ){ return R( std::forward<F>(f)( std::forward<decltype(x)>(x)... ) ); }, std::forward<V>( v )... ); +} +++ What this does is basically calls
+std::visit+ to do the work, but instead of passing itf, + we pass a lambda that does the same asf+ except it converts the result to a common typeR. +Ris supposed to bestd::variant<...>where the ellipsis denotes the + return types of callingf+ with all possible combinations of variant values. ++ We'll first define a helper quoted metafunction
+Qret<F>+ that returns the result of the application ofF+ to arguments of typeT...: +template<class F> struct Qret +{ + template<class... T> using invoke = decltype( std::declval<F>()( std::declval<T>()... ) ); +}; +++ (Unfortunately, we can't just define this metafunction inside
+rvisit; the language prohibits defining + template aliases inside functions.) ++ With
+Qretin hand, avariantof the possible return types is + just a matter of applying it over the possible combinations of the variant + values: +using R = mp_product<Qret<F>::template invoke, std::remove_reference_t<V>...>; +++ Why does this work?
+mp_product<F, + L1<T1...>, L2<T2...>, ..., Ln<Tn...>>returnsL1<F<U1, U2, ..., Un>, ...>, + whereUitraverse all possible + combinations of list values. Since in our case allLi+ arestd::variant, the result will also bestd::variant. ++ One more step remains. Suppose that, as above, we're passing two variants + of type
+std::variant<short, int, float>+ andFis[]( + auto const& x, auto const& y ){ return x + y; }. This + will generateRof length + 9, one per each combination, but many of those elements will be the same, + eitherintorfloat, and we need to filter out the duplicates. + So, +using R = mp_unique<mp_product<Qret<F>::template invoke, std::remove_reference_t<V>...>>; +++ and we're done: +
+#include <boost/mp11.hpp> +#include <boost/core/demangle.hpp> +#include <variant> +#include <type_traits> +#include <typeinfo> +#include <iostream> + +using namespace boost::mp11; + +template<class F> struct Qret +{ + template<class... T> using invoke = decltype( std::declval<F>()( std::declval<T>()... ) ); +}; + +template<class F, class... V> auto rvisit( F&& f, V&&... v ) +{ + using R = mp_unique<mp_product<Qret<F>::template invoke, std::remove_reference_t<V>...>>; + return std::visit( [&]( auto&&... x ){ return R( std::forward<F>(f)( std::forward<decltype(x)>(x)... ) ); }, std::forward<V>( v )... ); +} + +template<class T> std::string name() +{ + return boost::core::demangle( typeid(T).name() ); +} + +int main() +{ + std::variant<signed char, unsigned char, signed short, unsigned short, int, unsigned> v1( 1 ); + + std::cout << "(" << name<decltype(v1)>() << ")v1: "; + std::visit( []( auto const& x ){ std::cout << "(" << name<decltype(x)>() << ")" << x << std::endl; }, v1 ); + + std::variant<int, float, double> v2( 2.0f ); + + std::cout << "(" << name<decltype(v2)>() << ")v2: "; + std::visit( []( auto const& x ){ std::cout << "(" << name<decltype(x)>() << ")" << x << std::endl; }, v2 ); + + auto v3 = rvisit( []( auto const& x, auto const& y ){ return x + y; }, v1, v2 ); + + std::cout << "(" << name<decltype(v3)>() << ")v3: "; + std::visit( []( auto const& x ){ std::cout << "(" << name<decltype(x)>() << ")" << x << std::endl; }, v3 ); +} +++@@ -203,48 +476,6 @@
---Definitions -
- A list is a — possibly but not necessarily variadic — template - class whose parameters are all types, for example
-mp_list<char[], - void>, -mp_list<>, -std::tuple<int, float, char>, -std::pair<int, float>,std::shared_ptr<X>. -- A metafunction is a class template or a template alias - whose parameters are all types, for example
-std::add_pointer_t, -std::is_const,mp_second, -mp_push_front,mp_list,std::tuple, -std::pair,std::shared_ptr, - or -template<class...> using F1 = void; -template<class T> using F2 = T*; -template<class... T> using F3 = std::integral_constant<std::size_t, sizeof...(T)>; --- A quoted metafunction is a class with a public metafunction - called
-invoke, for example -struct Q1 { template<class...> using invoke = void; }; -struct Q2 { template<class T> using invoke = T*; }; -struct Q3 { template<class... T> using invoke = std::integral_constant<std::size_t, sizeof...(T)>; }; --- An integral constant type is a class with a public member -
-valuethat is an integral - constant in the C++ sense. For example,std::integral_constant<int, - 7>, - or -struct N { static int constexpr value = 2; }; ---@@ -1373,7 +1604,7 @@
Last revised: March 17, 2017 at 00:17:37 GMT |
+Last revised: March 17, 2017 at 03:26:57 GMT |