---
title: "P3367R5: constexpr coroutines"
document: P3367R5
date: 2026-07-15
audience: CWG Core,LWG Library
reply-to:
  - "Hana Dusíková"
---

Number:

P3367R5

2026-06-14

[Core](mailto:Core (YOU-NEED-JAVASCRIPT-ENABLED) #Q29yZSA8Y29yZUBsaXN0cy5pc29jcHAub3JnPj9zdWJqZWN0PVAzMzY3UjU6IGNvbnN0ZXhwciBjb3JvdXRpbmVz) and [Library](mailto:Library (YOU-NEED-JAVASCRIPT-ENABLED) #TGlicmFyeSA8bGliQGxpc3RzLmlzb2NwcC5vcmc+P3N1YmplY3Q9UDMzNjdSNTogY29uc3RleHByIGNvcm91dGluZXM=)

Target:

C++29

Author:

[Hana Dusíková](mailto:hana dusikova (YOU-NEED-JAVASCRIPT-ENABLED) #SGFuYStEdXMlQzMlQURrb3YlQzMlQTEgPGhhbmlja2FAaGFuaWNrYS5uZXQ+P3N1YmplY3Q9UDMzNjdSNTogY29uc3RleHByIGNvcm91dGluZXM=)

---

◀︎

# P3367R5: constexpr coroutines

This paper is proposing making coroutines functional during constant evaluation. Even when most of use-cases for coroutines are based on I/O and event based, coroutines are still useful for compile-time based computation, eg. `std::generator`.

## Changes

- [R4](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3367r4.html) → R5: removal of *constexpr-suitable function* definition (and its references) as it's now a synonym to just a function. Changes based on [LWG](https://docs.google.com/document/d/1DwCHJmaO3qp-rcsBadAkRLYUKYe-NgO9jFmdDlx_7q0/edit?tab=t.gwnlzjdkr0js#heading=h.xjke6sxfcold) ([github](https://github.com/cplusplus/papers/issues/2069#issuecomment-4699132255)) and [CWG](https://wiki.isocpp.org/2026-06_Brno:CoreWorkingGroup#D3367R5_constexpr_coroutines) ([github](https://github.com/cplusplus/papers/issues/2069#issuecomment-4692522033)) wording reviews. This revisision contains the sentence LWG asked me to add, but I forgot to notify LWG designed people to check the addition. The sentence is now reviewed by Tomasz (with Jeff being notified in time before the mailing deadline).
- [R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3367r3.html) → [R4](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3367r4.html): allow `co_await` and `co_yield` only within coroutine evaluation
- [R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3367r2.html) → [R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3367r3.html): [added motivation text](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3367r3.html), [added AST transform approach examples](https://isocpp.org/files/papers/P3367R3.html#standard-transformation)
- [R1](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3367r1.html) → [R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3367r2.html): Updated wording to elide allocator functionality in coroutine promise types. Added discussion about alternative implementation strategies. Added information about decision by various groups which seen this paper.
- [R0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3372r0.html) → [R1](https://wg21.link/p3372r1): Changed wording mentioning lifetime of coroutines after expr.const 5.2

## Timeline

November 2024: EWG in Wroclaw

POLL: P3367 constexpr coroutines send to CWG and LEWG for inclusion for C++26

SF

F

N

A

SA

4

12

3

0

Outcome:

Consensus.

November 2024: LEWG in Wroclaw

POLL: Forward P3367R1 to LWG for C++26

SF

F

N

A

SA

12

5

0

Outcome:

Unanimous consent.

November 2024: CWG in Wroclaw

Multiple implementers expressed concerns about resources needed for the implementation of this feature, where the resources might be better spent implementing C++ features where market demand is higher.

EWG is requested to reconsider putting this feature into the standard.

**Note:** CWG did review of the wording and only question was about what to do with allocation / deallocation functions. Wording was updated to elide these calls during constant evaluation of a coroutine. Concern about resources was reflected by updating the paper to contain informations about alternative implementation strategies.

Based on anecdotal evidence people don't use coroutines for two reasons: one is missing standard library support (which is partially resolved with `std::generator`) and second is mutual exclusivity with much more popular constant evaluated code. Having constant evaluation of couroutines will help alleviate this and I do expect seeing libraries implementing lazy coroutine based parsers.

February 2025: EWG in Hagenberg

POLL: P3367R3: constexpr coroutines: Forward to CWG for inclusion in C++26

SF

F

N

A

SA

2

15

23

5

Outcome:

Consensus against.

February 2025: EWG in Hagenberg

POLL: P3367R3: constexpr coroutines: Forward to CWG for inclusion in C++29

SF

F

N

A

SA

13

23

12

3

1

Outcome:

Consensus.

**Dissent**

- Still premature, needs implementation work beyond time horizon
- Competes with unknown workload from other C++29 features

Note: 

Paper development was suspended as it was waiting for 29 cycle.

Juny 2026: LWG in Brno

POLL: LWG is happy with the changes in P3367r5 (with requested changes checked by JG and TK) to be moved by Core?

F

A

N

6

0

3

Outcome:

Consensus.

**Note:** It missed final check of the addition of the requested sentence by JG or TK due me also organizing the Brno meeting and being busy and forgetting about it.

June 2026: CWG in Brno

Ready for plenary (**Note:** it wasn't on the plenar due having only tentative approval from LWG.)

July 2026

The missing sentence was confirmed to be correct by Tomasz (and Jeff was notified.)

## Motivation

Currently C++'s [doesn't allow coroutines to be `constexpr`](https://eel.is/c++draft/dcl.constexpr#3.1). This limitation forces users to choose between having `constexpr` compatible library or (maybe) simpler coroutine interface. As a library author I do prefer `constexpr` but I do not want to make this decision.

I think this limitation and weak library support for coroutines are main limiting factors for bigger adoption of coroutines. I personally want to be able to write [CTRE](https://github.com/hanickadot/compile-time-regular-expressions) coroutine interface which allows partially process input on a regular expression state and continue later when it will be resumed.

### Quote

Well, you just told me coroutines are the best way to solve some problems, so wouldn't I also want to use the Best Way at compile time? (quote from Jason Turner, co-author of *"constexpr all the things"* talk)

### Coroutines are not just for asynchronous

Most people see uses for coroutines for asynchronous and event based programming. Coroutines are useful for parsers, modeling clock-perfect timing in emulators, and generally any state machines.

### Fibonnaci generator

When this paper is merged into the standard, users will be able to use `std::generator`-like coroutines to generate or calculate data.

```cpp
template <typename T> constexpr auto fib() -> std::generator<T> {
    T a = 0;
    T b = 1;
    co_yield a;
    do {
        co_yield b;
        auto tmp = b;
        b += a;
        a = tmp;
    } while (true);
}

template <typename T, size_t N> constexpr auto calculate_fibonnaci() {
    auto res = std::array<T, N>{};
    std::ranges::copy(fib<T>() | std::views::take(N), res.begin());
    return res;
}

constexpr auto cached_fibonnaci = calculate_fibonnaci<unsigned, 20>();
```

## Evaluation of constexpr coroutines

Implementation must make sure to avoid stack exhaustion and store evaluation state and coroutine's local variables in a way to avoid it. By simply resuming coroutine and evaluating it on system stack (in case of AST walking implementations) will lead to it.

xml version="1.0" encoding="UTF-8"?

A

B

C

E

suspend

D

Y

X

F

X

Y

A

B

suspend

D

?

F

suspend

non-coroutine code

suspended coroutine code

non-coroutine code

coroutine code

coroutine

system

stack

(unrelated)

suspend

ret

suspend

This is because coroutines can be interlieved and their state must be maintained to be resumed. The stack then contains mixed coroutines and normal code together. To avoid this situation the easiest way to model a coroutine is to use coroutine (or coroutine-like functionality) to store the state (represented with values on stack) somewhere else. Because AST walk is unbounded, obvious first choice is a stackfull coroutine (fiber, not a thread!).

Lifetime of coroutine is bounded to be only within constant evaluation similary as memory allocation. Any coroutine leaking outside boundaries of constant evaluation means whole constant evalution failed.

xml version="1.0" encoding="UTF-8"?

suspend

X

Y

?

D

F

suspend

A

B

suspend

non-coroutine code

switch stack

system

stack

stackfull

coroutine

(unrelated)

## Implementation experience

Partially implemented in clang available on my [github](https://github.com/hanickadot/llvm-project/tree/P3367-constexpr-coroutines), implementation should be ready for its presentation at Wroclaw meeting, and also will be soon available on compiler explorer (thanks Matt!).

Most of functionality needed was already present in Clang, it was mostly about removing checks in the parser.

Another part was implementing the functionality in Clang's interpreter and there I needed to add fibers (stackfull coroutines) as the interpreter recursive walks over AST. Ability to save interpreter's stack content did minimize impact of the change to only resuming, suspending, and variable storage and life-time management.

At the end of evaluation the interpret needs to check objects holding fibers if there is still any coroutine not released, if there is it report similar error as when there is an unreleased memory allocation.

Hardest problem was implementing local "stack", as `createLocal` function was designed around idea of having only one branch of evaluation. This I solved by providing context of currently evaluated coroutine in `EvalInfo` and switching it on every suspension / resume of a coroutine.

### Alternative implementation approaches

The implementation is using fibers (not threads!) to create a storage for execution of coroutines outside of main stack. Main requirement here is to make sure jumping between coroutines won't run out of the system stack.

Following subpart of paper shows alternative approaches which can model coroutines. All of them have same expressive ability and model coroutines well. These implementation has various advantages and costs. For every model of constant evaluation should be possible found good and usable match.

#### Byte-code interpreter

[Clang implementation](https://clang.llvm.org/docs/ConstantInterpreter.html) is starting to use a byte-code virtual machine based interpreter. In such implementation the implementation approach is identical for actually compiling coroutines to a native code. Only thing you need is ability to change current stack / or reference coroutine variables based on offset to coroutine frame.

Such interpret has many advantages over AST walking one mostly in speed and security. I was told initial measurements in the byte-code interpreter in clang are three orders of magnitude faster over recursive AST walking. Security is inherit to fact of not using system stack for the evaluation at all.

#### C++ coroutines

For compilers which are using C++20 code itself another implementation strategy is to not use the system stack for walking AST, but change all necessory functions into coroutines which would model ordinary functions with only one return point to its callee. But these functions can be suspended and evaluation can switch to evaluation of a constexpr coroutine. Because all coroutines will be allocated on heap, this also hardens implementation's constexpr evaluator.

xml version="1.0" encoding="UTF-8"?

(unused)

system

stack

non-coroutine code

a

s

y

m

e

t

r

i

c

t

r

a

n

s

f

e

r

evalation order

coroutine code

symmetric transfer

X

Y

?

D

F

A

B

suspend

#### AST transformation / split

Last feasible approach is to implement a transformation of coroutine function, into multiple trees, representing all possible paths which can be taken from each suspension point. Everytime such splitted coroutine is suspended, constexpr evaluator can unroll all its recursive functions. Implementation then needs to implement a storage for coroutine local variables which survive suspension. Hardest part is to implement special AST nodes to represent expressions split for suspension in middle.

xml version="1.0" encoding="UTF-8"?

A

B

C

E

D

F

A

B

C

F

E

D

ret

suspend

coroutine

transform

**Note:** similar split is already done in compiler (for Clang at LLVM level, GCC just before leaving frontend, [for Swift in its frontend](https://llvm.org/docs/Coroutines.html#async-lowering))

C++ coroutines are already defined as a set of transformations over a function based on detection of `co_await`, `co_yield`, and `co_return` keywords. This approach is going a bit further but it can still be done manually.

##### Standard transformation

Simple generator which take a value, yield it and later yield value<sup>2</sup>.

```cpp
auto simple(T start) -> std::generator<T> {
	co_yield start;
	auto quad = start * start;
	co_yield quad;
}
```

This code is [current transformed](https://eel.is/c++draft/dcl.fct.def.coroutine#5) into:

```cpp
auto simple(T start) -> std::generator<T> {
	return std::generator<T>{ coroutine-frame {
		std::generator<T>::promise_type _promise{};
		auto start = start; // copy arguments
		try {
			co_await _promise.initial_suspend();
			co_await _promise.yield_value(start);
			auto quad = start * start;
			co_await _promise.yield_value(quad);
			// body
		} catch (...) {
			if (!initial-await-resume-called) {
				throw;
			}
			_promise.promise.unhandled_exception();
		}
		// final-suspend:
		 co_await promise.final_suspend() ;
	}};
}
```

As you can see, it's all just transformation. A function into a coroutine body explicitly managing life-time of a promise type, and `co_yield` into `co_await`. Compiler is already doing a lot of transformation in frontend. This proposed implementation strategy is just making a bit more transformations.

##### Transformation into void-style continuation

Any coroutine can be transformed into a set of `void` returning functions:

```cpp
coroutine simple(T argument) {
	auto result = co_await normal_function(argument);
	co_return result;
}
```

With resulting code looking somehow like this:

```cpp
struct coroutine_state_base {
	// where to jump
	void (*resume_point)(coroutine_state_base *) = nullptr;
};	

// unique type per each coroutine function
struct __coroutine_state: coroutine_state_base {
	// all arguments here...
	T argument;
	
	// promise type
	coroutine::promise_type promise;

	// all variables / temporaries which can survive suspension
	initial_awaiter initial_suspend_result;
	final_awaiter final_suspend_result;
	decltype(normal_function(argument)) tmp;
};

constexpr coroutine simple(T argument) { // <------------- starting point
	// create state
	auto * state = new __coroutine_state{};
	
	// copy arguments
	state->argument = argument;
	
	// construct promise
	new (&state->promise) coroutine::promise_type{...};
	
	// construct result
	auto result = state->promise.get_return_object();
	
	// call (jump-into) coroutine body
	simple_start(state);
	
	// return result object with coroutine handle (usually)
	return result;
}

constexpr void simple_start(coroutine_state_base * vstate) {
	auto * state = static_cast<__coroutine_state *>(vstate);
	
	// INITIAL SUSPEND
	state->initial_suspend_result = state->promise.initial_suspend()
	if (!state->initial_suspend_result.await_ready()) {
		state->resume_point = &simple_after_init_suspend;
		
		// tail-recursion jump to next coroutine or continue
		NEXT-COROUTINE(state->initial_suspend_result.await_suspend(std::coroutine_handle{state}));
	}
	return simple_after_init_suspend(state); // tail-recursion
}

constexpr void simple_after_init_suspend(coroutine_state_base * vstate) {
	auto * state = static_cast<__coroutine_state *>(vstate);
	
	// BEFORE CUSTOM CO_AWAIT
	try {
		(void)state->initial_suspend.await_resume();
		std::destroy_at(&state->initial_suspend_result); // lifetime of temporary must be destroyed
		
		// normal body
		state->tmp = normal_function(state->argument);
		
		if (!state->tmp.await_ready()) {
			state->resume_point = &simple_after_first_await;
			
			// tail-recursion jump to next coroutine or continue
			NEXT-COROUTINE(state->tmp.await_suspend(std::coroutine_handle{state}));
		}
		
		return simple_after_first_await(state);
	} catch (...) {
		std::destroy_at(&state->tmp);
		state->promise.unhandled_exception();
		return simple_final_suspend(state);
	}
}

constexpr void simple_after_first_await(coroutine_state_base * vstate) {
	auto * state = static_cast<__coroutine_state *>(vstate);
	
	// RESUMED or just jumped after CUSTOM CO_AWAIT
	try {
		// get result (which doesn't survive suspension, so it can be put here on stack)
		auto result = state->tmp.await_resume();
		
		// co_return result
		state->promise.return_value(result); 
		
		// end of scope for state->tmp
		std::destroy_at(&state->tmp);
		
	} catch (...) {
		std::destroy_at(&state->tmp);
		state->promise.unhandled_exception();
	}
	
	// jump to final suspend
	return simple_final_suspend(state);
}

constexpr void simple_final_suspend(coroutine_state_base * vstate) {
	auto * state = static_cast<__coroutine_state *>(vstate);
	
	// FINAL SUSPEND
	state->final_suspend_result = state->promise.final_suspend();
	if (!state->final_suspend_result.await_ready()) {
		state->resume_point = nullptr;
		
		// tail-recursion jump to next coroutine or continue
		NEXT-COROUTINE(state->final_suspend_result.await_suspend(std::coroutine_handle{state}));
	}
	// destruction of state after final-suspend
	delete &state;
	// no-return ... which will return us to our resumer
}
```

Whole transformation is just changing all edges to standalone `void` returning function between which can be jumped with a tail recursion. Each `.await_suspend(coroutine_handle)` can return handle to next coroutine, boolean with information if it should be a suspend or not, or just void, which means simple suspend.

```cpp
// NEXT-COROUTINE(EXPR) is:
using type = decltype(EXPR);                                                        
if constexpr (std::is_void_v<type>) {                                         
	EXPR;                                                                             
	return; // return to caller                                                       
} else if constexpr (std::same_as<type, bool>) {                              
	if (EXPR) {                                                                       
		return; // return to caller                                                     
	}                                                                                 
	// continue in coroutine                                                          
} else if constexpr (std::convertible_to<std::coroutine_handle<void>>) {
	auto * next_state = static_cast<coroutine_state_base *>(E(XPR).address());                                                          
	return next_state->resume_point(next_state); // tail-recursion
}                                                                                   
```

Most of the transformation is lifetime handling and splitting at each `co_await`. The suspend transformation is already part of compilers so `await_ready()`, `await_suspend(std::coroutine_handle)`, `await_resume()` is already part of each compiler.

##### Loops transformation

```cpp
constexpr coroutine loops() noexcept {
		while (co_await check()) {
			another_function();
		}
	}
```

Code above is transformed into:

```cpp
constexpr void loops_main(coroutine_state_base * vstate) noexcept {
	// initial suspend skipped for readability
		
	auto * state = static_cast<__coroutine_state *>(vstate);
	state->tmp = check();
	if (!state->tmp.await_ready()) {
		state->resume_point = &loops_main_body;
		NEXT-COROUTINE(state->tmp.await_suspend(std::coroutine_handle{state}));
	}
	return loops_main_body(state); // tail-recursion
}

constexpr void loops_main_body(coroutine_handle_base * vstate) noexcept {
	auto * state = static_cast<__coroutine_state *>(vstate);
	if (state->tmp.await_resume()) {
		another_function();
		return loops_main(state); // tail-recursion
	}
	std::destroy_at(&state->tmp);
	return loops_final_suspend(state);
}
```

##### Tail-recursion

To implement constexpr coroutines with the AST transformation a tail recursion is needed to be supported in constant evaluator.

#### Comparison

<!-- tomd:lossy-table -->
| Implementation | Storage for | Storage for | Area needed to be changed |
| --- | --- | --- | --- |
| Implementation | Variables | State | Area needed to be changed |
| Stackfull coroutines | overlay over local variables | fiber (alternative stack on a heap) | builtins to control coroutine flow, execution state to keep track of allocations |
| Byte-code | coroutine frame | instruction pointer | control of instruction pointer |
| Stackless coroutines | overlay over local variables | suspended coroutines | convert all AST walking functions into coroutines |
| AST transformation | instance of coroutine state | function pointer | custom lifetime handling needs to be added for temporaries / variables across suspend points |

## Impact on existing code

None, this is a pure extension, it allows code to be constexpr which wasn't case before.

## Proposed wording changes

Remove all obstacles blocking coroutines from being constant evaluatable. Make sure all coroutines are destroyed at end of constant evaluation. Everything is now constexpr-suitable by default, so its definition is removed.

### Language wording

#### expr.const.core

:::wording

### Core constant expressions [expr.const.core]

1

#

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

- ...
- (2.3)an invocation of a non-constexpr function;62
- (2.4)an invocation of an undefined constexpr function;
- (2.5)an invocation of an instantiated constexpr function that is not constexpr-suitable;
- a construction of a coroutine promise object ([dcl.fct.def.coroutine]), unless the coroutine promise object is destroyed within the evaluation of *E*;
- (2.6)an invocation of a virtual function ([class.virtual]) for an object whose dynamic type is constexpr-unknown;
- (2.7)an expression that would exceed the implementation-defined limits (see [implimits]);
- ...
- (2.23)a construction of an exception object, unless the exception object and all of its implicit copies created by invocations of std::current_exception or std::rethrow_exception ([propagation]) are destroyed within the evaluation of E;
- (2.24)a *throw-expression* ([expr.throw]) with no operand, unless there is a currently handled exception whose exception object was constructed within the evaluation of E;
- (2.25)an *await-expression* ([expr.await]), unless the enclosing coroutine was entered within the evaluation of E;
- (2.26)a *yield-expression* ([expr.yield]), unless the enclosing coroutine was entered within the evaluation of E;
- (2.27)a three-way comparison ([expr.spaceship]), relational ([expr.rel]), or equality ([expr.eq]) operator where the result is unspecified;

:::

#### dcl.constexpr

:::wording

### The constexpr and consteval specifiers [dcl.constexpr]

2

#

A

constexpr

or

consteval

specifier
used in the declaration of a function
declares that function to be
a

*constexpr function*

.

> [
> 
> Note 3
> 
> :
> 
> A function declared with the
> 
> consteval
> 
> specifier
> is an immediate function (
> 
> [expr.const.imm]
> 
> )
> 
> .
> 
> —
> 
> end note
> 
> ]

A destructor, an allocation function, or a deallocation function
shall not be declared with the

consteval

specifier

.

3

#

A function is

*constexpr-suitable*

if
it is not a coroutine (

[dcl.fct.def.coroutine]

)

.

Except for instantiated constexpr functions,
non-templated constexpr functions shall be constexpr-suitable

.

> [
> 
> Example 2
> 
> :
> 
> constexpr int square(int x)
> { return x * x; } // OK
> constexpr long long_max()
> { return 2147483647; } // OK
> constexpr int abs(int x) {
> if (x < 0)
>     x = -x;
>   return x;                     // OK
> }
> constexpr int constant_non_42(int n) { // OK
> if (n == 42) {
> static int value = n;
>     return value;
>   }
> return n;
> }
> constexpr int uninit() {
> struct { int a; } s;
>   return s.a;                   // error: uninitialized read of s.a
> }
> constexpr int prev(int x)
> { return --x; } // OK
> constexpr int g(int x, int n) { // OK
> int r = 1;
>   while (--n > 0) r *= x;
>   return r;
> }
> 
> 
> —
> 
> end example
> 
> ]

4

#

An invocation of a constexpr function in a given context
produces the same result as
an invocation of an equivalent non-constexpr function in the same context
in all respects except that

- (4.1)an invocation of a constexpr function can appear in a constant expression ([expr.const.core]) and
- (4.2)copy elision is not performed in a constant expression ([class.copy.elision]).

> [
> 
> Note 4
> 
> :
> 
> Declaring a function constexpr can change whether an expression
> is a constant expression
> 
> .
> 
> This can indirectly cause calls to
> 
> std​::​is_constant_evaluated
> 
> within an invocation of the function to produce a different value
> 
> .
> 
> —
> 
> end note
> 
> ]

> [
> 
> Note 5
> 
> :
> 
> It is possible to write a constexpr function for which
> no invocation satisfies the requirements of a core constant expression
> 
> .
> 
> —
> 
> end note
> 
> ]

:::

#### dcl.fct.def.coroutine

:::wording

### Coroutine definitions [dcl.fct.def.coroutine]

9

#

An implementation may need to allocate additional storage for a coroutine

.

This storage is known as the

*coroutine state*

and is obtained by calling
a non-array allocation function (

[basic.stc.dynamic.allocation]

)

.

During constant evaluation, no allocation occurs.

10

#

In all circumstances, tT

he allocation function's name is looked up by searching for it in the scope of the promise type

.

- (10.1)If the search finds any declarations, overload resolution is performed on a function call created by assembling an argument list. The first argument is the amount of space requested, and is a prvalue of type std::size_t. The lvalues p1…pn are the successive arguments. If no viable function is found ([over.match.viable]), overload resolution is performed again on a function call created by passing just the amount of space required as a prvalue of type std::size_t.
- (10.2)If the search finds no declarations, a search is performed in the global scope. Overload resolution is performed on a function call created by passing the amount of space required as a prvalue of type std::size_t.

:::

### Removal of `constexpr-suitable` in core wording

Removed the term *constexpr-suitable* has some impact on lambdas and defaulted functions which are now all by default `constexpr`.

#### expr.prim.lambda.closure

:::wording

#### Closure types [expr.prim.lambda.closure]

7

#

The function call operator or operator template is
a static member function or static member function template (

[class.static.mfct]

)
if the

*lambda-expression*

's

*parameter-declaration-clause*

is followed by

static

.

Otherwise, it is
a non-static member function or member function template (

[class.mfct.non.static]

)
that is declared

const

(

[class.mfct.non.static]

) if and only if the

*lambda-expression*

's

*parameter-declaration-clause*

is not
followed by

mutable

and
the

*lambda-declarator*

does not contain
an explicit object parameter

.

It is neither virtual nor declared

volatile

.

Any

*noexcept-specifier*

or

*function-contract-specifier*

(

[dcl.contract.func]

)
specified on a

*lambda-expression*

applies to the corresponding function call operator or operator template

.

An

*attribute-specifier-seq*

in a

*lambda-declarator*

appertains
to the type of the corresponding function call operator or operator template

.

An

*attribute-specifier-seq*

in a

*lambda-expression*

preceding a

*lambda-declarator*

appertains to the corresponding function call operator or operator template

.

The function call operator or any given operator template specialization
is a constexpr function

 if either
the corresponding *lambda-expression*'s
*parameter-declaration-clause*
is followed by constexpr or consteval, or
it is constexpr-suitable ([dcl.constexpr])

.

It is an immediate function (

[dcl.constexpr]

)
if the corresponding

*lambda-expression*

's

*parameter-declaration-clause*

is followed by

consteval

.

:::

#### dcl.fct.def.default

:::wording

### Explicitly-defaulted functions [dcl.fct.def.default]

3

#

A function explicitly defaulted on its first declaration
is implicitly inline (

[dcl.inline]

)

,

and

is implicitly 

constexpr (

[dcl.constexpr]

)

if it is constexpr-suitable

.

> [
> 
> Note 1
> 
> :
> 
> Other defaulted functions are not implicitly constexpr
> 
> .
> 
> —
> 
> end note
> 
> ]

4

#

:::

### Library wording

Make `coroutine_handle` and `noop_coroutine` constexpr.

:::wording

## Coroutines [support.coroutine]

### General [support.coroutine.general]

1

#

The header

<coroutine>

defines several types providing
compile and run-time support for
coroutines in a C++ program

.

### Header <coroutine> synopsis [coroutine.syn]

🔗// all freestanding
#include <compare> // see [compare.syn]
namespace std {
// [coroutine.traits], coroutine traits
template<class R, class... ArgTypes>
struct coroutine_traits;

  // [coroutine.handle], coroutine handle
template<class Promise = void>
struct coroutine_handle;

  // [coroutine.handle.compare], comparison operators
constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept;
  constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept;

  // [coroutine.handle.hash], hash support
template<class T> struct hash;
  template<class P> struct hash<coroutine_handle<P>>;

  // [coroutine.noop], no-op coroutines
struct noop_coroutine_promise;

  template<> struct coroutine_handle<noop_coroutine_promise>;
  using noop_coroutine_handle = coroutine_handle<noop_coroutine_promise>;

  constexpr noop_coroutine_handle noop_coroutine() noexcept;

  // [coroutine.trivial.awaitables], trivial awaitables
struct suspend_never;
  struct suspend_always;
}

### Coroutine traits [coroutine.traits]

#### General [coroutine.traits.general]

1

#

Subclause

[coroutine.traits]

defines requirements on classes representing

*coroutine traits*

,
and defines the class template

coroutine_traits

that meets those requirements

.

#### Class template coroutine_traits [coroutine.traits.primary]

1

#

The header

<coroutine>

defines the primary template

coroutine_traits

such that
if

ArgTypes

is a parameter pack of types and
if the

*qualified-id*

R​::​promise_type

is valid and
denotes a type (

[temp.deduct]

),
then

coroutine_traits<R, ArgTypes...>

has the following publicly
accessible member:

using promise_type = typename R::promise_type;

Otherwise,

coroutine_traits<R, ArgTypes...>

has no members

.

2

#

Program-defined specializations of this template shall define a publicly
accessible nested type named

promise_type

.

### Class template coroutine_handle [coroutine.handle]

#### General [coroutine.handle.general]

🔗

namespace std {
template<>
struct coroutine_handle<void>
{
// [coroutine.handle.con], construct/reset
constexpr coroutine_handle() noexcept;
    constexpr coroutine_handle(nullptr_t) noexcept;
    constexpr coroutine_handle& operator=(nullptr_t) noexcept;

    // [coroutine.handle.export.import], export/import
constexpr void* address() const noexcept;
    static constexpr coroutine_handle from_address(void* addr);

    // [coroutine.handle.observers], observers
constexpr explicit operator bool() const noexcept;
    constexpr bool done() const;

    // [coroutine.handle.resumption], resumption
constexpr void operator()() const;
    constexpr void resume() const;
    constexpr void destroy() const;

  private:
void* ptr;  // *exposition only*
};

  template<class Promise>
struct coroutine_handle
  {
// [coroutine.handle.con], construct/reset
constexpr coroutine_handle() noexcept;
    constexpr coroutine_handle(nullptr_t) noexcept;
    static constexpr coroutine_handle from_promise(Promise&);
    constexpr coroutine_handle& operator=(nullptr_t) noexcept;

    // [coroutine.handle.export.import], export/import
constexpr void* address() const noexcept;
    static constexpr coroutine_handle from_address(void* addr);

    // [coroutine.handle.conv], conversion
constexpr operator coroutine_handle<>() const noexcept;

    // [coroutine.handle.observers], observers
constexpr explicit operator bool() const noexcept;
    constexpr bool done() const;

    // [coroutine.handle.resumption], resumption
constexpr void operator()() const;
    constexpr void resume() const;
    constexpr void destroy() const;

    // [coroutine.handle.promise], promise access
constexpr Promise& promise() const;

  private:
void* ptr;  // *exposition only*
};
}

1

#

An object of type

coroutine_handle<T>

is called a

*coroutine handle*

and can be used to refer to a suspended or executing coroutine

.

A

coroutine_handle

object whose
member

address()

returns a null pointer value
does not refer to any
coroutine

.

Two

coroutine_handle

objects refer to the same coroutine
if and only if their member

address()

returns the same non-null value

.

2

#

If a program declares an explicit or partial specialization of

coroutine_handle

, the behavior is undefined

.

3

Any invocation of a member function of a type that is a specialization of

coroutine_handle

that violates the precondition in the corresponding

Preconditions

element is not a core constant expression ([expr.const.core]).

#### Construct/reset [coroutine.handle.con]

🔗

constexpr coroutine_handle() noexcept;
constexpr coroutine_handle(nullptr_t) noexcept;

1

#

Postconditions

:

address() == nullptr

.

🔗

static constexpr coroutine_handle from_promise(Promise& p);

2

#

Preconditions

:

p

is a reference to a promise object of a coroutine

.

3

#

Postconditions

:

addressof(h.promise()) == addressof(p)

.

4

#

Returns

: A coroutine handle

h

referring to the coroutine

.

🔗

constexpr coroutine_handle& operator=(nullptr_t) noexcept;

5

#

Postconditions

:

address() == nullptr

.

6

#

Returns

:

*this

.

#### Conversion [coroutine.handle.conv]

🔗

constexpr operator coroutine_handle<>() const noexcept;

1

#

Effects

: Equivalent to:

return coroutine_handle<>​::​from_address(address());

#### Export/import [coroutine.handle.export.import]

🔗

constexpr void* address() const noexcept;

1

#

Returns

:

ptr

.

🔗

static constexpr coroutine_handle<> coroutine_handle<>::from_address(void* addr);

2

#

Preconditions

:

addr

was obtained via a prior call to

address

on an object whose type is a specialization of

coroutine_handle

.

3

#

Postconditions

:

from_address(address()) == *this

.

🔗

static constexpr coroutine_handle<Promise> coroutine_handle<Promise>::from_address(void* addr);

4

#

Preconditions

:

addr

was obtained via a prior call to

address

on an object of type

cv

coroutine_handle<Promise>

.

5

#

Postconditions

:

from_address(address()) == *this

.

#### Observers [coroutine.handle.observers]

🔗

constexpr explicit operator bool() const noexcept;

1

#

Returns

:

address() != nullptr

.

🔗

constexpr bool done() const;

2

#

Preconditions

:

*this

refers to a suspended coroutine

.

3

#

Returns

:

true

if the coroutine is suspended at its
final suspend point, otherwise

false

.

#### Resumption [coroutine.handle.resumption]

1

#

Resuming a coroutine via

resume

,

operator()

, or

destroy

on an execution agent other than the one on which it was suspended
has

implementation-defined behavior
unless
each execution agent either is
an instance of

std​::​thread

or

std​::​jthread

,
or is the thread that executes

main

.

> [
> 
> Note 1
> 
> :
> 
> A coroutine that is resumed on a different execution agent should
> avoid relying on consistent thread identity throughout, such as holding
> a mutex object across a suspend point
> 
> .
> 
> —
> 
> end note
> 
> ]

> [
> 
> Note 2
> 
> :
> 
> A concurrent resumption of the coroutine can result in a data race
> 
> .
> 
> —
> 
> end note
> 
> ]

🔗

constexpr void operator()() const;
constexpr void resume() const;

2

#

Preconditions

:

*this

refers to a suspended coroutine

.

The coroutine is not suspended at its final suspend point

.

3

#

Effects

: Resumes the execution of the coroutine

.

🔗

constexpr void destroy() const;

4

#

Preconditions

:

*this

refers to a suspended coroutine

.

5

#

Effects

: Destroys the coroutine (

[dcl.fct.def.coroutine]

)

.

#### Promise access [coroutine.handle.promise]

🔗

constexpr Promise& promise() const;

1

#

Preconditions

:

*this

refers to a coroutine

.

2

#

Returns

: A reference to the promise of the coroutine

.

#### Comparison operators [coroutine.handle.compare]

🔗

constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept;

1

#

Returns

:

x.address() == y.address()

.

🔗

constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept;

2

#

Returns

:

compare_three_way()(x.address(), y.address())

.

#### Hash support [coroutine.handle.hash]

🔗

template<class P> struct hash<coroutine_handle<P>>;

1

#

The specialization is enabled (

[unord.hash]

)

.

### No-op coroutines [coroutine.noop]

#### Class noop_coroutine_promise [coroutine.promise.noop]

🔗

struct noop_coroutine_promise {};

1

#

The class

noop_coroutine_promise

defines the promise type for
the coroutine referred to
by

noop_coroutine_handle

(

[coroutine.syn]

)

.

#### Class coroutine_handle<noop_coroutine_promise> [coroutine.handle.noop]

#### General [coroutine.handle.noop.general]

🔗

namespace std {
template<>
struct coroutine_handle<noop_coroutine_promise>
{
// [coroutine.handle.noop.conv], conversion
constexpr operator coroutine_handle<>() const noexcept;

    // [coroutine.handle.noop.observers], observers
constexpr explicit operator bool() const noexcept;
    constexpr bool done() const noexcept;

    // [coroutine.handle.noop.resumption], resumption
constexpr void operator()() const noexcept;
    constexpr void resume() const noexcept;
    constexpr void destroy() const noexcept;

    // [coroutine.handle.noop.promise], promise access
constexpr noop_coroutine_promise& promise() const noexcept;

    // [coroutine.handle.noop.address], address
constexpr void* address() const noexcept;
  private:
constexpr coroutine_handle(*unspecified*);
    void* ptr;  // *exposition only*
};
}

#### Conversion [coroutine.handle.noop.conv]

🔗

constexpr operator coroutine_handle<>() const noexcept;

1

#

Effects

: Equivalent to:

return coroutine_handle<>​::​from_address(address());

#### Observers [coroutine.handle.noop.observers]

🔗

constexpr explicit operator bool() const noexcept;

1

#

Returns

:

true

.

🔗

constexpr bool done() const noexcept;

2

#

Returns

:

false

.

#### Resumption [coroutine.handle.noop.resumption]

🔗

constexpr void operator()() const noexcept;
constexpr void resume() const noexcept;
constexpr void destroy() const noexcept;

1

#

Effects

: None

.

2

#

Remarks

: If

noop_coroutine_handle

is converted to

coroutine_handle<>

,
calls to

operator()

,

resume

and

destroy

on that handle
will also have no observable effects

.

#### Promise access [coroutine.handle.noop.promise]

🔗

constexpr noop_coroutine_promise& promise() const noexcept;

1

#

Returns

: A reference to the promise object associated with this
coroutine handle

.

#### Address [coroutine.handle.noop.address]

🔗

constexpr void* address() const noexcept;

1

#

Returns

:

ptr

.

2

#

Remarks

: A

noop_coroutine_handle

's

ptr

is always a
non-null pointer value

.

#### Function noop_coroutine [coroutine.noop.coroutine]

🔗

constexpr noop_coroutine_handle noop_coroutine() noexcept;

1

#

Returns

: A handle to a coroutine that has no observable effects
when resumed or destroyed

.

2

#

Remarks

: A handle returned from

noop_coroutine

may or may not
compare equal to a handle returned from another invocation
of

noop_coroutine

.

### Trivial awaitables [coroutine.trivial.awaitables]

🔗namespace std {
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
  struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
}

1

#

> [
> 
> Note 1
> 
> :
> 
> The types
> 
> suspend_never
> 
> and
> 
> suspend_always
> 
> can be used
> to indicate that an
> 
> *await-expression*
> 
> either never
> suspends or always suspends, and in either case does not produce a value
> 
> .
> 
> —
> 
> end note
> 
> ]

:::

### Removal of `constexpr-suitable` in library wording

Term `constexpr-suitable` was removed as it would be "A function is constexpr-suitable." as in past it was a long list of things which makes functions syntactically not being able to be constexpr, after this paper every constexpr marked function can be constant evaluated unless it is blocked by something from [expr.const].

#### pairs.pair

:::wording

### Class template pair [pairs.pair]

🔗

namespace std {
template<class T1, class T2>
struct pair {
using first_type  = T1;
    using second_type = T2;

    T1 first;
    T2 second;

    pair(const pair&) = default;
    pair(pair&&) = default;
    constexpr explicit(*see below*) pair();
    constexpr explicit(*see below*) pair(const T1& x, const T2& y);
    template<class U1 = T1, class U2 = T2>
constexpr explicit(*see below*) pair(U1&& x, U2&& y);
    template<class U1, class U2>
constexpr explicit(*see below*) pair(pair<U1, U2>& p);
    template<class U1, class U2>
constexpr explicit(*see below*) pair(const pair<U1, U2>& p);
    template<class U1, class U2>
constexpr explicit(*see below*) pair(pair<U1, U2>&& p);
    template<class U1, class U2>
constexpr explicit(*see below*) pair(const pair<U1, U2>&& p);
    template<*pair-like* P>
constexpr explicit(*see below*) pair(P&& p);
    template<class... Args1, class... Args2>
constexpr pair(piecewise_construct_t,
                     tuple<Args1...> first_args, tuple<Args2...> second_args);

    constexpr pair& operator=(const pair& p);
    constexpr const pair& operator=(const pair& p) const;
    template<class U1, class U2>
constexpr pair& operator=(const pair<U1, U2>& p);
    template<class U1, class U2>
constexpr const pair& operator=(const pair<U1, U2>& p) const;
    constexpr pair& operator=(pair&& p) noexcept(*see below*);
    constexpr const pair& operator=(pair&& p) const;
    template<class U1, class U2>
constexpr pair& operator=(pair<U1, U2>&& p);
    template<class U1, class U2>
constexpr const pair& operator=(pair<U1, U2>&& p) const;
    template<*pair-like* P>
constexpr pair& operator=(P&& p);
    template<*pair-like* P>
constexpr const pair& operator=(P&& p) const;

    constexpr void swap(pair& p) noexcept(*see below*);
    constexpr void swap(const pair& p) const noexcept(*see below*);
  };

  template<class T1, class T2>
    pair(T1, T2) -> pair<T1, T2>;
}

1

#

Member functions of

pair

do not throw exceptions unless one of
the element-wise operations specified to be called for that operation
throws an exception

.

2

#

The defaulted move and copy constructor, respectively, of

pair

is a constexpr function if and only if all required element-wise
initializations for move and copy, respectively,
would be constexpr-suitable (

[dcl.constexpr]

)

.

3

#

If

(is_trivially_destructible_v<T1> && is_trivially_destructible_v<T2>)

is

true

, then the destructor of

pair

is trivial

.

4

#

pair<T, U>

is a structural type (

[temp.param]

)
if

T

and

U

are both structural types

.

Two values

p1

and

p2

of type

pair<T, U>

are template-argument-equivalent (

[temp.type]

) if and only if

p1.first

and

p2.first

are template-argument-equivalent and

p1.second

and

p2.second

are template-argument-equivalent

.

🔗

constexpr explicit(*see below*) pair();

5

#

Constraints

:

- (5.1)is_default_constructible_v<T1> is true and
- (5.2)is_default_constructible_v<T2> is true.

6

#

Effects

: Value-initializes

first

and

second

.

7

#

Remarks

: The expression inside

explicit

evaluates to

true

if and only if either

T1

or

T2

is not implicitly default-constructible

.

> [
> 
> Note 1
> 
> :
> 
> This behavior can be implemented with a trait that checks
> whether a
> 
> const T1&
> 
> or a
> 
> const T2&
> 
> can be initialized with
> 
> {}
> 
> .
> 
> —
> 
> end note
> 
> ]

🔗

constexpr explicit(*see below*) pair(const T1& x, const T2& y);

8

#

Constraints

:

- (8.1)is_copy_constructible_v<T1> is true and
- (8.2)is_copy_constructible_v<T2> is true.

9

#

Effects

: Initializes

first

with

x

and

second

with

y

.

10

#

Remarks

: The expression inside

explicit

is equivalent to:

!is_convertible_v<const T1&, T1> || !is_convertible_v<const T2&, T2>

🔗

template<class U1 = T1, class U2 = T2> constexpr explicit(*see below*) pair(U1&& x, U2&& y);

11

#

Constraints

:

- (11.1)is_constructible_v<T1, U1> is true and
- (11.2)is_constructible_v<T2, U2> is true.

12

#

Effects

: Initializes

first

with

std​::​forward<U1>(x)

and

second

with

std​::​forward<U2>(y)

.

13

#

Remarks

: The expression inside

explicit

is equivalent to:

!is_convertible_v<U1, T1> || !is_convertible_v<U2, T2>

This constructor is defined as deleted if

reference_constructs_from_temporary_v<first_type, U1&&>

is

true

or

reference_constructs_from_temporary_v<second_type, U2&&>

is

true

.

🔗

template<class U1, class U2> constexpr explicit(*see below*) pair(pair<U1, U2>& p);
template<class U1, class U2> constexpr explicit(*see below*) pair(const pair<U1, U2>& p);
template<class U1, class U2> constexpr explicit(*see below*) pair(pair<U1, U2>&& p);
template<class U1, class U2> constexpr explicit(*see below*) pair(const pair<U1, U2>&& p);
template<*pair-like* P> constexpr explicit(*see below*) pair(P&& p);

14

#

Let

*FWD*(u)

be

static_cast<decltype(u)>(u)

.

15

#

Constraints

:

- (15.1)For the last overload, remove_cvref_t<P> is not a specialization of ranges::subrange,
- (15.2)is_constructible_v<T1, decltype(get<0>(*FWD*(p)))> is true, and
- (15.3)is_constructible_v<T2, decltype(get<1>(*FWD*(p)))> is true.

16

#

Effects

: Initializes

first

with

get<0>(*FWD*(p))

and

second

with

get<1>(*FWD*(p))

.

17

#

Remarks

: The expression inside

explicit

is equivalent to:

!is_convertible_v<decltype(get<0>(*FWD*(p))), T1> ||
!is_convertible_v<decltype(get<1>(*FWD*(p))), T2>

The constructor is defined as deleted if

reference_constructs_from_temporary_v<first_type, decltype(get<0>(*FWD*(p)))> ||
reference_constructs_from_temporary_v<second_type, decltype(get<1>(*FWD*(p)))>

is

true

.

🔗

template<class... Args1, class... Args2>
constexpr pair(piecewise_construct_t,
                 tuple<Args1...> first_args, tuple<Args2...> second_args);

18

#

Mandates

:

- (18.1)is_constructible_v<T1, Args1...> is true and
- (18.2)is_constructible_v<T2, Args2...> is true.

19

#

Effects

: Initializes

first

with arguments of types

Args1...

obtained by forwarding the elements of

first_args

and initializes

second

with arguments of types

Args2...

obtained by forwarding the elements of

second_args

.

(Here, forwarding
an element

x

of type

U

within a

tuple

object means calling

std​::​forward<U>(x)

.

)

This form of construction, whereby constructor
arguments for

first

and

second

are each provided in a separate

tuple

object, is called

*piecewise construction*

.

> [
> 
> Note 2
> 
> :
> 
> If a data member of
> 
> pair
> 
> is of reference type and
> its initialization binds it to a temporary object,
> the program is ill-formed (
> 
> [class.base.init]
> 
> )
> 
> .
> 
> —
> 
> end note
> 
> ]

🔗

constexpr pair& operator=(const pair& p);

20

#

Effects

: Assigns

p.first

to

first

and

p.second

to

second

.

21

#

Returns

:

*this

.

22

#

Remarks

: This operator is defined as deleted unless

is_copy_assignable_v<T1>

is

true

and

is_copy_assignable_v<T2>

is

true

.

🔗

constexpr const pair& operator=(const pair& p) const;

23

#

Constraints

:

- (23.1)is_copy_assignable_v<const T1> is true and
- (23.2)is_copy_assignable_v<const T2> is true.

24

#

Effects

: Assigns

p.first

to

first

and

p.second

to

second

.

25

#

Returns

:

*this

.

🔗

template<class U1, class U2> constexpr pair& operator=(const pair<U1, U2>& p);

26

#

Constraints

:

- (26.1)is_assignable_v<T1&, const U1&> is true and
- (26.2)is_assignable_v<T2&, const U2&> is true.

27

#

Effects

: Assigns

p.first

to

first

and

p.second

to

second

.

28

#

Returns

:

*this

.

🔗

template<class U1, class U2> constexpr const pair& operator=(const pair<U1, U2>& p) const;

29

#

Constraints

:

- (29.1)is_assignable_v<const T1&, const U1&> is true, and
- (29.2)is_assignable_v<const T2&, const U2&> is true.

30

#

Effects

: Assigns

p.first

to

first

and

p.second

to

second

.

31

#

Returns

:

*this

.

🔗

constexpr pair& operator=(pair&& p) noexcept(*see below*);

32

#

Constraints

:

- (32.1)is_move_assignable_v<T1> is true and
- (32.2)is_move_assignable_v<T2> is true.

33

#

Effects

: Assigns

std​::​forward<T1>(p.first)

to

first

and

std​::​forward<T2>(p.second)

to

second

.

34

#

Returns

:

*this

.

35

#

Remarks

: The exception specification is equivalent to:

is_nothrow_move_assignable_v<T1> && is_nothrow_move_assignable_v<T2>

🔗

constexpr const pair& operator=(pair&& p) const;

36

#

Constraints

:

- (36.1)is_assignable_v<const T1&, T1> is true and
- (36.2)is_assignable_v<const T2&, T2> is true.

37

#

Effects

: Assigns

std​::​forward<T1>(p.first)

to

first

and

std​::​forward<T2>(p.second)

to

second

.

38

#

Returns

:

*this

.

🔗

template<class U1, class U2> constexpr pair& operator=(pair<U1, U2>&& p);

39

#

Constraints

:

- (39.1)is_assignable_v<T1&, U1> is true and
- (39.2)is_assignable_v<T2&, U2> is true.

40

#

Effects

: Assigns

std​::​forward<U1>(p.first)

first

and

std​::​forward<U2>(p.second)

to

second

.

41

#

Returns

:

*this

.

🔗

template<*pair-like* P> constexpr pair& operator=(P&& p);

42

#

Constraints

:

- (42.1)*different-from*<P, pair> ([range.utility.helpers]) is true,
- (42.2)remove_cvref_t<P> is not a specialization of ranges::subrange,
- (42.3)is_assignable_v<T1&, decltype(get<0>(std::forward<P>(p)))> is true, and
- (42.4)is_assignable_v<T2&, decltype(get<1>(std::forward<P>(p)))> is true.

43

#

Effects

: Assigns

get<0>(std​::​forward<P>(p))

to

first

and

get<1>(std​::​forward<P>(p))

to

second

.

44

#

Returns

:

*this

.

🔗

template<*pair-like* P> constexpr const pair& operator=(P&& p) const;

45

#

Constraints

:

- (45.1)*different-from*<P, pair> ([range.utility.helpers]) is true,
- (45.2)remove_cvref_t<P> is not a specialization of ranges::subrange,
- (45.3)is_assignable_v<const T1&, decltype(get<0>(std::forward<P>(p)))> is true, and
- (45.4)is_assignable_v<const T2&, decltype(get<1>(std::forward<P>(p)))> is true.

46

#

Effects

: Assigns

get<0>(std​::​forward<P>(p))

to

first

and

get<1>(std​::​forward<P>(p))

to

second

.

47

#

Returns

:

*this

.

🔗

template<class U1, class U2> constexpr const pair& operator=(pair<U1, U2>&& p) const;

48

#

Constraints

:

- (48.1)is_assignable_v<const T1&, U1> is true, and
- (48.2)is_assignable_v<const T2&, U2> is true.

49

#

Effects

: Assigns

std​::​forward<U1>(p.first)

to

first

and

std​::​forward<U2>(u.second)

to

second

.

50

#

Returns

:

*this

.

🔗

constexpr void swap(pair& p) noexcept(*see below*);
constexpr void swap(const pair& p) const noexcept(*see below*);

51

#

Mandates

:

- (51.1)For the first overload, is_swappable_v<T1> is true and is_swappable_v<T2> is true.
- (51.2)For the second overload, is_swappable_v<const T1> is true and is_swappable_v<const T2> is true.

52

#

Preconditions

:

first

is swappable with (

[swappable.requirements]

)

p.first

and

second

is swappable with

p.second

.

53

#

Effects

: Swaps

first

with

p.first

and

second

with

p.second

.

54

#

Remarks

: The exception specification is equivalent to:

- (54.1)is_nothrow_swappable_v<T1> && is_nothrow_swappable_v<T2> for the first overload, and
- (54.2)is_nothrow_swappable_v<const T1> && is_nothrow_swappable_v<const T2> for the second overload.

:::

#### tuple.cnstr

:::wording

#### Construction [tuple.cnstr]

1

#

In the descriptions that follow, let

i

be in the range
[

0, sizeof...(Types)

) in order,

Ti

be the

ith

type in

Types

, and

Ui

be the

ith

type in a template parameter pack named

UTypes

, where indexing
is zero-based

.

2

#

For each

tuple

constructor, an exception is thrown only if the construction of
one of the types in

Types

throws an exception

.

3

#

The defaulted move and copy constructor, respectively, of

tuple

is a constexpr function if and only if all
required element-wise initializations for move and copy, respectively,
would be constexpr-suitable (

[dcl.constexpr]

)

.

The defaulted move and copy constructor of

tuple<>

are
constexpr functions

.

4

#

If

is_trivially_destructible_v<Ti>

is

true

for all

Ti

,
then the destructor of

tuple

is trivial

.

5

#

The default constructor of

tuple<>

is trivial

.

🔗

constexpr explicit(*see below*) tuple();

6

#

Constraints

:

is_default_constructible_v<Ti>

is

true

for all

i

.

7

#

Effects

: Value-initializes each element

.

8

#

Remarks

: The expression inside

explicit

evaluates to

true

if and only if

Ti

is not
copy-list-initializable from an empty list
for at least one

i

.

> [
> 
> Note 1
> 
> :
> 
> This behavior can be implemented with a trait that checks whether
> a
> 
> const Ti&
> 
> can be initialized with
> 
> {}
> 
> .
> 
> —
> 
> end note
> 
> ]

🔗

constexpr explicit(*see below*) tuple(const Types&...)
noexcept((is_nothrow_copy_constructible_v<Types> && ...));

9

#

Constraints

:

sizeof...(Types)  ≥ 1

and

is_copy_constructible_v<Ti>

is

true

for all

i

.

10

#

Effects

: Initializes each element with the value of the
corresponding parameter

.

11

#

Remarks

: The expression inside

explicit

is equivalent to:

!conjunction_v<is_convertible<const Types&, Types>...>

🔗

template<class... UTypes> constexpr explicit(*see below*) tuple(UTypes&&... u)
noexcept((is_nothrow_constructible_v<Types, UTypes> && ...));

12

#

Let

*disambiguating-constraint*

be:

- (12.1)negation<is_same<remove_cvref_t<U0>, tuple>> if sizeof...(Types) is 1;
- (12.2)otherwise, bool_constant<!is_same_v<remove_cvref_t<U0>, allocator_arg_t> || is_- same_v<remove_cvref_t<T0>, allocator_arg_t>> if sizeof...(Types) is 2 or 3;
- (12.3)otherwise, true_type.

13

#

Constraints

:

- (13.1)sizeof...(Types) equals sizeof...(UTypes),
- (13.2)sizeof...(Types) ≥ 1, and
- (13.3)conjunction_v<*disambiguating-constraint*, is_constructible<Types, UTypes>...> is true.

14

#

Effects

: Initializes the elements in the tuple with the
corresponding value in

std​::​forward<UTypes>(u)

.

15

#

Remarks

: The expression inside

explicit

is equivalent to:

!conjunction_v<is_convertible<UTypes, Types>...>

This constructor is defined as deleted if

(reference_constructs_from_temporary_v<Types, UTypes&&> || ...)

is

true

.

🔗

tuple(const tuple& u) = default;

16

#

Mandates

:

is_copy_constructible_v<Ti>

is

true

for all

i

.

17

#

Effects

: Initializes each element of

*this

with the
corresponding element of

u

.

🔗

tuple(tuple&& u) = default;

18

#

Constraints

:

is_move_constructible_v<Ti>

is

true

for all

i

.

19

#

Effects

: For all

i

, initializes the

ith

element of

*this

with

std​::​forward<Ti>(get<i>(u))

.

🔗

template<class... UTypes> constexpr explicit(*see below*) tuple(tuple<UTypes...>& u);
template<class... UTypes> constexpr explicit(*see below*) tuple(const tuple<UTypes...>& u);
template<class... UTypes> constexpr explicit(*see below*) tuple(tuple<UTypes...>&& u);
template<class... UTypes> constexpr explicit(*see below*) tuple(const tuple<UTypes...>&& u);

20

#

Let

I

be the pack

0, 1, …, (sizeof...(Types) - 1)

.

Let

*FWD*(u)

be

static_cast<decltype(u)>(u)

.

21

#

Constraints

:

- (21.1)sizeof...(Types) equals sizeof...(UTypes), and
- (21.2)(is_constructible_v<Types, decltype(get<I>(*FWD*(u)))> && ...) is true, and
- (21.3)either sizeof...(Types) is not 1, or (when Types... expands to T and UTypes... expands to U) is_convertible_v<decltype(u), T>, is_constructible_v<T, decltype(u)>, and is_same_v<T, U> are all false.

22

#

Effects

: For all

i

, initializes the

ith

element of

*this

with

get<i>(*FWD*(u))

.

23

#

Remarks

: The expression inside

explicit

is equivalent to:

!(is_convertible_v<decltype(get<I>(*FWD*(u))), Types> && ...)

The constructor is defined as deleted if

(reference_constructs_from_temporary_v<Types, decltype(get<I>(*FWD*(u)))> || ...)

is

true

.

🔗

template<class U1, class U2> constexpr explicit(*see below*) tuple(pair<U1, U2>& u);
template<class U1, class U2> constexpr explicit(*see below*) tuple(const pair<U1, U2>& u);
template<class U1, class U2> constexpr explicit(*see below*) tuple(pair<U1, U2>&& u);
template<class U1, class U2> constexpr explicit(*see below*) tuple(const pair<U1, U2>&& u);

24

#

Let

*FWD*(u)

be

static_cast<decltype(u)>(u)

.

25

#

Constraints

:

- (25.1)sizeof...(Types) is 2,
- (25.2)is_constructible_v<T0, decltype(get<0>(*FWD*(u)))> is true, and
- (25.3)is_constructible_v<T1, decltype(get<1>(*FWD*(u)))> is true.

26

#

Effects

: Initializes the first element with

get<0>(*FWD*(u))

and
the second element with

get<1>(*FWD*(​u))

.

27

#

Remarks

: The expression inside

explicit

is equivalent to:

!is_convertible_v<decltype(get<0>(*FWD*(u))), T0> ||
!is_convertible_v<decltype(get<1>(*FWD*(u))), T1>

The constructor is defined as deleted if

reference_constructs_from_temporary_v<T0, decltype(get<0>(*FWD*(u)))> ||
reference_constructs_from_temporary_v<T1, decltype(get<1>(*FWD*(u)))>

is

true

.

🔗

template<*tuple-like* UTuple>
constexpr explicit(*see below*) tuple(UTuple&& u);

28

#

Let

I

be the pack

0, 1, …, (sizeof...(Types) - 1)

.

29

#

Constraints

:

- (29.1)*different-from*<UTuple, tuple> ([range.utility.helpers]) is true,
- (29.2)remove_cvref_t<UTuple> is not a specialization of ranges::subrange,
- (29.3)sizeof...(Types) equals tuple_size_v<remove_cvref_t<UTuple>>,
- (29.4)(is_constructible_v<Types, decltype(get<I>(std::forward<UTuple>(u)))> && ...) is true, and
- (29.5)either sizeof...(Types) is not 1, or (when Types... expands to T) is_convertible_v<UTuple, T> and is_constructible_v<T, UTuple> are both false.

30

#

Effects

: For all

i

, initializes the

ith

element of

*this

with

get<i>(std​::​forward<UTuple>(u))

.

31

#

Remarks

: The expression inside

explicit

is equivalent to:

!(is_convertible_v<decltype(get<I>(std::forward<UTuple>(u))), Types> && ...)

The constructor is defined as deleted if

(reference_constructs_from_temporary_v<Types, decltype(get<I>(std::forward<UTuple>(u)))>
|| ...)

is

true

.

🔗

template<class Alloc>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a);
template<class Alloc>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, const Types&...);
template<class Alloc, class... UTypes>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, UTypes&&...);
template<class Alloc>
constexpr tuple(allocator_arg_t, const Alloc& a, const tuple&);
template<class Alloc>
constexpr tuple(allocator_arg_t, const Alloc& a, tuple&&);
template<class Alloc, class... UTypes>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&);
template<class Alloc, class... UTypes>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&);
template<class Alloc, class... UTypes>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&);
template<class Alloc, class... UTypes>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&);
template<class Alloc, class U1, class U2>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&);
template<class Alloc, class U1, class U2>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&);
template<class Alloc, class U1, class U2>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&);
template<class Alloc, class U1, class U2>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);
template<class Alloc, *tuple-like* UTuple>
constexpr explicit(*see below*)
    tuple(allocator_arg_t, const Alloc& a, UTuple&&);

