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.

C++23’s new function syntax

elbeno, 5 September, 20225 September, 2022

We’ve had a couple of ways to spell functions for a long time:

[[nodiscard]] auto say_a_to(
    std::string_view what, std::string_view name) -> std::string {
  return std::string{what} + ", " + std::string{name} + "!";
}

say_a_to("Hi", "Kate"); // -> "Hi, Kate!"

struct {
  [[nodiscard]] auto operator()(
      std::string_view what, std::string_view name) const -> std::string {
    return std::string{what} + ", " + std::string{name} + "!";
  }
} say_b_to;

say_b_to("Hello", "Tony"); // -> "Hello, Tony!"

And we’ve had a shortcut for that second one for a while, too:

auto say_c_to = [] [[nodiscard]] (
    std::string_view what, std::string_view name) -> std::string {
  return std::string{what} + ", " + std::string{name} + "!";
};

say_c_to("Bye", "Kate"); // -> "Bye, Kate!"

(Aside: notice where [[nodiscard]] is positioned, since C++23’s P2173, to apply to the lambda’s call operator.)

All these ways to spell functions look the same at the call site. But “regular functions” and function objects behave a bit differently, so as application and library writers we use them for different things according to our needs – how we need users to customize or overload them; whether we want ADL; etc.

C++23 added a new way to spell functions:

struct {
  [[nodiscard]] auto operator[](
      std::string_view what, std::string_view name) const -> std::string {
    return std::string{what} + ", " + std::string{name} + "!";
  }
} say_d_to;

say_d_to["Goodbye", "Tony"]; // -> "Goodbye, Tony!"

It hasn’t been publicized as such, but that’s exactly what it is. C++20 removed the ability to use the comma operator in subscripts (P1161). As a follow-up to that, C++23 now allows multi-argument subscript operators (P2128). And that effectively gives us an alternative syntax for calling functions. The subscript operator now has the same mechanics as the function call operator. These operators are now the only two that can take arbitrary numbers of arguments of arbitrary types. And they’re also the same precedence.

So we can do things like this too:

struct {
  template <std::integral ...Ts>
  [[nodiscard]] auto operator[](Ts... ts) const noexcept {
    return (0 + ... + ts);
  }
} sum;

And call it accordingly:

const auto s1 = sum[1, 2, 3]; // 6
const auto s2 = sum[];        // 0

(Yes, operator[] can also be written as a nullary function.)

This works, today. Probably because to the compiler this was already the bread-and-butter of how functions work anyway, so I’m guessing (although IANACW) it was pretty easy to implement these papers.

P2128‘s envisaged use cases are all about numeric computing and multi-dimensional arrays with integral subscripting. But that’s not all that operator[] is now. Quite literally, it’s alternative syntax for a function call, with everything that might imply.

What use might this be? Well a few things spring to mind. Using operator[] for function calls has all the same lookup and customization implications as using operator(), but adds the inability to call through type-erased function wrappers — at least at the moment. So that might be useful to someone.

A second convention that springs to mind is perhaps for pure functions. If a function is “pure” then it will always return the same output given the same input, which means mathematically it can be implemented with a lookup table. Using operator[] historically looks something like a map lookup, so perhaps it’s a natural fit for pure function syntax?

It might also be useful to naturally express two different areas of functionality within a library, or operations with different evaluation semantics (compile-time? runtime? lazy?) as characterized by function calls with operator() and with operator[]. This would perhaps provide a nice call-site indication to make the code more readable.

There are sure to be other uses. Should you look for operator[] coming soon to a library near you? I don’t know. This might seem strange to some folks, but it’s not necessarily less readable; just less familiar. And if there’s one thing I know about C++, it’s that it’s a short hop from bizarre newly-discovered quirk to established technique. operator[] is now equivalent to operator(), and when someone finds a use for that, it will get used.

C++

Post navigation

Previous post
Next post

Related Posts

How to print anything in C++ (part 2)

1 February, 201530 June, 2015

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript We have a basic plan, and the ability to detect concrete types. But how can we detect whether an object supports output with operator<<? For this, there is a recently-discovered amazing trick. Here’s the code: template using void_t =…

Read More

C++Now 2018 Trip Report

16 May, 201816 May, 2018

Last week, a little fewer than 150 C++ programmers gathered in Aspen, CO for C++Now 2018. This year the conference was scheduled before Mother’s Day, so with it being quite a bit earlier than usual, I was half-expecting snow and travel delays. In fact last week turned out to be…

Read More

More string hashing with C++11 constexpr

14 October, 201515 October, 2015

(Start at the beginning of the series – and all the source can be found in my github repo) So FNV1 was easy, and Murmur3 wasn’t too much harder; for a challenge and to see how far I could go, I decided to try to compute an MD5 string hash…

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