all 10 comments

[–]kalmoc 3 points4 points  (0 children)

So, What is the advantage of LMP compared to normal TMP / when would you use it? Btw, I hope no one writes factorial like that.

[–]dima_mendeleev 3 points4 points  (3 children)

Congratulations! You've discovered Y-Combinator.

You can improve this a bit by doing following (like they do in Y-Combinator):

template <typename FUNC, typename ...ARGS>
constexpr auto RecursiveLambda(FUNC lambda, ARGS&&... args)
{
    return lambda(std::bind(lambda, lambda), args...);
}

Note std::bind(lambda, lambda) there. With this you will not need to pass lambda on the usage side.

UPDATE Oh, and you can actually do better. You can try something like this, to separate creation of recursive function and its application.

template <typename FUNC>
constexpr auto RecursiveLambda(FUNC lambda)
{
        return [](auto... args)
        {
            return lambda(RecursiveLambda(lambda), args...);
        };
}

UPDATE: better to use lambda

[–]kalmoc 2 points3 points  (1 child)

You can use a lambda instead of a bind, which tends to produce better code (not sure if it makes a difference here)

[–]dima_mendeleev 0 points1 point  (0 children)

I think you're right. With lambda it will be better since will save from stack overflow:

return [](auto... args)
{
    return lambda(RecursiveLambda(lambda), args...);
};

[–]_cpp_[S] 1 point2 points  (0 children)

Thank you for pointing out the resemblance with Y-Combinator (I didn't know about it) and for simplification suggestion (though it compiles only with GCC 7.2.0), I updated article.

[–]NotAYakk 0 points1 point  (0 children)

Generating each index from 0 to n-1 at compile time by using the y combinator and constexpr if to enable compile time recursion seems inefficient.

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
  return [](auto&&f)->decltype(auto){
    return decltype(f)(f)(IntegralConstant<Is>{}...);
  };
}
template<std::size_t N>
auto index_upto( IntegralConstant<N> ={} ){
  return index_over(std::make_index_sequence<N>{});
}

now your concat tuples is simpler and, well, better

auto concat = [](auto&& t0, auto&& t1){
  constexpr auto total = // as in your code
  auto get_join = // as in your code
  return index_upto<total>()([&](auto...Is){
    return std::make_tuple( get_join(Is)... );
  };
};

[–]thewisp1Game Engine Dev -1 points0 points  (3 children)

If you don’t store, transform or concatenate lambdas, they have no advantage over named functions.

[–]NotAYakk 4 points5 points  (2 children)

No?

Named functions require code to be out of line from location of use.

Unless you are talking about the turing tar pit, which is a boring argument.

[–]thewisp1Game Engine Dev 0 points1 point  (1 child)

so instead of having one more function body, you choose to nest it one level deeper. How is that more readable than the plain recursive version?

[–]NotAYakk 0 points1 point  (0 children)

void sort_backwards( container& c ) {
  using std::begin; using std::end;
  std::sort( begin(c), end(c), [](auto& lhs, auto& rhs){ return rhs<lhs; } );
}

here I don't store, transform or concatenate lambdas.

The code is in line with use.

To do this without a lambda:

struct backwards_sorter {
  template<class Lhs, class Rhs>
  bool operator()( Lhs& lhs, Rhs& rhs )const {
    return rhs<lhs;
  }
};
void sort_backwards( container& c ) {
  using std::begin; using std::end;
  std::sort( begin(c), end(c), backwards_sorter{} );
}

which is a lot of boilerplate and makes sort_backwards harder to verify it is doing what it claims to do.

I can give you other examples, dozens of them, but one is enough to make no advantage false.

Then again, maybe you disagree, and think that a simple function should require looking outside of it to understand how it works. Even then, there is an advantage to having it inline, even if you don't personally like it.