|
||||
File indexing completed on 2025-01-18 09:37:58
0001 /*! 0002 @file 0003 Defines `boost::hana::demux`. 0004 0005 Copyright Louis Dionne 2013-2022 0006 Distributed under the Boost Software License, Version 1.0. 0007 (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) 0008 */ 0009 0010 #ifndef BOOST_HANA_FUNCTIONAL_DEMUX_HPP 0011 #define BOOST_HANA_FUNCTIONAL_DEMUX_HPP 0012 0013 #include <boost/hana/basic_tuple.hpp> 0014 #include <boost/hana/config.hpp> 0015 #include <boost/hana/detail/decay.hpp> 0016 0017 #include <cstddef> 0018 #include <utility> 0019 0020 0021 namespace boost { namespace hana { 0022 //! @ingroup group-functional 0023 //! Invoke a function with the results of invoking other functions 0024 //! on its arguments. 0025 //! 0026 //! Specifically, `demux(f)(g...)` is a function such that 0027 //! @code 0028 //! demux(f)(g...)(x...) == f(g(x...)...) 0029 //! @endcode 0030 //! 0031 //! Each `g` is called with all the arguments, and then `f` is called 0032 //! with the result of each `g`. Hence, the arity of `f` must match 0033 //! the number of `g`s. 0034 //! 0035 //! This is called `demux` because of a vague similarity between this 0036 //! device and a demultiplexer in signal processing. `demux` takes what 0037 //! can be seen as a continuation (`f`), a bunch of functions to split a 0038 //! signal (`g...`) and zero or more arguments representing the signal 0039 //! (`x...`). Then, it calls the continuation with the result of 0040 //! splitting the signal with whatever functions where given. 0041 //! 0042 //! @note 0043 //! When used with two functions only, `demux` is associative. In other 0044 //! words (and noting `demux(f, g) = demux(f)(g)` to ease the notation), 0045 //! it is true that `demux(demux(f, g), h) == demux(f, demux(g, h))`. 0046 //! 0047 //! 0048 //! Signature 0049 //! --------- 0050 //! The signature of `demux` is 0051 //! \f[ 0052 //! \mathtt{demux} : 0053 //! (B_1 \times \dotsb \times B_n \to C) 0054 //! \to ((A_1 \times \dotsb \times A_n \to B_1) 0055 //! \times \dotsb 0056 //! \times (A_1 \times \dotsb \times A_n \to B_n)) 0057 //! \to (A_1 \times \dotsb \times A_n \to C) 0058 //! \f] 0059 //! 0060 //! This can be rewritten more tersely as 0061 //! \f[ 0062 //! \mathtt{demux} : 0063 //! \left(\prod_{i=1}^n B_i \to C \right) 0064 //! \to \prod_{j=1}^n \left(\prod_{i=1}^n A_i \to B_j \right) 0065 //! \to \left(\prod_{i=1}^n A_i \to C \right) 0066 //! \f] 0067 //! 0068 //! 0069 //! Link with normal composition 0070 //! ---------------------------- 0071 //! The signature of `compose` is 0072 //! \f[ 0073 //! \mathtt{compose} : (B \to C) \times (A \to B) \to (A \to C) 0074 //! \f] 0075 //! 0076 //! A valid observation is that this coincides exactly with the type 0077 //! of `demux` when used with a single unary function. Actually, both 0078 //! functions are equivalent: 0079 //! @code 0080 //! demux(f)(g)(x) == compose(f, g)(x) 0081 //! @endcode 0082 //! 0083 //! However, let's now consider the curried version of `compose`, 0084 //! `curry<2>(compose)`: 0085 //! \f[ 0086 //! \mathtt{curry_2(compose)} : (B \to C) \to ((A \to B) \to (A \to C)) 0087 //! \f] 0088 //! 0089 //! For the rest of this explanation, we'll just consider the curried 0090 //! version of `compose` and so we'll use `compose` instead of 0091 //! `curry<2>(compose)` to lighten the notation. With currying, we can 0092 //! now consider `compose` applied to itself: 0093 //! \f[ 0094 //! \mathtt{compose(compose, compose)} : 0095 //! (B \to C) \to (A_1 \to A_2 \to B) \to (A_1 \to A_2 \to C) 0096 //! \f] 0097 //! 0098 //! If we uncurry deeply the above expression, we obtain 0099 //! \f[ 0100 //! \mathtt{compose(compose, compose)} : 0101 //! (B \to C) \times (A_1 \times A_2 \to B) \to (A_1 \times A_2 \to C) 0102 //! \f] 0103 //! 0104 //! This signature is exactly the same as that of `demux` when given a 0105 //! single binary function, and indeed they are equivalent definitions. 0106 //! We can also generalize this further by considering 0107 //! `compose(compose(compose, compose), compose)`: 0108 //! \f[ 0109 //! \mathtt{compose(compose(compose, compose), compose)} : 0110 //! (B \to C) \to (A_1 \to A_2 \to A_3 \to B) 0111 //! \to (A_1 \to A_2 \to A_3 \to C) 0112 //! \f] 0113 //! 0114 //! which uncurries to 0115 //! \f[ 0116 //! \mathtt{compose(compose(compose, compose), compose)} : 0117 //! (B \to C) \times (A_1 \times A_2 \times A_3 \to B) 0118 //! \to (A_1 \times A_2 \times A_3 \to C) 0119 //! \f] 0120 //! 0121 //! This signature is exactly the same as that of `demux` when given a 0122 //! single ternary function. Hence, for a single n-ary function `g`, 0123 //! `demux(f)(g)` is equivalent to the n-times composition of `compose` 0124 //! with itself, applied to `g` and `f`: 0125 //! @code 0126 //! demux(f)(g) == fold_left([compose, ..., compose], id, compose)(g, f) 0127 //! // ^^^^^^^^^^^^^^^^^^^^^ n times 0128 //! @endcode 0129 //! 0130 //! More information on this insight can be seen [here][1]. Also, I'm 0131 //! not sure how this insight could be generalized to more than one 0132 //! function `g`, or if that is even possible. 0133 //! 0134 //! 0135 //! Proof of associativity in the binary case 0136 //! ----------------------------------------- 0137 //! As explained above, `demux` is associative when it is used with 0138 //! two functions only. Indeed, given functions `f`, `g` and `h` with 0139 //! suitable signatures, we have 0140 //! @code 0141 //! demux(f)(demux(g)(h))(x...) == f(demux(g)(h)(x...)) 0142 //! == f(g(h(x...))) 0143 //! @endcode 0144 //! 0145 //! On the other hand, we have 0146 //! @code 0147 //! demux(demux(f)(g))(h)(x...) == demux(f)(g)(h(x...)) 0148 //! == f(g(h(x...))) 0149 //! @endcode 0150 //! 0151 //! and hence `demux` is associative in the binary case. 0152 //! 0153 //! 0154 //! Example 0155 //! ------- 0156 //! @include example/functional/demux.cpp 0157 //! 0158 //! [1]: http://stackoverflow.com/q/5821089/627587 0159 #ifdef BOOST_HANA_DOXYGEN_INVOKED 0160 constexpr auto demux = [](auto&& f) { 0161 return [perfect-capture](auto&& ...g) { 0162 return [perfect-capture](auto&& ...x) -> decltype(auto) { 0163 // x... can't be forwarded unless there is a single g 0164 // function, or that could cause double-moves. 0165 return forwarded(f)(forwarded(g)(x...)...); 0166 }; 0167 }; 0168 }; 0169 #else 0170 template <typename F> 0171 struct pre_demux_t; 0172 0173 struct make_pre_demux_t { 0174 struct secret { }; 0175 template <typename F> 0176 constexpr pre_demux_t<typename detail::decay<F>::type> operator()(F&& f) const { 0177 return {static_cast<F&&>(f)}; 0178 } 0179 }; 0180 0181 template <typename Indices, typename F, typename ...G> 0182 struct demux_t; 0183 0184 template <typename F> 0185 struct pre_demux_t { 0186 F f; 0187 0188 template <typename ...G> 0189 constexpr demux_t<std::make_index_sequence<sizeof...(G)>, F, 0190 typename detail::decay<G>::type...> 0191 operator()(G&& ...g) const& { 0192 return {make_pre_demux_t::secret{}, this->f, static_cast<G&&>(g)...}; 0193 } 0194 0195 template <typename ...G> 0196 constexpr demux_t<std::make_index_sequence<sizeof...(G)>, F, 0197 typename detail::decay<G>::type...> 0198 operator()(G&& ...g) && { 0199 return {make_pre_demux_t::secret{}, static_cast<F&&>(this->f), static_cast<G&&>(g)...}; 0200 } 0201 }; 0202 0203 template <std::size_t ...n, typename F, typename ...G> 0204 struct demux_t<std::index_sequence<n...>, F, G...> { 0205 template <typename ...T> 0206 constexpr demux_t(make_pre_demux_t::secret, T&& ...t) 0207 : storage_{static_cast<T&&>(t)...} 0208 { } 0209 0210 basic_tuple<F, G...> storage_; 0211 0212 template <typename ...X> 0213 constexpr decltype(auto) operator()(X&& ...x) const& { 0214 return hana::at_c<0>(storage_)( 0215 hana::at_c<n+1>(storage_)(x...)... 0216 ); 0217 } 0218 0219 template <typename ...X> 0220 constexpr decltype(auto) operator()(X&& ...x) & { 0221 return hana::at_c<0>(storage_)( 0222 hana::at_c<n+1>(storage_)(x...)... 0223 ); 0224 } 0225 0226 template <typename ...X> 0227 constexpr decltype(auto) operator()(X&& ...x) && { 0228 return static_cast<F&&>(hana::at_c<0>(storage_))( 0229 static_cast<G&&>(hana::at_c<n+1>(storage_))(x...)... 0230 ); 0231 } 0232 }; 0233 0234 template <typename F, typename G> 0235 struct demux_t<std::index_sequence<0>, F, G> { 0236 template <typename ...T> 0237 constexpr demux_t(make_pre_demux_t::secret, T&& ...t) 0238 : storage_{static_cast<T&&>(t)...} 0239 { } 0240 0241 basic_tuple<F, G> storage_; 0242 0243 template <typename ...X> 0244 constexpr decltype(auto) operator()(X&& ...x) const& { 0245 return hana::at_c<0>(storage_)( 0246 hana::at_c<1>(storage_)(static_cast<X&&>(x)...) 0247 ); 0248 } 0249 0250 template <typename ...X> 0251 constexpr decltype(auto) operator()(X&& ...x) & { 0252 return hana::at_c<0>(storage_)( 0253 hana::at_c<1>(storage_)(static_cast<X&&>(x)...) 0254 ); 0255 } 0256 0257 template <typename ...X> 0258 constexpr decltype(auto) operator()(X&& ...x) && { 0259 return static_cast<F&&>(hana::at_c<0>(storage_))( 0260 static_cast<G&&>(hana::at_c<1>(storage_))(static_cast<X&&>(x)...) 0261 ); 0262 } 0263 }; 0264 0265 BOOST_HANA_INLINE_VARIABLE constexpr make_pre_demux_t demux{}; 0266 #endif 0267 }} // end namespace boost::hana 0268 0269 #endif // !BOOST_HANA_FUNCTIONAL_DEMUX_HPP
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |