
Zach Laine <whatwasthataddress <at> gmail.com> writes:
On Sun, Mar 8, 2015 at 2:56 PM, Eric Niebler <eniebler <at> boost.org> wrote:
[...]
As for lazy branches, that can also be handled simply by let/defer:
// Test that the unselected branch does not get evaluated: template<typename T> using test_lazy_if_ = let<lazy::if_<std::is_void<T>, T, defer<std::pair, T> > >; static_assert(std::is_same<test_lazy_if_≤void>, void>::value, "");
And don't forget this part! :)
Oops, I forgot. Ok, so lazy branches in Hana work as follows. First, you use the `eval_if` function, which takes a condition and two branches in the form of lambdas. But that's not all; the lambdas must accept a parameter (usually called _), which can be used to defer the compile-time evaluation of expressions as required. An example: template <typename N> auto fact(N n) { return hana::eval_if(n == hana::int_<0>, [](auto _) { return hana::int_<1>; }, [=](auto _) { return n * fact(_(n) - hana::int_<1>); } ); } What happens here is that `eval_if` will pass an identity function to the selected branch. Hence, `_(x)` is always the same as `x`, but the compiler can't tell until the lambda has been called! Hence, the compiler has to wait before it instantiates the body of the lambda and no infinite recursion happens. Also note that `always` can be used to make `eval_if` easier to work with: template <typename N> auto fact(N n) { return hana::eval_if(n == hana::int_<0>, always(hana::int_<1>), [=](auto _) { return n * fact(_(n) - hana::int_<1>); } ); } A lot of sugar could be added to `eval_if`, like a better syntax (similar to Phoenix's). Other obvious extensions would be that nullary lambdas could be supported too. None of this was not done yet because I lack time and wanted to concentrate on the "essence", not the sugar. Also, there are several caveats. First, because we're using lambdas, it means that the function's result can't be used in a constant expression. This is IMO a stupid limitation of the language and one that should be lifted. If someone is willing to team up with me for writing a proposal, I would be interested. The second caveat is that compilers currently have several bugs regarding deeply nested lambdas with captures. So you always risk crashing the compiler, but this is a question of time before it is not a problem anymore. Finally, it means that conditionals can't be written directly inside unevaluated contexts. The reason is that a lambda can't appear in an unevaluated context, for example in `decltype`. There are good reasons for this restriction in the language, but the current wording is way too strict IMO. I would be willing to team up for a paper regarding this too. One way to workaround this is to completely lift your type computations into variable templates instead. So instead of writing e.g. (stupid example, just to show) template <typename T> struct f : decltype(eval_if(true_, [](auto _) { return type<T>; }, [](auto _) { return type<T>; } )) { }; you could instead write template <typename T> auto f_impl(_type<T> t) { return eval_if(true_, [](auto) { return type<T>; }, [](auto) { return type<T>; } ); } template <typename T> using f = decltype(f_impl(type<T>)); Now, this hoop-jumping only has to be done in one place, because you use (or should be using) normal function notation everywhere else in your metaprogram to perform type computations. So the syntactic cost is amortized. Also, it should be the case that template <typename T> auto f_impl = eval_if(true_, [](auto) { return type<T>; }, [](auto) { return type<T>; } ); is valid. Hence, you could use variable templates to easily bridge between Hana and any type-only (weaker!) interface. However, the above currently fails to compile on Clang (it used to work, I'll investigate and fill a bug report). Regards, Louis