diff --git a/doc/html/mp11.html b/doc/html/mp11.html index a1235fd..8994454 100644 --- a/doc/html/mp11.html +++ b/doc/html/mp11.html @@ -33,9 +33,16 @@

Table of Contents

Overview
+
Definitions
+
Examples
+
+
Generating Test + Cases
+
Computing Return + Types
+
Reference
-
Definitions
Integral Constants, <boost/mp11/integral.hpp>
mp_bool<B>
@@ -175,7 +182,7 @@ 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 @@

+

+ 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; };
+
+
+
+ +
+ +

+ 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 and U, for example t + + u. + We want to test whether result<T, + U> + gives correct results for various combinations of T + and U, 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 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<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 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<int, + float>. + Let's write our own function template rvisit + that is the same as visit + but returns a variant: +

+
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 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<F> + that returns the result of the application of F + to arguments of type T...: +

+
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, 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<Qret<F>::template invoke, std::remove_reference_t<V>...>;
+
+

+ Why does this work? mp_product<F, + L1<T1...>, L2<T2...>, ..., Ln<Tn...>> returns L1<F<U1, U2, ..., Un>, ...>, + 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<short, int, float> + 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<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 @@

-

- 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; };
-
-
-
- +

Last revised: March 17, 2017 at 00:17:37 GMT

Last revised: March 17, 2017 at 03:26:57 GMT


diff --git a/doc/mp11.qbk b/doc/mp11.qbk index 6a3052e..f1f55e9 100644 --- a/doc/mp11.qbk +++ b/doc/mp11.qbk @@ -23,17 +23,14 @@ [template endsimplesect[] [block '''''']] -[section Overview] - [include mp11/overview.qbk] - -[endsect] +[include mp11/definitions.qbk] +[include mp11/examples.qbk] [section Reference] The contents of the library are in namespace `boost::mp11`, unless specified otherwise. -[include mp11/definitions.qbk] [include mp11/integral.qbk] [include mp11/list.qbk] [include mp11/utility.qbk] diff --git a/doc/mp11/examples.qbk b/doc/mp11/examples.qbk new file mode 100644 index 0000000..1b5a367 --- /dev/null +++ b/doc/mp11/examples.qbk @@ -0,0 +1,175 @@ +[/ + / 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 invoke = 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 invoke, 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 invoke, std::remove_reference_t...>>; + +and we're done: + + #include + #include + #include + #include + #include + #include + + using namespace boost::mp11; + + template struct Qret + { + template using invoke = decltype( std::declval()( std::declval()... ) ); + }; + + template auto rvisit( F&& f, V&&... v ) + { + using R = mp_unique::template invoke, 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] diff --git a/doc/mp11/overview.qbk b/doc/mp11/overview.qbk index 89f7cff..9c1389d 100644 --- a/doc/mp11/overview.qbk +++ b/doc/mp11/overview.qbk @@ -6,6 +6,8 @@ / http://www.boost.org/LICENSE_1_0.txt) /] +[section Overview] + Mp11 is a C++11 metaprogramming library based on template aliases and variadic templates. It implements the approach outlined in the article [@http://pdimov.com/cpp2/simple_cxx11_metaprogramming.html Simple C++11 metaprogramming] @@ -15,15 +17,17 @@ The general principles upon which Mp11 is built are that algorithms and metafunc template aliases of the form `F` and data structures are lists of the form `L`, with the library placing no requirements on `L`. `mp_list` is the built-in list type, but `std::tuple`, `std::pair` and `std::variant` are also perfectly -legitimate list types, although of course `std:pair`, due to having exactly two elements, +legitimate list types, although of course `std::pair`, due to having exactly two elements, is not resizeable and will consequently not work with algorithms that need to add or remove elements. Another distinguishing feature of this approach is that lists (`L`) have the same form as metafunctions (`F`) and can therefore be used as such. For example, applying `std::add_pointer_t` -to the list `std::tuple` by way of `mp_transform, std::add_pointer_t>` +to the list `std::tuple` by way of `mp_transform>` gives us `std::tuple`, but we can also apply `mp_list` to the same tuple: - using R = mp_transform, mp_list>; + using R = mp_transform>; and get `std::tuple, mp_list>`. + +[endsect]