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 +
+value
that 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
+T
andU
, 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 +variant
s. So for instance, + if thevariant
v1
contains1
, + and thevariant
v2
contains2.0f
, +std::visit(f, v1, v2)
+ will callf(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
+ andv2
are 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 eitherint
orfloat
depending on whatv1
+ andv2
hold. ++ A type that can hold either
+int
+ orfloat
already 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
. +R
is 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
+Qret
in hand, avariant
of 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>, ...>
, + whereUi
traverse 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>
+ andF
is[]( + auto const& x, auto const& y ){ return x + y; }
. This + will generateR
of length + 9, one per each combination, but many of those elements will be the same, + eitherint
orfloat
, 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 -
-value
that 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 |