Cpp Notes

cpp20_lambda_familiar_template_syntax

C++20 Lambdas: Familiar Template Syntax - Ben Deane

Basics

// C++14: I can only say "auto" here
auto fnc_in_cpp14 = [](auto&& x, auto&& y) {
  // I have to recover the types through decltype
  something_else(std::forward<decltype(x)>(x), std::forward<decltype(y)>(y));
};

// In C++20, we get "Familiar Template Syntax lambdas".

// C++20: I can name the types now
auto fnc_in_cpp20 = []<class X, class Y>(X&& x, Y&& y) {
  // And use the names of type here - no longer need the decltype
  something_else(std::forward<X>(x), std::forward<Y>(y));
};

// P0428 Familiar Template Syntax for Generic Lambdas
// A "template head" is now in the syntax,
// to make access to the deduced types much easier.

So where would we use it?

// where would you use it? consider `std::apply`: where I need to delegate to
// another function just to destructure the template argument.

namespace detail {
template <class F, class Tuple, std::size_t... Is>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t,
                                    std::index_sequence<Is...>) {
  return std::invoke(std::forward<F>(f),
                     std::get<Is>(std::forward<Tuple>(t))...);
}
}  // namespace detail

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t) {
  return detail::apply_impl(
      std::forward<F>(f), std::forward<Tuple>(t),
      std::make_index_sequence<
          std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}
// C++20: How about immediately-invoking a lambda instead of delegating?
// A familiar template syntax immediately invoked lambda expression(FTSIILE).

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t) {
  return [&]<auto... Is>(std::index_sequence<Is...>) {
    return std::invoke(std::forward<F>(f),
                       std::get<Is>(std::forward<Tuple>(t))...);
  }
  (std::make_index_sequence<
      std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

Another example: for_each on tuple

for_each on unary function - not too bad

// simple unary for_each on tuples, using std::apply.
template <class Tuple, class F>
F for_each(Tuple&& t, F&& f) {
  return std::apply(
             [&]<class... Ts>(Ts&&... ts) { (f(std::forward<Ts>(ts)), ...); },
             std::forward<Tuple>(t)),
         f;
}
// But it gets trickier if we want a binary function over 2 tuples,
// or an n-ary function over multiple tuples.

Not so good when we even with 2-ary function for_each

// C++ 14 way, the same, you have to use a helper function
template <class Tuple1, class Tuple2, class F, std::size_t... Is>
F for_each_impl(Tuple1&& t1, Tuple2&& t2, F&& f, std::index_sequence<Is...>) {
  return (std::invoke(f, std::get<Is>(std::forward<Tuple1>(t1)),
                         std::get<Is>(std::forward<Tuple2>(t2))),
          ...),
         f;
}
template <class Tuple1, class Tuple2, class F>
constexpr decltype(auto) for_each(Tuple1&& t1, Tuple2&& t2, F&& f) {
  return for_each_impl(
      std::forward<Tuple1>(t1), std::forward<Tuple2>(t2), std::forward<F>(f),
      std::make_index_sequence<
          std::tuple_size<std::remove_reference_t<Tuple1>>::value>{});
}
// C++ 20 way:
// Use reference-capture and variadic template head.

template <class Tuple1, class Tuple2, class F>
constexpr decltype(auto) for_each(Tuple1&& t1, Tuple2&& t2, F&& f) {
  return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
    return (std::invoke(f, std::get<Is>(std::forward<Tuple1>(t1)),
                        std::get<Is>(std::forward<Tuple2>(t2))),
            ...),
           f;
  }
  (std::make_index_sequence<
      std::tuple_size<std::remove_reference_t<Tuple1>>::value>{});
}

// Less forwarding, less noise.

Conclusion A LITTLE C++20 GOODNESS

  • If you're delegating to an ancillary function just to destructure a type, consider a Familiar Template Syntax Immediately-Invoked Lambda Expression.
    • No more decltype
    • Reference-capture provides automatic value category preservation
    • Simpler argument handling
    • Especially useful for destructuring with std::index_sequence
    • FTSIILES FTW!