1<!-- Copyright 2018 Paul Fultz II 2 Distributed under the Boost Software License, Version 1.0. 3 (http://www.boost.org/LICENSE_1_0.txt) 4--> 5 6Conditional overloading 7======================= 8 9Conditional overloading takes a set of functions and calls the first one that is callable. This is one of the ways to resolve ambiguity with overloads, but more importantly it allows an alternative function to be used when the first is not callable. 10 11Stringify 12--------- 13 14Take a look at this example of defining a `stringify` function from 15stackoverflow [here](http://stackoverflow.com/questions/30189926/metaprograming-failure-of-function-definition-defines-a-separate-function/30515874). 16 17The user would like to write `stringify` to call `to_string` where applicable 18and fallback on using `sstream` to convert to a string. Most of the top 19answers usually involve some amount of metaprogramming using either `void_t` 20or `is_detected`(see [n4502](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf)): 21 22 template<class T> 23 using to_string_t = decltype(std::to_string(std::declval<T>())); 24 25 template<class T> 26 using has_to_string = std::experimental::is_detected<to_string_t, T>; 27 28 template<typename T> 29 typename std::enable_if<has_to_string<T>{}, std::string>::type 30 stringify(T t) 31 { 32 return std::to_string(t); 33 } 34 template<typename T> 35 typename std::enable_if<!has_to_string<T>{}, std::string>::type 36 stringify(T t) 37 { 38 return static_cast<std::ostringstream&>(std::ostringstream() << t).str(); 39 } 40 41However, with Boost.HigherOrderFunctions it can simply be written like 42this: 43 44 BOOST_HOF_STATIC_LAMBDA_FUNCTION(stringify) = first_of( 45 [](auto x) BOOST_HOF_RETURNS(std::to_string(x)), 46 [](auto x) BOOST_HOF_RETURNS(static_cast<std::ostringstream&>(std::ostringstream() << x).str()) 47 ); 48 49So, using [`BOOST_HOF_RETURNS`](/include/boost/hof/returns) not only deduces the return type for the function, but it also constrains the function on whether the expression is valid or not. So by writing `BOOST_HOF_RETURNS(std::to_string(x))` then the first function will try to call `std::to_string` function if possible. If not, then the second function will be called. 50 51The second function still uses [`BOOST_HOF_RETURNS`](/include/boost/hof/returns), so the function will still be constrained by whether the `<<` stream operator can be used. Although it may seem unnecessary because there is not another function, however, this makes the function composable. So we could use this to define a `serialize` function that tries to call `stringify` first, otherwise it looks for the member `.serialize()`: 52 53 BOOST_HOF_STATIC_LAMBDA_FUNCTION(serialize) = first_of( 54 [](auto x) BOOST_HOF_RETURNS(stringify(x)), 55 [](auto x) BOOST_HOF_RETURNS(x.serialize()) 56 ); 57 58static_if 59--------- 60 61In addition, this can be used with the [`boost::hof::if_`](/include/boost/hof/if) decorator to create `static_if`-like 62constructs on pre-C++17 compilers. For example, Baptiste Wicht discusses how one could write `static_if` in C++ [here](http://baptiste-wicht.com/posts/2015/07/simulate-static_if-with-c11c14.html). 63 64He wants to be able to write this: 65 66 template<typename T> 67 void decrement_kindof(T& value){ 68 if constexpr(std::is_same<std::string, T>()){ 69 value.pop_back(); 70 } else { 71 --value; 72 } 73 } 74 75However, that isn't possible before C++17. With Boost.HigherOrderFunctions one can simply write 76this: 77 78 template<typename T> 79 void decrement_kindof(T& value) 80 { 81 eval(first_of( 82 if_(std::is_same<std::string, T>())([&](auto id){ 83 id(value).pop_back(); 84 }), 85 [&](auto id){ 86 --id(value); 87 } 88 )); 89 } 90 91The `id` parameter passed to the lambda is the [`identity`](/include/boost/hof/identity) function. As explained in the article, this is used to delay the lookup of types by making it a dependent type(i.e. the type depends on a template parameter), which is necessary to avoid compile errors. The [`eval`](/include/boost/hof/eval) function that is called will pass this `identity` function to the lambdas. 92 93The advantage of using Boost.HigherOrderFunctions instead of the solution in Baptiste 94Wicht's blog, is that [`first_of`](/include/boost/hof/conditional) allows more than just two conditions. So if 95there was another trait to be checked, such as `is_stack`, it could be written 96like this: 97 98 template<typename T> 99 void decrement_kindof(T& value) 100 { 101 eval(first_of( 102 if_(is_stack<T>())([&](auto id){ 103 id(value).pop(); 104 }), 105 if_(std::is_same<std::string, T>())([&](auto id){ 106 id(value).pop_back(); 107 }), 108 [&](auto id){ 109 --id(value); 110 } 111 )); 112 } 113 114Type traits 115----------- 116 117Furthermore, this technique can be used to write type traits as well. Jens 118Weller was looking for a way to define a general purpose detection for pointer 119operands(such as `*` and `->`). One way to accomplish this is like 120this: 121 122 // Check that T has member function for operator* and ope 123 template<class T> 124 auto has_pointer_member(const T&) -> decltype( 125 &T::operator*, 126 &T::operator->, 127 std::true_type{} 128 ); 129 130 BOOST_HOF_STATIC_LAMBDA_FUNCTION(has_pointer_operators) = first_of( 131 BOOST_HOF_LIFT(has_pointer_member), 132 [](auto* x) -> bool_constant<(!std::is_void<decltype(*x)>())> { return {}; }, 133 always(std::false_type{}) 134 ); 135 136 template<class T> 137 struct is_dereferenceable 138 : decltype(has_pointer_operators(std::declval<T>())) 139 {}; 140 141Which is much simpler than the other implementations that were written, which were 142about 3 times the amount of code(see [here](https://gist.github.com/lefticus/6fdccb18084a1a3410d5)). 143 144The `has_pointer_operators` function works by first trying to call `has_pointer_member` which returns `true_type` if the type has member functions `operator*` and `operator->`, otherwise the function is not callable. The next function is only callable on pointers, which returns true if it is not a `void*` pointer(because `void*` pointers are not dereferenceable). Finally, if none of those functions matched then the last function will always return `false_type`. 145