32

#

Preconditions

:

Alloc

meets
the

*Cpp17Allocator*

requirements (

[allocator.requirements.general]

)

.

33

#

Effects

: Equivalent to the preceding constructors except that each element is constructed with

uses-allocator construction

.

:::

#### variant.ctor

:::wording

#### Constructors [variant.ctor]

1

#

In the descriptions that follow, let

i

be in the range [

0, sizeof...(Types)

),
and

Ti

be the

ith

type in

Types

.

🔗

constexpr variant() noexcept(*see below*);

2

#

Constraints

:

is_default_constructible_v<T0>

is

true

.

3

#

Effects

: Constructs a

variant

holding a value-initialized value of type

T0

.

4

#

Postconditions

:

valueless_by_exception()

is

false

and

index()

is

0

.

5

#

Throws

: Any exception thrown by the value-initialization of

T0

.

6

#

Remarks

:

This function is constexpr if and only if the
value-initialization of the alternative type T0
would be constexpr-suitable ([dcl.constexpr]).

The exception specification is equivalent to

is_nothrow_default_constructible_v<T0>

.

> [
> 
> Note 1
> 
> :
> 
> See also class
> 
> monostate
> 
> .
> 
> —
> 
> end note
> 
> ]

🔗

constexpr variant(const variant& w) noexcept(*see below*);

