Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Familiar Template Syntax IILEs

elbeno, 23 October, 20196 May, 2024

A lot has already been said in the blogosphere about the use of immediately-invoked lambda expressions (IILEs) for initialization, and they’re certainly very useful.

In C++20, P0428 gives us “familiar template syntax” for lambdas. Now, instead of writing a regular generic lambda:

auto add = [] (auto x, auto y) {
  return x + y;
};

we have the option to use “familiar template syntax” to name the template arguments:

auto add = [] <typename T> (T x, T y) {
  return x + y;
};

This has several uses — see the paper for motivations listed there — but one I want to draw attention to in particular: when we use an FTSL as an IILE, it can simplify code.

Examples, you say? I have two for you.

First, consider the “possible implementation” of std::apply listed on cppreference.com:

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    return std::invoke(std::forward<F>(f), std::get<I>(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>>>{});
}

With an FTSIILE, that can become:

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

With an FTSIILE, we avoid having to forward multiple times: we can capture by reference into the lambda instead, and do the forwarding once, inside. The result for small functions like this is about half as much code, better encapsulated. No need to declare a helper any more just to destructure template arguments.

Here’s another quick example, from Jonathan Boccara’s recent blog post about STL Algorithms on Tuples. In that post, Jonathan presents for_each2, a function template that applies a binary function over two tuples:

template <class Tuple1, class Tuple2, class F, std::size_t... I>
F for_each2_impl(Tuple1&& t1, Tuple2&& t2, F&& f, std::index_sequence<I...>)
{
    return (void)std::initializer_list<int>{(std::forward<F>(f)(std::get<I>(std::forward<Tuple1>(t1)), std::get<I>(std::forward<Tuple2>(t2))),0)...}, f;
}

template <class Tuple1, class Tuple2, class F>
constexpr decltype(auto) for_each2(Tuple1&& t1, Tuple2&& t2, F&& f)
{
    return for_each2_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>{});
}

There is a small problem in the original code here: it’s not right to call std::forward<F>(f) in for_each2_impl and potentially move multiple times from the same callable. But that’s not too important; the function rewritten in C++20 could look like this:

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

Again, less std::forward boilerplate, better encapsulation, less code overall, and the generated code is identical.

So there you have it. Plain IILEs can improve initialization in regular code with less fuss that it would take to extract a small function, and FTSIILEs can now improve template code in the same way, removing the need for separate functions that previously existed only to destructure template arguments and that necessitated more forwarding boilerplate.

C++

Post navigation

Previous post
Next post

Related Posts

CppCon 2017 Trip Report

30 September, 201730 September, 2017

Last week in Bellevue, WA, around 1100 C++ programmers got together for CppCon. I love this conference – it’s a chance to meet up with my existing C++ community friends and make new ones, to learn new techniques and explore parts of C++, and to get excited about where C++…

Read More

C++ Guru Question – followup

12 August, 201430 June, 2015

(following on from C++ Guru Question) There are a few reasons why the code before didn’t work: mainly a) C++ template argument deduction works one-way with a list of candidates, it’s not H-M type inference. b) A C++ lambda is a thing with some internal type, not a std::function (although…

Read More

Exercising Ranges (part 7)

1 July, 20151 July, 2015

(Start at the beginning of the series if you want more context.) Calculus operations It’s relatively simple to do differentiation and integration of power series, since they’re just regular polynomial-style. No trigonometric identities, logarithms or other tricky integrals here. Just the plain rule for positive powers of x. For differentiation,…

Read More
©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes