In this article, we are going to review two new features of C++23. Now the language allows the call operator (operator()
) and the subscription operator (operator[]
) to be static. Let’s jump into the details.
static operator()
As we saw earlier in our big C++ algorithms tutorial, function objects are extensively used in the standard library to customise the behaviour of several functions. With the introduction of ranges in C++20 (and of earlier non-standard libraries), the usage of function objects became even more widespread.
Many function objects are extremely simple. Let’s take an implementation of isEven.
1
auto isEven = [](int i) {return i % 2 == 0;};
When the compiler transforms this lambda into an actual function object, it’ll look somehow like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class __lambda_5_19 {
public:
inline /*constexpr */ bool operator()(int i) const {
return (i % 2) == 0;
}
using retType_5_19 = bool (*)(int);
inline constexpr operator retType_5_19 () const noexcept {
return __invoke;
};
private:
static inline /*constexpr */ bool __invoke(int i) {
return __lambda_5_19{}.operator()(i);
}
};
It’s worth noting that even though this lambda has no capture, it has no state at all, operator()
is not static. What does it mean in practice? As it’s explained in the Motivation section of P1169R4, the consequence is that if operator()
cannot be inlined, the implicit object parameter (this
) must be passed around which manifests itself as an extra register call in the generated assembly code.
This is a violation of the fundamental principle behind C++: you don’t pay for what you don’t need. Here you do.
Thanks to this proposal, the above problem is going to be solved by allowing operator()
to be a static
member function. This new feature will come in handy both for hand-written function objects and for lambdas.
In the case of a lambda, you have to explicitly declare it static
if you want that the generated object has a static
call operator:
1
auto isEven = [](int i) static {return i % 2 == 0;};
At the same time, you only declare a lambda static
if it has no capture, if it has a capture then the compiler reminds you that you violated the rules:
1
2
// ERROR: 'static' lambda specifier with lambda capture
auto isDivisableBy = [operand](int i) static {return i % operand == 0;};
static operator[]
P2589R0 makes operator[]
consistent with operator()
with regards of static
ness.
The need for this consistency was expressed in some places already:
- In the already discussed proposal to allow
operator()
bestatic
- In P2128R6 allowing multidimensional subscription operators
- And in EWG88 where Gabriel Dos Reis already brought up in 2014 that
operator()
andoperator[]
should be handled the same way and both should be allowed to be static.
Now this becomes valid code:
1
2
3
4
5
class MyClass {
public:
static int operator[](size_t index);
// ...
};
Conclusion
In this post, we reviewed two papers that got accepted for C++23. The language now allows both operator()
and operator[]
to be static
in the principle of not having to pay for something you don’t use. In this case, that something is the this
pointer. Probably static
call operators will be more frequently used than the static
subscription operator, but keeping the language consistent is a good idea!
Both these changes are already available on gcc (13) and clang(16) at the moment of publication in July 2023.
In the next post, we’ll stay with the subscription operator and discuss how and why it’s going to support multidimensional arrays.
Connect deeper
If you liked this article, please
- hit on the like button,
- subscribe to my newsletter
- and let’s connect on Twitter!