7

#

Effects

: If

w

holds a value, initializes the

variant

to hold the same
alternative as

w

and direct-initializes the contained value
with

*GET*<j>(w)

, where

j

is

w.index()

.

Otherwise, initializes the

variant

to not hold a value

.

8

#

Throws

: Any exception thrown by
the initialization of the contained value

.

9

#

Remarks

: This constructor is defined as deleted unless

is_copy_constructible_v<Ti>

is

true

for all

i

.

If

is_trivially_copy_constructible_v<Ti>

is

true

for all

i

, this constructor is trivial

.

The exception specification is equivalent to the logical

and

of

is_nothrow_copy_constructible_v<Ti>

for all

i

.

🔗

constexpr variant(variant&& w) noexcept(*see below*);

10

#

Constraints

:

is_move_constructible_v<Ti>

is

true

for all

i

.

11

#

Effects

: If

w

holds a value, initializes the

variant

to hold the same
alternative as

w

and direct-initializes the contained value with

*GET*<j>(std​::​move(w))

, where

j

is

w.index()

.

Otherwise, initializes the

variant

to not hold a value

.

12

#

Throws

: Any exception thrown by
the initialization of the contained value

.

13

#

Remarks

: The exception specification is equivalent to the logical

and

of

is_nothrow_move_constructible_v<Ti>

for all

i

.

If

is_trivially_move_constructible_v<Ti>

is

true

for all

i

, this constructor is trivial

.

🔗

template<class T> constexpr variant(T&& t) noexcept(*see below*);

14

#

Let

Tj

be a type that is determined as follows:
build an imaginary function

*FUN*(Ti)

for each alternative type

Ti

for which

Ti x[] =

{std​::​forward<T>(t)};

is well-formed for some invented variable

x

.

The overload

*FUN*(Tj)

selected by overload
resolution for the expression

*FUN*(std​::​forward<T>(​t))

defines
the alternative

Tj

which is the type of the contained value after
construction

.

15

#

Constraints

:

- (15.1)sizeof...(Types) is nonzero,
- (15.2)is_same_v<remove_cvref_t<T>, variant> is false,
- (15.3)remove_cvref_t<T> is neither a specialization of in_place_type_t nor a specialization of in_place_index_t,
- (15.4)is_constructible_v<Tj, T> is true, and
- (15.5)the expression *FUN*(std::forward<T>(t)) (with *FUN* being the above-mentioned set of imaginary functions) is well-formed. [*Note 2*: variant<string, string> v("abc"); is ill-formed, as both alternative types have an equally viable constructor for the argument. — *end note*]

16

#

Effects

: Initializes

*this

to hold the alternative type

Tj

and
direct-non-list-initializes the contained value with

std​::​forward<T>(t)

.

17

#

Postconditions

:

holds_alternative<Tj>(*this)

is

true

.

18

#

Throws

: Any exception thrown by
the initialization of the contained value

.

19

#

Remarks

: The exception specification is equivalent to

is_nothrow_constructible_v<Tj, T>

.

If

Tj

's selected constructor is a constexpr constructor,
this constructor is a constexpr constructor

.

🔗

template<class T, class... Args> constexpr explicit variant(in_place_type_t<T>, Args&&... args);

20

#

Constraints

:

- (20.1)There is exactly one occurrence of T in Types... and
- (20.2)is_constructible_v<T, Args...> is true.

21

#

Effects

: Direct-non-list-initializes the contained value of type

T

with

std​::​forward<Args>(args)...

.

22

#

Postconditions

:

holds_alternative<T>(*this)

is

true

.

23

#

Throws

: Any exception thrown by
the initialization of the contained value

.

24

#

Remarks

: If

T

's selected constructor is a constexpr constructor, this
constructor is a constexpr constructor

.

🔗

template<class T, class U, class... Args>
constexpr explicit variant(in_place_type_t<T>, initializer_list<U> il, Args&&... args);

