Skip to content

amarmer/Lambda-Metaprogramming-LMP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

87 Commits
 
 

Repository files navigation

Lambda Metaprogramming (LMP)

Published in https://isocpp.org/blog/2018/01/recursive-lambda-and-metaprogramming

Word metaprogramming is used together with word template - template metaprogramming (TMP).
Here is described that metaprogramming can be used with word lambda as well - lambda metaprogramming (LMP).

For instance in a template function foo allocates array on stack using std::array.

template <int SIZE>
void foo() {
  std::array<char, SIZE> arr;
}

foo<100>();

This is how it can be implemented using lambda.

auto foo = [](auto size) {
  std::array<char, size> arr;
};
    
foo(std::integral_constant<int, 100>());

std::array<char, size> compiles because size is std::integral_constant which has constexpr cast operator to int.

Another way to implement it.

auto foo = [](auto* pSize) {
  std::array<char, sizeof(*pSize)> arr;
};
    
foo((char (*)[100])());

TMP is often used with recursion. For instance, a classic example of calculating factorial using TMP.

template <unsigned int n>
constexpr int factorial() {
  if constexpr(0 == n)
    return 1;
  else
    return n * factorial<n - 1>();
};

Bellow is implementation of factorial using LMP.

Lambda doesn't have a name, and it is not possible to call it recursively.
In order to call lambda recursively, a helper function RecursiveLambda is used.

template <typename LAMBDA>
constexpr auto RecursiveLambda(LAMBDA lambda)
{
  return [lambda](auto&&... args)
  {
    return lambda(lambda, std::forward<decltype(args)>(args)...);
  };
}

Factorial implementation using RecursiveLambda.

auto factorial = RecursiveLambda(
  [](auto lambda, auto n) {
    if constexpr(n == 0)
      return 1;
    else
      return n * lambda(lambda, IntegralConstant<n - 1>());
  }
);

constexpr auto factorial_5 = factorial(std::integral_constant<int, 5>());

Bellow are LMP examples with tuple.

TupleSize is a helper function which simplifies getting size of a tuple.

template <typename TPL>
constexpr auto TupleSize() { return std::tuple_size<typename std::decay<TPL>::type>::value; }

And IntegralConstant is a helper alias for std::integral_constant.

template <auto N>
using IntegralConstant = std::integral_constant<decltype(N), N>;

Example of lambda function printLambda which enumerates a tuple and converts each tuple's element to string.
Then this function can be used to print tuple's elements using format lambda which is passed as a parameter.

std::function<void(std::function<void(int, const std::string&)>)> printLambda;

printLambda = [&tpl](auto format) {
  RecursiveLambda(
    [](auto lambda, const auto& tpl, auto format, auto index) {
      if constexpr(index < TupleSize<decltype(tpl)>()) {
        std::stringstream strStream;
        strStream << std::get<index>(tpl);

        format(index, strStream.str());

        lambda(lambda, tpl, format, IntegralConstant<index + 1>());
      }
    }
  )(tpl, format, IntegralConstant<0>());
};

printLambda([](int index, const std::string& tupleEl) {
  std::cout << "tuple[" << index << "]: " << tupleEl << std::endl;
});

Example of how a new tuple with reversed elements can be created.

auto reversedTpl = RecursiveLambda(
  [&tpl](auto lambda, auto index, auto&&...args) {
    if constexpr(0 == sizeof...(args))
      return lambda(lambda, IntegralConstant<index + 1>(), std::make_tuple(std::get<index>(tpl)));
    else {
      if constexpr(index < TupleSize<decltype(tpl)>())
        return lambda(lambda, 
                      IntegralConstant<index + 1>(), 
                      std::tuple_cat(std::make_tuple(std::get<index>(tpl)),
                                     std::forward<decltype(args)>(args)...));
      else
        return std::forward<decltype(args)...>(args...);
    }
  }
)(IntegralConstant<0>());

Code above can be simplified if added second parameter std::tuple<>().

auto reversedTpl = RecursiveLambda(
  [&tpl](auto lambda, auto index, const auto& curTpl) {
    if constexpr(index < TupleSize<decltype(tpl)>())
      return lambda(lambda, 
                    IntegralConstant<index + 1>(), 
                    std::tuple_cat(std::make_tuple(std::get<index>(tpl)), curTpl));
    else
      return curTpl;
  }
)(IntegralConstant<0>(), std::tuple<>());

Example of how to cancatenate 2 tuples.

auto catTpl = RecursiveLambda(
  [&tpl1, &tpl2](auto lambda, auto index, auto&&...args) {
    constexpr auto total = TupleSize<decltype(tpl1)>() + TupleSize<decltype(tpl2)>();
    if constexpr(0 == total)
      return std::tuple<>();
    else {
      auto getEl = [&](auto index) {
        if constexpr(index < TupleSize<decltype(tpl1)>())
          return std::get<index>(tpl1);
        else
          return std::get<index - TupleSize<decltype(tpl1)>()>(tpl2);
      };

      if constexpr(0 == sizeof...(args))
        return lambda(lambda, IntegralConstant<index + 1>(), getEl(index));
      else
        if constexpr(index < total)
          return lambda(lambda, 
                        IntegralConstant<index + 1>(), 
                        std::forward<decltype(args)>(args)..., 
                        getEl(index));
        else
          return std::make_tuple(std::forward<decltype(args)>(args)...);
    }
  }
)(IntegralConstant<0>());

After posting this arcticle on reddit/cpp, reddit.com/user/dima_mendeleev suggested a simplification,
with which no need to call lambda(lambda, ...), just lambda(...). It compiles only with GCC 7.2.0.

template <typename LAMBDA>
constexpr auto RecursiveLambda(LAMBDA lambda)
{
  return [lambda](auto&&... args)
  {
    return lambda(RecursiveLambda(lambda), std::forward<decltype(args)>(args)...);
  };
}

With modified RecursiveLambda function above, which creates reversed tuple, it looks like:

auto reversedTpl = RecursiveLambda(
  [&tpl](auto lambda, auto index, const auto& curTpl) {
    if constexpr(index < TupleSize<decltype(tpl)>())
      return lambda(IntegralConstant<index + 1>(),
                    std::tuple_cat(std::make_tuple(std::get<index>(tpl)), curTpl));
    else
      return curTpl;
  }
)(IntegralConstant<0>(), std::tuple<>());

It would be usefull in C++ to have keyword lambda which allows to refer to a lambda function from inside the function,
similar to the keyword this inside a class. Then code above could look like:

auto reversedTpl = [&tpl](auto index, const auto& curTpl) {
  if constexpr(index < TupleSize<decltype(tpl)>())
    return lambda(IntegralConstant<index + 1>(), 
                  std::tuple_cat(std::make_tuple(std::get<index>(tpl)), curTpl));
  else
    return curTpl;
}(IntegralConstant<0>(), std::tuple<>());

About

Lambda Metaprogramming (LMP)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published