A code editor with four icons symbolizing DevOps, developers, a gear, and a cluster.

This article covers the highlights of the C++ standardization proposals before the International Organization for Standardization (ISO) committee's Core and Evolution Working Groups last year. Read on to find out what's coming in C++23.

Virtual ISO working group meetings

For most of 2020, meetings about the C++ standard were held virtually and treated by the attendees as tentative, mostly filling time until we could meet in person again. As in-person meetings continue to be pushed out further into the future, and we've gotten more comfortable with the online format, we've gotten a lot more done in virtual meetings.

Now, instead of doing most of our business during the in-person meetings three times a year, the different working groups frequently meet throughout the year, some weekly, some monthly, or whatever seems appropriate. During the weeks when we previously had our in-person meetings, we hold only the Monday plenary meeting, virtually, to ratify the work the various groups have done in the intervening months.

Notable core language changes for C++23

The "Declarations and where to find them" and "Down with ()" papers that I mentioned in last year's report were accepted, as expected.

if consteval

The new if consteval syntax (P1938R3) creates an immediate function context within a constexpr function. In C++20. many people expected to be able to create such a context with if (std::is_constant_evaluated()), but that syntax doesn't have that effect, and does have significant pitfalls. So new syntax seemed warranted. An example of the new syntax is:

consteval int f(int i) { return i; }
constexpr int g(int i) {
    if consteval {
        return f(i) + 1; // ok: immediate function context
    } else {
        return 42;
    }
}

Deducing "this"

P0847R7 allows the object parameter (normally implicitly this) of a non-static member function to be declared explicitly, and have its type deduced:

struct X {
  int i;
  template <typename Self>
  auto&& foo(this Self&& self) { return self.i; }
};

The primary use is expected to be to deduce the value category of the object argument, replacing the need for multiple overloads with different ref-qualifiers. One possibly surprising effect is that the object parameter type can also deduce to a derived type, causing references to a particular member to resolve instead to a member of the same name in the derived class:

struct D : X {
  char i;
} d;
char& r = d.foo(); // ok, returns reference to D::i, not B::i

Adding #elifdef and #elifndef preprocessing directives

These directives were recently added to C23, so P2334R1 also adds them to C++23 to avoid preprocessor incompatibilities. Programmers have often been surprised that these directives don't exist, and in C++23 they will.

Multidimensional subscript operator

P2128R6 changes the operator[] member function from always having a single parameter to having any number of parameters. As a result, a multidimensional array class can use ar[1,2,3] to index directly rather than through the proxy classes needed to support ar[1][2][3]. Previously, the ar[1,2,3] syntax was deprecated.

C++ Identifier Syntax using Unicode Annex 31

Past C++ standards have attempted to define their own subsets of Unicode to allow in identifiers, but each revision had flaws. C++23 (P1949R7) leaves that task instead to the Unicode Consortium, which now provides Annex 31 for this purpose. This change conveniently coincides with the recent concern about Trojan source attacks using bidirectional text, because Annex 31 significantly limits the use of bidirectional characters in identifiers.

Note: Find out how to prevent Trojan Source attacks with GCC 12.

CWG2397, auto specifier for pointers and references to arrays

P2386R0 allows the declaration of pointers and references to arrays of auto, such as:

  int a[3];
  auto (*p)[3] = &a; // now OK

Non-literal variables, labels, and gotos in constexpr functions

Restrictions on constexpr functions have been gradually weakening since C++11, in particular by changing requirements that a particular construct never appear in the function; instead the construct can be used but makes the evaluation non-constant. P2242R3 makes that change for the constructs mentioned in the title, and for static or thread-local variables.

What's in the pipeline for C++23?

In addition to the changes just described, which the committee has accepted, additional changes are likely to make it into C++23. At least they have moved on to electronic voting by the full Evolution Working Group.

Attributes on lambda-expressions

There has not yet been a way to apply an attribute like [[nodiscard]] to a lambda's operator(). The proposed P2173R1 uses the syntax [][[nodiscard]]() { ... }, which matches the position of attributes that appertain to a declarator-id in a declaration that has one.

The equality operator you are looking for

P2468 attempts to fix a long-standing problem. The implicit reversal of operator== made some valid C++17 code ill-formed in C++20, most frequently where a class defines comparison operators that are accidentally asymmetric:

struct S {
  bool operator==(const S&) { return true; }  // mistakenly non-const
  bool operator!=(const S&) { return false; } // mistakenly non-const
};
bool b = S{} != S{}; // well-formed in C++17, ambiguous in C++20

In the GCC compiler, I made this example work by making a reversed operator worse than an unreversed operator if they have the same parameter types. P2468 considered this fix, but instead proposes that if a class has matching operator== and operator!= declarations, no reversed candidates are considered.

More constexpr relaxations

In C++20, a function declared constexpr that can never produce a constant result is ill-formed, and no diagnostic is required. This has proven a burden for implementers who would like their function to be usable in a constant expression when other functions that it calls become usable, but don't want to have to use macro trickery to coordinate the addition of the constexpr keyword at the same time. So P2448 proposes removing that rule, along with the related rule that a defaulted member function can be declared constexpr only if it could produce a constant result.

P2350 proposes allowing a class to be marked as constexpr to avoid the need to mark each member function separately.

P2280 proposes that binding an object with a non-constant address to a reference parameter of a constexpr function be allowed in a constant-expression, so long as it is never accessed. This is intended particularly to support uses like the following:

template <typename T, size_t N>
constexpr size_t array_size(T (&)[N]) {
    return N; // referent of parameter is never used
}

Inspired by these changes, for GCC 12, I've added an -fimplicit-constexpr option that takes what seems to me the natural next step of treating all inline functions as implicitly constexpr; I'm interested in feedback about people's experience with it before I propose it formally.

Extended floating-point types and standard names

P1467 adds conditionally-supported std::float{16,32,64,128}_t and std::bfloat16_t for the corresponding IEEE types, as well as corresponding literal suffixes and adjustments to overload resolution to rank conversions between the greatly expanded set of floating-point types.

Static operator()

It seems unnecessary to prohibit the operator() member function from being static, and P1169 allows it.  If a static definition had been allowed when lambdas were introduced, the call operator for a captureless lambda probably would have been implicitly static, but this paper does not propose making that change.

Support for #warning

P2437 adds the #warning preprocessor directive. Most compilers have already supported it for a long time.

Labels at the end of compound statements

P2324 makes a convenience tweak that has already been adopted by C.

Portable assumptions

P1774 adds the [[assume(expr)]] syntax to tell the compiler that it should assume during optimization that the expression argument evaluates to true. The C++20 contracts proposal ran aground on disagreement over whether contracts should or should not be assumed, so this proposal is now independent of any potential future contracts feature. This might be the least likely of the features discussed here to make it into C++23, as there is significant resistance to any assumption feature from one vendor, but there was strong support from the rest of the evolution group when the paper was discussed.

Conclusion

Work continues in study groups on various other proposals, such as pattern matching (P1371) and a return of Contracts (P2182), but they are not trying to hit the deadline for C++23.

At the moment, the next in-person meeting is planned for November 2022. We'll see whether it actually happens, and if so, how it is affected by our experience with virtual meetings.

Many of the proposed changes in C++23 simplify or relax complex restrictions. The changes reflect requests from the field and should make C++ easier for complex projects.

Last updated: January 6, 2023