25

#

Constraints

:

- (25.1)There is exactly one occurrence of T in Types... and
- (25.2)is_constructible_v<T, initializer_list<U>&, Args...> is true.

26

#

Effects

: Direct-non-list-initializes the contained value of type

T

with

il, std​::​forward<Args>(​args)...

.

27

#

Postconditions

:

holds_alternative<T>(*this)

is

true

.

28

#

Throws

: Any exception thrown by
the initialization of the contained value

.

29

#

Remarks

: If

T

's selected constructor is a constexpr constructor, this
constructor is a constexpr constructor

.

🔗

template<size_t I, class... Args> constexpr explicit variant(in_place_index_t<I>, Args&&... args);

30

#

Constraints

:

- (30.1)I is less than sizeof...(Types) and
- (30.2)is_constructible_v<TI, Args...> is true.

31

#

Effects

: Direct-non-list-initializes the contained value of type

TI

with

std​::​forward<Args>(args)...

.

32

#

Postconditions

:

index()

is

I

.

33

#

Throws

: Any exception thrown by
the initialization of the contained value

.

34

#

Remarks

: If

TI

's selected constructor is a constexpr constructor, this
constructor is a constexpr constructor

.

🔗

template<size_t I, class U, class... Args>
constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);

35

#

Constraints

