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

Pointer-to-member-functions can be tricky

31 August, 201831 August, 2018

Note: the following applies to Microsoft’s compiler only — not to GCC or Clang. Pointers-to-member-functions (PMFs) are a bit off the beaten track in C++. They aren’t very syntactically pleasing, and they aren’t as easy to deal with as regular pointers-to-free-functions (PFFs). But they still see use, particularly in pre-C++11…

Read More

A problem with C++ lambdas?

18 February, 201530 June, 2015

C++ lambdas are wonderful for all sorts of reasons (especially with their C++14-and-beyond power). But I’ve run into a problem that I can’t think of a good way around yet. If you’re up to date with C++, of course you know that rvalue references and move semantics are a major…

Read More

Exercising Ranges (part 2)

1 July, 20151 July, 2015

(Start at the beginning of the series if you want more context.) First steps with power series A power series (or polynomial, to give it a more familiar term in the case where it’s finite) is represented simply as a sequence of coefficients of increasing powers of x. This is…

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