:

- (35.1)I is less than sizeof...(Types) and
- (35.2)is_constructible_v<TI, initializer_list<U>&, Args...> is true.

36

#

Effects

: Direct-non-list-initializes the contained value of type

TI

with

il, std​::​forward<Args>(​args)...

.

37

#

Postconditions

:

index()

is

I

.

38

#

Throws

: Any exception thrown by
the initialization of the contained value

.

39

#

Remarks

: If

TI

's selected constructor is a constexpr constructor, this
constructor is a constexpr constructor

.

:::

#### time.duration.general

:::wording

### General [time.duration.general]

1

#

A

duration

type measures time between two points in time (

time_point

s)

.

A

duration

has a representation which holds a count of ticks and a tick period

.

The tick period is the amount of time which occurs from one tick to the next, in units
of seconds

.

It is expressed as a rational constant using the template

ratio

.

🔗

namespace std::chrono {
template<class Rep, class Period = ratio<1>>
class duration {
public:
using rep    = Rep;
    using period = Period::type;

  private:
    rep rep_;       // *exposition only*
public:
// [time.duration.cons], construct/copy/destroy
constexpr duration() = default;
    template<class Rep2>
constexpr explicit duration(const Rep2& r);
    template<class Rep2, class Period2>
constexpr duration(const duration<Rep2, Period2>& d);
    ~duration() = default;
    duration(const duration&) = default;
    duration& operator=(const duration&) = default;

    // [time.duration.observer], observer
constexpr rep count() const;

    // [time.duration.arithmetic], arithmetic
constexpr common_type_t<duration> operator+() const;
    constexpr common_type_t<duration> operator-() const;
    constexpr duration& operator++();
    constexpr duration  operator++(int);
    constexpr duration& operator--();
    constexpr duration  operator--(int);

    constexpr duration& operator+=(const duration& d);
    constexpr duration& operator-=(const duration& d);

    constexpr duration& operator*=(const rep& rhs);
    constexpr duration& operator/=(const rep& rhs);
    constexpr duration& operator%=(const rep& rhs);
    constexpr duration& operator%=(const duration& rhs);

    // [time.duration.special], special values
static constexpr duration zero() noexcept;
    static constexpr duration min() noexcept;
    static constexpr duration max() noexcept;
  };
}

2

#

Rep

shall be an arithmetic type or a class emulating an arithmetic type

.

If a specialization of

duration

is instantiated with
a cv-qualified type or a specialization of

duration

as the argument for the template
parameter

Rep

, the program is ill-formed

.

3

#

If

Period

is not a specialization of

ratio

, the program is ill-formed

.

If

Period​::​num

is not positive, the program is ill-formed

.

4

#

Members of

duration

do not throw exceptions other than
those thrown by the indicated operations on their representations

.

5

#

The defaulted copy constructor of

duration

shall be a
constexpr function if and only if the required initialization
of the member

rep_

for copy and move, respectively, would
be constexpr-suitable (

[dcl.constexpr]

)

.

6

#

> [
> 
> Example 1
> 
> :
> 
> duration<long, ratio<60>> d0;       // holds a count of minutes using a long
> duration<long long, milli> d1;      // holds a count of milliseconds using a long long
> duration<double, ratio<1, 30>>  d2; // holds a count with a tick period of 130 of a second
> // (30 Hz) using a double
> 
> 
> —
> 
> end example
> 
> ]

:::

### Feature test macro

:::wording

#### Predefined macro names [cpp.predefined]

```
__cpp_impl_constexpr_coroutine 2026??L
```

#### Header <version> synopsis [version.syn]

```
#define __cpp_lib_constexpr_coroutine 2026??L // freestanding, also in <coroutine>
```

:::
