Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Algol68
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
C with Coccinelle
C++ with Coccinelle
C++ (Circle)
CIRCT
Clean
Clojure
CMake
CMakeScript
COBOL
C++ for OpenCL
MLIR
Cppx
Cppx-Blue
Cppx-Gold
Cpp2-cppfront
Crystal
C#
CUDA C++
D
Dart
Elixir
Erlang
Fortran
F#
GLSL
Go
Haskell
HLSL
Helion
Hook
Hylo
IL
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Mojo
Nim
Numba
Nix
Objective-C
Objective-C++
OCaml
Odin
OpenCL C
Pascal
Pony
PTX
Python
Racket
Raku
Ruby
Rust
Sail
Snowball
Scala
Slang
Solidity
Spice
SPIR-V
Swift
LLVM TableGen
Toit
Triton
TypeScript Native
V
Vala
Visual Basic
Vyper
WASM
Yul (Solidity IR)
Zig
Javascript
GIMPLE
Ygen
sway
c++ source #1
Output
Compile to binary object
Link to binary
Execute the code
Intel asm syntax
Demangle identifiers
Verbose demangling
Filters
Unused labels
Library functions
Directives
Comments
Horizontal whitespace
Debug intrinsics
Compiler
6502-c++ 11.1.0
ARM GCC 10.2.0
ARM GCC 10.3.0
ARM GCC 10.4.0
ARM GCC 10.5.0
ARM GCC 11.1.0
ARM GCC 11.2.0
ARM GCC 11.3.0
ARM GCC 11.4.0
ARM GCC 12.1.0
ARM GCC 12.2.0
ARM GCC 12.3.0
ARM GCC 12.4.0
ARM GCC 12.5.0
ARM GCC 13.1.0
ARM GCC 13.2.0
ARM GCC 13.2.0 (unknown-eabi)
ARM GCC 13.3.0
ARM GCC 13.3.0 (unknown-eabi)
ARM GCC 13.4.0
ARM GCC 13.4.0 (unknown-eabi)
ARM GCC 14.1.0
ARM GCC 14.1.0 (unknown-eabi)
ARM GCC 14.2.0
ARM GCC 14.2.0 (unknown-eabi)
ARM GCC 14.3.0
ARM GCC 14.3.0 (unknown-eabi)
ARM GCC 15.1.0
ARM GCC 15.1.0 (unknown-eabi)
ARM GCC 15.2.0
ARM GCC 15.2.0 (unknown-eabi)
ARM GCC 4.5.4
ARM GCC 4.6.4
ARM GCC 5.4
ARM GCC 6.3.0
ARM GCC 6.4.0
ARM GCC 7.3.0
ARM GCC 7.5.0
ARM GCC 8.2.0
ARM GCC 8.5.0
ARM GCC 9.3.0
ARM GCC 9.4.0
ARM GCC 9.5.0
ARM GCC trunk
ARM gcc 10.2.1 (none)
ARM gcc 10.3.1 (2021.07 none)
ARM gcc 10.3.1 (2021.10 none)
ARM gcc 11.2.1 (none)
ARM gcc 5.4.1 (none)
ARM gcc 7.2.1 (none)
ARM gcc 8.2 (WinCE)
ARM gcc 8.3.1 (none)
ARM gcc 9.2.1 (none)
ARM msvc v19.0 (ex-WINE)
ARM msvc v19.10 (ex-WINE)
ARM msvc v19.14 (ex-WINE)
ARM64 Morello gcc 10.1 Alpha 2
ARM64 gcc 10.2
ARM64 gcc 10.3
ARM64 gcc 10.4
ARM64 gcc 10.5.0
ARM64 gcc 11.1
ARM64 gcc 11.2
ARM64 gcc 11.3
ARM64 gcc 11.4.0
ARM64 gcc 12.1
ARM64 gcc 12.2.0
ARM64 gcc 12.3.0
ARM64 gcc 12.4.0
ARM64 gcc 12.5.0
ARM64 gcc 13.1.0
ARM64 gcc 13.2.0
ARM64 gcc 13.3.0
ARM64 gcc 13.4.0
ARM64 gcc 14.1.0
ARM64 gcc 14.2.0
ARM64 gcc 14.3.0
ARM64 gcc 15.1.0
ARM64 gcc 15.2.0
ARM64 gcc 4.9.4
ARM64 gcc 5.4
ARM64 gcc 5.5.0
ARM64 gcc 6.3
ARM64 gcc 6.4
ARM64 gcc 7.3
ARM64 gcc 7.5
ARM64 gcc 8.2
ARM64 gcc 8.5
ARM64 gcc 9.3
ARM64 gcc 9.4
ARM64 gcc 9.5
ARM64 gcc trunk
ARM64 msvc v19.14 (ex-WINE)
AVR gcc 10.3.0
AVR gcc 11.1.0
AVR gcc 12.1.0
AVR gcc 12.2.0
AVR gcc 12.3.0
AVR gcc 12.4.0
AVR gcc 12.5.0
AVR gcc 13.1.0
AVR gcc 13.2.0
AVR gcc 13.3.0
AVR gcc 13.4.0
AVR gcc 14.1.0
AVR gcc 14.2.0
AVR gcc 14.3.0
AVR gcc 15.1.0
AVR gcc 15.2.0
AVR gcc 4.5.4
AVR gcc 4.6.4
AVR gcc 5.4.0
AVR gcc 9.2.0
AVR gcc 9.3.0
Arduino Mega (1.8.9)
Arduino Uno (1.8.9)
BPF clang (trunk)
BPF clang 13.0.0
BPF clang 14.0.0
BPF clang 15.0.0
BPF clang 16.0.0
BPF clang 17.0.1
BPF clang 18.1.0
BPF clang 19.1.0
BPF clang 20.1.0
BPF clang 21.1.0
EDG (experimental reflection)
EDG 6.5
EDG 6.5 (GNU mode gcc 13)
EDG 6.6
EDG 6.6 (GNU mode gcc 13)
EDG 6.7
EDG 6.7 (GNU mode gcc 14)
EDG 6.8
EDG 6.8 (GNU mode gcc 15)
FRC 2019
FRC 2020
FRC 2023
HPPA gcc 14.2.0
HPPA gcc 14.3.0
HPPA gcc 15.1.0
HPPA gcc 15.2.0
KVX ACB 4.1.0 (GCC 7.5.0)
KVX ACB 4.1.0-cd1 (GCC 7.5.0)
KVX ACB 4.10.0 (GCC 10.3.1)
KVX ACB 4.11.1 (GCC 10.3.1)
KVX ACB 4.12.0 (GCC 11.3.0)
KVX ACB 4.2.0 (GCC 7.5.0)
KVX ACB 4.3.0 (GCC 7.5.0)
KVX ACB 4.4.0 (GCC 7.5.0)
KVX ACB 4.6.0 (GCC 9.4.1)
KVX ACB 4.8.0 (GCC 9.4.1)
KVX ACB 4.9.0 (GCC 9.4.1)
KVX ACB 5.0.0 (GCC 12.2.1)
KVX ACB 5.2.0 (GCC 13.2.1)
LoongArch64 clang (trunk)
LoongArch64 clang 17.0.1
LoongArch64 clang 18.1.0
LoongArch64 clang 19.1.0
LoongArch64 clang 20.1.0
LoongArch64 clang 21.1.0
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 13.3.0
M68K gcc 13.4.0
M68K gcc 14.1.0
M68K gcc 14.2.0
M68K gcc 14.3.0
M68K gcc 15.1.0
M68K gcc 15.2.0
M68k clang (trunk)
MRISC32 gcc (trunk)
MSP430 gcc 4.5.3
MSP430 gcc 5.3.0
MSP430 gcc 6.2.1
MinGW clang 14.0.3
MinGW clang 14.0.6
MinGW clang 15.0.7
MinGW clang 16.0.0
MinGW clang 16.0.2
MinGW gcc 11.3.0
MinGW gcc 12.1.0
MinGW gcc 12.2.0
MinGW gcc 13.1.0
MinGW gcc 14.3.0
MinGW gcc 15.2.0
RISC-V (32-bits) gcc (trunk)
RISC-V (32-bits) gcc 10.2.0
RISC-V (32-bits) gcc 10.3.0
RISC-V (32-bits) gcc 11.2.0
RISC-V (32-bits) gcc 11.3.0
RISC-V (32-bits) gcc 11.4.0
RISC-V (32-bits) gcc 12.1.0
RISC-V (32-bits) gcc 12.2.0
RISC-V (32-bits) gcc 12.3.0
RISC-V (32-bits) gcc 12.4.0
RISC-V (32-bits) gcc 12.5.0
RISC-V (32-bits) gcc 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 13.3.0
RISC-V (32-bits) gcc 13.4.0
RISC-V (32-bits) gcc 14.1.0
RISC-V (32-bits) gcc 14.2.0
RISC-V (32-bits) gcc 14.3.0
RISC-V (32-bits) gcc 15.1.0
RISC-V (32-bits) gcc 15.2.0
RISC-V (32-bits) gcc 8.2.0
RISC-V (32-bits) gcc 8.5.0
RISC-V (32-bits) gcc 9.4.0
RISC-V (64-bits) gcc (trunk)
RISC-V (64-bits) gcc 10.2.0
RISC-V (64-bits) gcc 10.3.0
RISC-V (64-bits) gcc 11.2.0
RISC-V (64-bits) gcc 11.3.0
RISC-V (64-bits) gcc 11.4.0
RISC-V (64-bits) gcc 12.1.0
RISC-V (64-bits) gcc 12.2.0
RISC-V (64-bits) gcc 12.3.0
RISC-V (64-bits) gcc 12.4.0
RISC-V (64-bits) gcc 12.5.0
RISC-V (64-bits) gcc 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 13.3.0
RISC-V (64-bits) gcc 13.4.0
RISC-V (64-bits) gcc 14.1.0
RISC-V (64-bits) gcc 14.2.0
RISC-V (64-bits) gcc 14.3.0
RISC-V (64-bits) gcc 15.1.0
RISC-V (64-bits) gcc 15.2.0
RISC-V (64-bits) gcc 8.2.0
RISC-V (64-bits) gcc 8.5.0
RISC-V (64-bits) gcc 9.4.0
RISC-V rv32gc clang (trunk)
RISC-V rv32gc clang 10.0.0
RISC-V rv32gc clang 10.0.1
RISC-V rv32gc clang 11.0.0
RISC-V rv32gc clang 11.0.1
RISC-V rv32gc clang 12.0.0
RISC-V rv32gc clang 12.0.1
RISC-V rv32gc clang 13.0.0
RISC-V rv32gc clang 13.0.1
RISC-V rv32gc clang 14.0.0
RISC-V rv32gc clang 15.0.0
RISC-V rv32gc clang 16.0.0
RISC-V rv32gc clang 17.0.1
RISC-V rv32gc clang 18.1.0
RISC-V rv32gc clang 19.1.0
RISC-V rv32gc clang 20.1.0
RISC-V rv32gc clang 21.1.0
RISC-V rv32gc clang 9.0.0
RISC-V rv32gc clang 9.0.1
RISC-V rv64gc clang (trunk)
RISC-V rv64gc clang 10.0.0
RISC-V rv64gc clang 10.0.1
RISC-V rv64gc clang 11.0.0
RISC-V rv64gc clang 11.0.1
RISC-V rv64gc clang 12.0.0
RISC-V rv64gc clang 12.0.1
RISC-V rv64gc clang 13.0.0
RISC-V rv64gc clang 13.0.1
RISC-V rv64gc clang 14.0.0
RISC-V rv64gc clang 15.0.0
RISC-V rv64gc clang 16.0.0
RISC-V rv64gc clang 17.0.1
RISC-V rv64gc clang 18.1.0
RISC-V rv64gc clang 19.1.0
RISC-V rv64gc clang 20.1.0
RISC-V rv64gc clang 21.1.0
RISC-V rv64gc clang 9.0.0
RISC-V rv64gc clang 9.0.1
Raspbian Buster
Raspbian Stretch
SPARC LEON gcc 12.2.0
SPARC LEON gcc 12.3.0
SPARC LEON gcc 12.4.0
SPARC LEON gcc 12.5.0
SPARC LEON gcc 13.1.0
SPARC LEON gcc 13.2.0
SPARC LEON gcc 13.3.0
SPARC LEON gcc 13.4.0
SPARC LEON gcc 14.1.0
SPARC LEON gcc 14.2.0
SPARC LEON gcc 14.3.0
SPARC LEON gcc 15.1.0
SPARC LEON gcc 15.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 12.4.0
SPARC gcc 12.5.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 13.3.0
SPARC gcc 13.4.0
SPARC gcc 14.1.0
SPARC gcc 14.2.0
SPARC gcc 14.3.0
SPARC gcc 15.1.0
SPARC gcc 15.2.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 12.4.0
SPARC64 gcc 12.5.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 13.3.0
SPARC64 gcc 13.4.0
SPARC64 gcc 14.1.0
SPARC64 gcc 14.2.0
SPARC64 gcc 14.3.0
SPARC64 gcc 15.1.0
SPARC64 gcc 15.2.0
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 12.4.0
TI C6x gcc 12.5.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 13.3.0
TI C6x gcc 13.4.0
TI C6x gcc 14.1.0
TI C6x gcc 14.2.0
TI C6x gcc 14.3.0
TI C6x gcc 15.1.0
TI C6x gcc 15.2.0
TI CL430 21.6.1
Tricore gcc 11.3.0 (EEESlab)
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
VAX gcc NetBSDELF 12.4.0 (Apr 16 05:27 2025)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
Xtensa ESP32 gcc 14.2.0 (20241119)
Xtensa ESP32 gcc 8.2.0 (2019r2)
Xtensa ESP32 gcc 8.2.0 (2020r1)
Xtensa ESP32 gcc 8.2.0 (2020r2)
Xtensa ESP32 gcc 8.4.0 (2020r3)
Xtensa ESP32 gcc 8.4.0 (2021r1)
Xtensa ESP32 gcc 8.4.0 (2021r2)
Xtensa ESP32-S2 gcc 11.2.0 (2022r1)
Xtensa ESP32-S2 gcc 12.2.0 (20230208)
Xtensa ESP32-S2 gcc 14.2.0 (20241119)
Xtensa ESP32-S2 gcc 8.2.0 (2019r2)
Xtensa ESP32-S2 gcc 8.2.0 (2020r1)
Xtensa ESP32-S2 gcc 8.2.0 (2020r2)
Xtensa ESP32-S2 gcc 8.4.0 (2020r3)
Xtensa ESP32-S2 gcc 8.4.0 (2021r1)
Xtensa ESP32-S2 gcc 8.4.0 (2021r2)
Xtensa ESP32-S3 gcc 11.2.0 (2022r1)
Xtensa ESP32-S3 gcc 12.2.0 (20230208)
Xtensa ESP32-S3 gcc 14.2.0 (20241119)
Xtensa ESP32-S3 gcc 8.4.0 (2020r3)
Xtensa ESP32-S3 gcc 8.4.0 (2021r1)
Xtensa ESP32-S3 gcc 8.4.0 (2021r2)
arm64 msvc v19.20 VS16.0
arm64 msvc v19.21 VS16.1
arm64 msvc v19.22 VS16.2
arm64 msvc v19.23 VS16.3
arm64 msvc v19.24 VS16.4
arm64 msvc v19.25 VS16.5
arm64 msvc v19.27 VS16.7
arm64 msvc v19.28 VS16.8
arm64 msvc v19.28 VS16.9
arm64 msvc v19.29 VS16.10
arm64 msvc v19.29 VS16.11
arm64 msvc v19.30 VS17.0
arm64 msvc v19.31 VS17.1
arm64 msvc v19.32 VS17.2
arm64 msvc v19.33 VS17.3
arm64 msvc v19.34 VS17.4
arm64 msvc v19.35 VS17.5
arm64 msvc v19.36 VS17.6
arm64 msvc v19.37 VS17.7
arm64 msvc v19.38 VS17.8
arm64 msvc v19.39 VS17.9
arm64 msvc v19.40 VS17.10
arm64 msvc v19.41 VS17.11
arm64 msvc v19.42 VS17.12
arm64 msvc v19.43 VS17.13
arm64 msvc v19.44 VS17.14
arm64 msvc v19.latest
armv7-a clang (trunk)
armv7-a clang 10.0.0
armv7-a clang 10.0.1
armv7-a clang 11.0.0
armv7-a clang 11.0.1
armv7-a clang 12.0.0
armv7-a clang 12.0.1
armv7-a clang 13.0.0
armv7-a clang 13.0.1
armv7-a clang 14.0.0
armv7-a clang 15.0.0
armv7-a clang 16.0.0
armv7-a clang 17.0.1
armv7-a clang 18.1.0
armv7-a clang 19.1.0
armv7-a clang 20.1.0
armv7-a clang 21.1.0
armv7-a clang 9.0.0
armv7-a clang 9.0.1
armv8-a clang (all architectural features, trunk)
armv8-a clang (trunk)
armv8-a clang 10.0.0
armv8-a clang 10.0.1
armv8-a clang 11.0.0
armv8-a clang 11.0.1
armv8-a clang 12.0.0
armv8-a clang 13.0.0
armv8-a clang 14.0.0
armv8-a clang 15.0.0
armv8-a clang 16.0.0
armv8-a clang 17.0.1
armv8-a clang 18.1.0
armv8-a clang 19.1.0
armv8-a clang 20.1.0
armv8-a clang 21.1.0
armv8-a clang 9.0.0
armv8-a clang 9.0.1
clad trunk (clang 21.1.0)
clad v1.10 (clang 20.1.0)
clad v1.8 (clang 18.1.0)
clad v1.9 (clang 19.1.0)
clad v2.00 (clang 20.1.0)
clad v2.1 (clang 21.1.0)
clad v2.2 (clang 21.1.0)
clang-cl 18.1.0
ellcc 0.1.33
ellcc 0.1.34
ellcc 2017-07-16
ez80-clang 15.0.0
ez80-clang 15.0.7
hexagon-clang 16.0.5
llvm-mos atari2600-3e
llvm-mos atari2600-4k
llvm-mos atari2600-common
llvm-mos atari5200-supercart
llvm-mos atari8-cart-megacart
llvm-mos atari8-cart-std
llvm-mos atari8-cart-xegs
llvm-mos atari8-common
llvm-mos atari8-dos
llvm-mos c128
llvm-mos c64
llvm-mos commodore
llvm-mos cpm65
llvm-mos cx16
llvm-mos dodo
llvm-mos eater
llvm-mos mega65
llvm-mos nes
llvm-mos nes-action53
llvm-mos nes-cnrom
llvm-mos nes-gtrom
llvm-mos nes-mmc1
llvm-mos nes-mmc3
llvm-mos nes-nrom
llvm-mos nes-unrom
llvm-mos nes-unrom-512
llvm-mos osi-c1p
llvm-mos pce
llvm-mos pce-cd
llvm-mos pce-common
llvm-mos pet
llvm-mos rp6502
llvm-mos rpc8e
llvm-mos supervision
llvm-mos vic20
loongarch64 gcc 12.2.0
loongarch64 gcc 12.3.0
loongarch64 gcc 12.4.0
loongarch64 gcc 12.5.0
loongarch64 gcc 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 13.3.0
loongarch64 gcc 13.4.0
loongarch64 gcc 14.1.0
loongarch64 gcc 14.2.0
loongarch64 gcc 14.3.0
loongarch64 gcc 15.1.0
loongarch64 gcc 15.2.0
mips clang 13.0.0
mips clang 14.0.0
mips clang 15.0.0
mips clang 16.0.0
mips clang 17.0.1
mips clang 18.1.0
mips clang 19.1.0
mips clang 20.1.0
mips clang 21.1.0
mips gcc 11.2.0
mips gcc 12.1.0
mips gcc 12.2.0
mips gcc 12.3.0
mips gcc 12.4.0
mips gcc 12.5.0
mips gcc 13.1.0
mips gcc 13.2.0
mips gcc 13.3.0
mips gcc 13.4.0
mips gcc 14.1.0
mips gcc 14.2.0
mips gcc 14.3.0
mips gcc 15.1.0
mips gcc 15.2.0
mips gcc 4.9.4
mips gcc 5.4
mips gcc 5.5.0
mips gcc 9.3.0 (codescape)
mips gcc 9.5.0
mips64 (el) gcc 12.1.0
mips64 (el) gcc 12.2.0
mips64 (el) gcc 12.3.0
mips64 (el) gcc 12.4.0
mips64 (el) gcc 12.5.0
mips64 (el) gcc 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 13.3.0
mips64 (el) gcc 13.4.0
mips64 (el) gcc 14.1.0
mips64 (el) gcc 14.2.0
mips64 (el) gcc 14.3.0
mips64 (el) gcc 15.1.0
mips64 (el) gcc 15.2.0
mips64 (el) gcc 4.9.4
mips64 (el) gcc 5.4.0
mips64 (el) gcc 5.5.0
mips64 (el) gcc 9.5.0
mips64 clang 13.0.0
mips64 clang 14.0.0
mips64 clang 15.0.0
mips64 clang 16.0.0
mips64 clang 17.0.1
mips64 clang 18.1.0
mips64 clang 19.1.0
mips64 clang 20.1.0
mips64 clang 21.1.0
mips64 gcc 11.2.0
mips64 gcc 12.1.0
mips64 gcc 12.2.0
mips64 gcc 12.3.0
mips64 gcc 12.4.0
mips64 gcc 12.5.0
mips64 gcc 13.1.0
mips64 gcc 13.2.0
mips64 gcc 13.3.0
mips64 gcc 13.4.0
mips64 gcc 14.1.0
mips64 gcc 14.2.0
mips64 gcc 14.3.0
mips64 gcc 15.1.0
mips64 gcc 15.2.0
mips64 gcc 4.9.4
mips64 gcc 5.4.0
mips64 gcc 5.5.0
mips64 gcc 9.5.0
mips64el clang 13.0.0
mips64el clang 14.0.0
mips64el clang 15.0.0
mips64el clang 16.0.0
mips64el clang 17.0.1
mips64el clang 18.1.0
mips64el clang 19.1.0
mips64el clang 20.1.0
mips64el clang 21.1.0
mipsel clang 13.0.0
mipsel clang 14.0.0
mipsel clang 15.0.0
mipsel clang 16.0.0
mipsel clang 17.0.1
mipsel clang 18.1.0
mipsel clang 19.1.0
mipsel clang 20.1.0
mipsel clang 21.1.0
mipsel gcc 12.1.0
mipsel gcc 12.2.0
mipsel gcc 12.3.0
mipsel gcc 12.4.0
mipsel gcc 12.5.0
mipsel gcc 13.1.0
mipsel gcc 13.2.0
mipsel gcc 13.3.0
mipsel gcc 13.4.0
mipsel gcc 14.1.0
mipsel gcc 14.2.0
mipsel gcc 14.3.0
mipsel gcc 15.1.0
mipsel gcc 15.2.0
mipsel gcc 4.9.4
mipsel gcc 5.4.0
mipsel gcc 5.5.0
mipsel gcc 9.5.0
nanoMIPS gcc 6.3.0 (mtk)
power gcc 11.2.0
power gcc 12.1.0
power gcc 12.2.0
power gcc 12.3.0
power gcc 12.4.0
power gcc 12.5.0
power gcc 13.1.0
power gcc 13.2.0
power gcc 13.3.0
power gcc 13.4.0
power gcc 14.1.0
power gcc 14.2.0
power gcc 14.3.0
power gcc 15.1.0
power gcc 15.2.0
power gcc 4.8.5
power64 AT12.0 (gcc8)
power64 AT13.0 (gcc9)
power64 gcc 11.2.0
power64 gcc 12.1.0
power64 gcc 12.2.0
power64 gcc 12.3.0
power64 gcc 12.4.0
power64 gcc 12.5.0
power64 gcc 13.1.0
power64 gcc 13.2.0
power64 gcc 13.3.0
power64 gcc 13.4.0
power64 gcc 14.1.0
power64 gcc 14.2.0
power64 gcc 14.3.0
power64 gcc 15.1.0
power64 gcc 15.2.0
power64 gcc trunk
power64le AT12.0 (gcc8)
power64le AT13.0 (gcc9)
power64le clang (trunk)
power64le gcc 11.2.0
power64le gcc 12.1.0
power64le gcc 12.2.0
power64le gcc 12.3.0
power64le gcc 12.4.0
power64le gcc 12.5.0
power64le gcc 13.1.0
power64le gcc 13.2.0
power64le gcc 13.3.0
power64le gcc 13.4.0
power64le gcc 14.1.0
power64le gcc 14.2.0
power64le gcc 14.3.0
power64le gcc 15.1.0
power64le gcc 15.2.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
qnx 8.0.0
s390x gcc 11.2.0
s390x gcc 12.1.0
s390x gcc 12.2.0
s390x gcc 12.3.0
s390x gcc 12.4.0
s390x gcc 12.5.0
s390x gcc 13.1.0
s390x gcc 13.2.0
s390x gcc 13.3.0
s390x gcc 13.4.0
s390x gcc 14.1.0
s390x gcc 14.2.0
s390x gcc 14.3.0
s390x gcc 15.1.0
s390x gcc 15.2.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 12.4.0
sh gcc 12.5.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 13.3.0
sh gcc 13.4.0
sh gcc 14.1.0
sh gcc 14.2.0
sh gcc 14.3.0
sh gcc 15.1.0
sh gcc 15.2.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (ex-WINE)
x64 msvc v19.10 (ex-WINE)
x64 msvc v19.14 (ex-WINE)
x64 msvc v19.20 VS16.0
x64 msvc v19.21 VS16.1
x64 msvc v19.22 VS16.2
x64 msvc v19.23 VS16.3
x64 msvc v19.24 VS16.4
x64 msvc v19.25 VS16.5
x64 msvc v19.27 VS16.7
x64 msvc v19.28 VS16.8
x64 msvc v19.28 VS16.9
x64 msvc v19.29 VS16.10
x64 msvc v19.29 VS16.11
x64 msvc v19.30 VS17.0
x64 msvc v19.31 VS17.1
x64 msvc v19.32 VS17.2
x64 msvc v19.33 VS17.3
x64 msvc v19.34 VS17.4
x64 msvc v19.35 VS17.5
x64 msvc v19.36 VS17.6
x64 msvc v19.37 VS17.7
x64 msvc v19.38 VS17.8
x64 msvc v19.39 VS17.9
x64 msvc v19.40 VS17.10
x64 msvc v19.41 VS17.11
x64 msvc v19.42 VS17.12
x64 msvc v19.43 VS17.13
x64 msvc v19.44 VS17.14
x64 msvc v19.latest
x86 djgpp 4.9.4
x86 djgpp 5.5.0
x86 djgpp 6.4.0
x86 djgpp 7.2.0
x86 g++ 1.27
x86 msvc v19.0 (ex-WINE)
x86 msvc v19.10 (ex-WINE)
x86 msvc v19.14 (ex-WINE)
x86 msvc v19.20 VS16.0
x86 msvc v19.21 VS16.1
x86 msvc v19.22 VS16.2
x86 msvc v19.23 VS16.3
x86 msvc v19.24 VS16.4
x86 msvc v19.25 VS16.5
x86 msvc v19.27 VS16.7
x86 msvc v19.28 VS16.8
x86 msvc v19.28 VS16.9
x86 msvc v19.29 VS16.10
x86 msvc v19.29 VS16.11
x86 msvc v19.30 VS17.0
x86 msvc v19.31 VS17.1
x86 msvc v19.32 VS17.2
x86 msvc v19.33 VS17.3
x86 msvc v19.34 VS17.4
x86 msvc v19.35 VS17.5
x86 msvc v19.36 VS17.6
x86 msvc v19.37 VS17.7
x86 msvc v19.38 VS17.8
x86 msvc v19.39 VS17.9
x86 msvc v19.40 VS17.10
x86 msvc v19.41 VS17.11
x86 msvc v19.42 VS17.12
x86 msvc v19.43 VS17.13
x86 msvc v19.44 VS17.14
x86 msvc v19.latest
x86 nvc++ 22.11
x86 nvc++ 22.7
x86 nvc++ 22.9
x86 nvc++ 23.1
x86 nvc++ 23.11
x86 nvc++ 23.3
x86 nvc++ 23.5
x86 nvc++ 23.7
x86 nvc++ 23.9
x86 nvc++ 24.1
x86 nvc++ 24.11
x86 nvc++ 24.3
x86 nvc++ 24.5
x86 nvc++ 24.7
x86 nvc++ 24.9
x86 nvc++ 25.1
x86 nvc++ 25.11
x86 nvc++ 25.3
x86 nvc++ 25.5
x86 nvc++ 25.7
x86 nvc++ 25.9
x86-64 Zapcc 190308
x86-64 clang (-fimplicit-constexpr)
x86-64 clang (Chris Bazley N3089)
x86-64 clang (EricWF contracts)
x86-64 clang (amd-staging)
x86-64 clang (assertions trunk)
x86-64 clang (clangir)
x86-64 clang (experimental -Wlifetime)
x86-64 clang (experimental P1061)
x86-64 clang (experimental P1144)
x86-64 clang (experimental P1221)
x86-64 clang (experimental P2561)
x86-64 clang (experimental P2998)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
x86-64 clang (experimental P3334)
x86-64 clang (experimental P3367)
x86-64 clang (experimental P3372)
x86-64 clang (experimental P3385)
x86-64 clang (experimental P3776)
x86-64 clang (experimental metaprogramming - P2632)
x86-64 clang (old concepts branch)
x86-64 clang (p1974)
x86-64 clang (pattern matching - P2688)
x86-64 clang (reflection - C++26)
x86-64 clang (reflection - TS)
x86-64 clang (resugar)
x86-64 clang (string interpolation - P3412)
x86-64 clang (thephd.dev)
x86-64 clang (trunk)
x86-64 clang (variadic friends - P2893)
x86-64 clang (widberg)
x86-64 clang 10.0.0
x86-64 clang 10.0.0 (assertions)
x86-64 clang 10.0.1
x86-64 clang 11.0.0
x86-64 clang 11.0.0 (assertions)
x86-64 clang 11.0.1
x86-64 clang 12.0.0
x86-64 clang 12.0.0 (assertions)
x86-64 clang 12.0.1
x86-64 clang 13.0.0
x86-64 clang 13.0.0 (assertions)
x86-64 clang 13.0.1
x86-64 clang 14.0.0
x86-64 clang 14.0.0 (assertions)
x86-64 clang 15.0.0
x86-64 clang 15.0.0 (assertions)
x86-64 clang 16.0.0
x86-64 clang 16.0.0 (assertions)
x86-64 clang 17.0.1
x86-64 clang 17.0.1 (assertions)
x86-64 clang 18.1.0
x86-64 clang 18.1.0 (assertions)
x86-64 clang 19.1.0
x86-64 clang 19.1.0 (assertions)
x86-64 clang 2.6.0 (assertions)
x86-64 clang 2.7.0 (assertions)
x86-64 clang 2.8.0 (assertions)
x86-64 clang 2.9.0 (assertions)
x86-64 clang 20.1.0
x86-64 clang 20.1.0 (assertions)
x86-64 clang 21.1.0
x86-64 clang 21.1.0 (assertions)
x86-64 clang 3.0.0
x86-64 clang 3.0.0 (assertions)
x86-64 clang 3.1
x86-64 clang 3.1 (assertions)
x86-64 clang 3.2
x86-64 clang 3.2 (assertions)
x86-64 clang 3.3
x86-64 clang 3.3 (assertions)
x86-64 clang 3.4 (assertions)
x86-64 clang 3.4.1
x86-64 clang 3.5
x86-64 clang 3.5 (assertions)
x86-64 clang 3.5.1
x86-64 clang 3.5.2
x86-64 clang 3.6
x86-64 clang 3.6 (assertions)
x86-64 clang 3.7
x86-64 clang 3.7 (assertions)
x86-64 clang 3.7.1
x86-64 clang 3.8
x86-64 clang 3.8 (assertions)
x86-64 clang 3.8.1
x86-64 clang 3.9.0
x86-64 clang 3.9.0 (assertions)
x86-64 clang 3.9.1
x86-64 clang 4.0.0
x86-64 clang 4.0.0 (assertions)
x86-64 clang 4.0.1
x86-64 clang 5.0.0
x86-64 clang 5.0.0 (assertions)
x86-64 clang 5.0.1
x86-64 clang 5.0.2
x86-64 clang 6.0.0
x86-64 clang 6.0.0 (assertions)
x86-64 clang 6.0.1
x86-64 clang 7.0.0
x86-64 clang 7.0.0 (assertions)
x86-64 clang 7.0.1
x86-64 clang 7.1.0
x86-64 clang 8.0.0
x86-64 clang 8.0.0 (assertions)
x86-64 clang 8.0.1
x86-64 clang 9.0.0
x86-64 clang 9.0.0 (assertions)
x86-64 clang 9.0.1
x86-64 clang rocm-4.5.2
x86-64 clang rocm-5.0.2
x86-64 clang rocm-5.1.3
x86-64 clang rocm-5.2.3
x86-64 clang rocm-5.3.3
x86-64 clang rocm-5.7.0
x86-64 clang rocm-6.0.2
x86-64 clang rocm-6.1.2
x86-64 clang rocm-6.2.4
x86-64 clang rocm-6.3.3
x86-64 clang rocm-6.4.0
x86-64 clang rocm-7.0.1
x86-64 gcc (C++26 contracts + GNU extensions)
x86-64 gcc (C++26 contracts)
x86-64 gcc (C++26 reflection)
x86-64 gcc (P2034 lambdas)
x86-64 gcc (Thomas Healy)
x86-64 gcc (contract labels)
x86-64 gcc (contracts natural syntax)
x86-64 gcc (contracts)
x86-64 gcc (coroutines)
x86-64 gcc (modules)
x86-64 gcc (trunk)
x86-64 gcc 10.1
x86-64 gcc 10.2
x86-64 gcc 10.3
x86-64 gcc 10.3 (assertions)
x86-64 gcc 10.4
x86-64 gcc 10.4 (assertions)
x86-64 gcc 10.5
x86-64 gcc 10.5 (assertions)
x86-64 gcc 11.1
x86-64 gcc 11.1 (assertions)
x86-64 gcc 11.2
x86-64 gcc 11.2 (assertions)
x86-64 gcc 11.3
x86-64 gcc 11.3 (assertions)
x86-64 gcc 11.4
x86-64 gcc 11.4 (assertions)
x86-64 gcc 12.1
x86-64 gcc 12.1 (assertions)
x86-64 gcc 12.2
x86-64 gcc 12.2 (assertions)
x86-64 gcc 12.3
x86-64 gcc 12.3 (assertions)
x86-64 gcc 12.4
x86-64 gcc 12.4 (assertions)
x86-64 gcc 12.5
x86-64 gcc 12.5 (assertions)
x86-64 gcc 13.1
x86-64 gcc 13.1 (assertions)
x86-64 gcc 13.2
x86-64 gcc 13.2 (assertions)
x86-64 gcc 13.3
x86-64 gcc 13.3 (assertions)
x86-64 gcc 13.4
x86-64 gcc 13.4 (assertions)
x86-64 gcc 14.1
x86-64 gcc 14.1 (assertions)
x86-64 gcc 14.2
x86-64 gcc 14.2 (assertions)
x86-64 gcc 14.3
x86-64 gcc 14.3 (assertions)
x86-64 gcc 15.1
x86-64 gcc 15.1 (assertions)
x86-64 gcc 15.2
x86-64 gcc 15.2 (assertions)
x86-64 gcc 3.4.6
x86-64 gcc 4.0.4
x86-64 gcc 4.1.2
x86-64 gcc 4.4.7
x86-64 gcc 4.5.3
x86-64 gcc 4.6.4
x86-64 gcc 4.7.1
x86-64 gcc 4.7.2
x86-64 gcc 4.7.3
x86-64 gcc 4.7.4
x86-64 gcc 4.8.1
x86-64 gcc 4.8.2
x86-64 gcc 4.8.3
x86-64 gcc 4.8.4
x86-64 gcc 4.8.5
x86-64 gcc 4.9.0
x86-64 gcc 4.9.1
x86-64 gcc 4.9.2
x86-64 gcc 4.9.3
x86-64 gcc 4.9.4
x86-64 gcc 5.1
x86-64 gcc 5.2
x86-64 gcc 5.3
x86-64 gcc 5.4
x86-64 gcc 5.5
x86-64 gcc 6.1
x86-64 gcc 6.2
x86-64 gcc 6.3
x86-64 gcc 6.4
x86-64 gcc 6.5
x86-64 gcc 7.1
x86-64 gcc 7.2
x86-64 gcc 7.3
x86-64 gcc 7.4
x86-64 gcc 7.5
x86-64 gcc 8.1
x86-64 gcc 8.2
x86-64 gcc 8.3
x86-64 gcc 8.4
x86-64 gcc 8.5
x86-64 gcc 9.1
x86-64 gcc 9.2
x86-64 gcc 9.3
x86-64 gcc 9.4
x86-64 gcc 9.5
x86-64 icc 13.0.1
x86-64 icc 16.0.3
x86-64 icc 17.0.0
x86-64 icc 18.0.0
x86-64 icc 19.0.0
x86-64 icc 19.0.1
x86-64 icc 2021.1.2
x86-64 icc 2021.10.0
x86-64 icc 2021.2.0
x86-64 icc 2021.3.0
x86-64 icc 2021.4.0
x86-64 icc 2021.5.0
x86-64 icc 2021.6.0
x86-64 icc 2021.7.0
x86-64 icc 2021.7.1
x86-64 icc 2021.8.0
x86-64 icc 2021.9.0
x86-64 icx 2021.1.2
x86-64 icx 2021.2.0
x86-64 icx 2021.3.0
x86-64 icx 2021.4.0
x86-64 icx 2022.0.0
x86-64 icx 2022.1.0
x86-64 icx 2022.2.0
x86-64 icx 2022.2.1
x86-64 icx 2023.0.0
x86-64 icx 2023.1.0
x86-64 icx 2023.2.1
x86-64 icx 2024.0.0
x86-64 icx 2024.1.0
x86-64 icx 2024.2.0
x86-64 icx 2024.2.1
x86-64 icx 2025.0.0
x86-64 icx 2025.0.1
x86-64 icx 2025.0.3
x86-64 icx 2025.0.4
x86-64 icx 2025.1.0
x86-64 icx 2025.1.1
x86-64 icx 2025.2.0
x86-64 icx 2025.2.1
x86-64 icx 2025.3.0
x86-64 icx 2025.3.1
x86-64 icx 2025.3.1
z180-clang 15.0.0
z180-clang 15.0.7
z80-clang 15.0.0
z80-clang 15.0.7
zig c++ 0.10.0
zig c++ 0.11.0
zig c++ 0.12.0
zig c++ 0.12.1
zig c++ 0.13.0
zig c++ 0.14.0
zig c++ 0.14.1
zig c++ 0.15.1
zig c++ 0.15.2
zig c++ 0.6.0
zig c++ 0.7.0
zig c++ 0.7.1
zig c++ 0.8.0
zig c++ 0.9.0
zig c++ trunk
Options
Source code
#include <iterator> #include <memory> #include <utility> #include <type_traits> #include <coroutine> #include <cassert> #include <cassert> #define KELCORO_CO_AWAIT_REQUIRED [[nodiscard("forget co_await?")]] #if defined(__GNUC__) || defined(__clang__) #define KELCORO_UNREACHABLE __builtin_unreachable() #elif defined(_MSC_VER) #define KELCORO_UNREACHABLE __assume(false) #else #define KELCORO_UNREACHABLE assert(false) #endif #define KELCORO_ASSUME(expr) \ if (!(expr)) \ KELCORO_UNREACHABLE // for some implementation reasons clang adds noinline on 'await_suspend' // https://github.com/llvm/llvm-project/issues/64945 // As workaround to not affect performance I explicitly mark 'await_suspend' as always inline // if no one can observe changes on coroutine frame after 'await_suspend' start until its end(including // returning) #ifdef __clang__ #define KELCORO_ASSUME_NOONE_SEES [[gnu::always_inline]] #else #define KELCORO_ASSUME_NOONE_SEES #endif #if defined(_MSC_VER) && !defined(__clang__) #define KELCORO_LIFETIMEBOUND [[msvc::lifetimebound]] #elif defined(__clang__) #define KELCORO_LIFETIMEBOUND [[clang::lifetimebound]] #else #define KELCORO_LIFETIMEBOUND #endif #if defined(_MSC_VER) && !defined(__clang__) #define KELCORO_PURE #else #define KELCORO_PURE [[gnu::pure]] #endif #ifdef _MSC_VER #define KELCORO_MSVC_EBO __declspec(empty_bases) #else #define KELCORO_MSVC_EBO #endif #ifdef __has_cpp_attribute #if __has_cpp_attribute(no_unique_address) #define KELCORO_NO_UNIQUE_ADDRESS [[no_unique_address]] #elif __has_cpp_attribute(msvc::no_unique_address) #define KELCORO_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #else #define KELCORO_NO_UNIQUE_ADDRESS #endif #if __has_cpp_attribute(clang::coro_await_elidable) #define KELCORO_ELIDE_CTX [[clang::coro_await_elidable]] #else #define KELCORO_ELIDE_CTX #endif #if __has_cpp_attribute(clang::coro_await_elidable_argument) #if defined(__clang__) && (__clang_major__ >= 20 && __clang_major__ < 22) // compiler bug with modules (at this point i dont know if modules used and what bug can happen, // its info from clang coroutine implementer) // https://github.com/alibaba/async_simple/blob/82f2b09f1280c1daf389ece82751e5420a4f2455/async_simple/CommonMacros.h#L59 #define KELCORO_ELIDABLE_ARG #else #define KELCORO_ELIDABLE_ARG [[clang::coro_await_elidable_argument]] #endif #else #define KELCORO_ELIDABLE_ARG #endif #else #define KELCORO_NO_UNIQUE_ADDRESS #define KELCORO_ELIDE_CTX #define KELCORO_ELIDABLE_ARG #endif // used in coroutine promise type to get default behavior // for all types which not handled by await_transform #define KELCORO_DEFAULT_AWAIT_TRANSFORM \ template <typename T> \ static T&& await_transform(T&& v) noexcept { \ return (T&&)(v); \ } #if __cpp_aggregate_paren_init < 201902L #define KELCORO_AGGREGATE_PAREN_INIT 0 #else #define KELCORO_AGGREGATE_PAREN_INIT 1 #endif #include <coroutine> #include <thread> #include <utility> namespace dd { enum struct schedule_errc : int { ok, cancelled, // if task awaited with this code, it should not produce new tasks timed_out, // not used now executor_overload, // not used now }; struct schedule_status { schedule_errc what = schedule_errc::ok; constexpr explicit operator bool() const noexcept { return what == schedule_errc::ok; } }; struct task_node { task_node* next = nullptr; std::coroutine_handle<> task = nullptr; schedule_errc status = schedule_errc::ok; static task_node deadpill() { return task_node{.next = nullptr, .task = nullptr}; } bool is_deadpill() const noexcept { return task == nullptr; } }; template <typename T> concept executor = requires(T& exe, task_node* node) { // preconditions for .attach: // node && node->task // node live until node->task.resume() and will be invalidated immediately after that exe.attach(node); // effect: schedules node->task to be executed on 'exe' }; struct any_executor_ref { private: // if nullptr, then thread pool ) void (*attach_node)(void*, task_node*); void* data; template <typename T> static void do_attach(void* e, task_node* node) { T& exe = *static_cast<T*>(e); exe.attach(node); } public: template <executor E> constexpr any_executor_ref(E& exe KELCORO_LIFETIMEBOUND) requires(!std::same_as<std::remove_volatile_t<E>, any_executor_ref> && !std::is_const_v<E>) : attach_node(&do_attach<E>), data(std::addressof(exe)) { } any_executor_ref(const any_executor_ref&) = default; any_executor_ref(any_executor_ref&&) = default; void attach(task_node* node) { attach_node(data, node); } }; // attaches all tasks from linked list starting from 'top' void attach_list(executor auto& e, task_node* top) { while (top) { task_node* next = top->next; e.attach(top); top = next; } } // returns new begin [[nodiscard]] constexpr task_node* reverse_list(task_node* top) noexcept { task_node* prev = nullptr; while (top) { task_node* next = std::exchange(top->next, prev); prev = std::exchange(top, next); } return prev; } template <typename E> struct KELCORO_CO_AWAIT_REQUIRED create_node_and_attach : task_node { E& e; // creates task node and attaches it explicit create_node_and_attach(std::type_identity_t<E>& e) noexcept : e(e) { } static bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle<> handle) noexcept { // set task before it is attached task = handle; e.attach(this); } [[nodiscard]] schedule_status await_resume() noexcept { return schedule_status{status}; } }; // ADL customization point, may be overloaded for your executor type, should return awaitable which // schedules execution of coroutine to 'e' template <executor E> KELCORO_CO_AWAIT_REQUIRED constexpr auto jump_on(E&& e KELCORO_LIFETIMEBOUND) noexcept { return create_node_and_attach<E>(e); } struct noop_executor_t { static void attach(task_node*) noexcept { } }; constexpr inline noop_executor_t noop_executor; struct this_thread_executor_t { static void attach(task_node* node) { node->task.resume(); } }; constexpr inline this_thread_executor_t this_thread_executor; struct new_thread_executor_t { static void attach(task_node* node) { std::thread([node]() mutable { node->task.resume(); }).detach(); } }; constexpr inline new_thread_executor_t new_thread_executor; } // namespace dd namespace dd::noexport { enum struct retkind_e : uint8_t { EMPTY, VAL, EX }; template <typename T> struct retblock_storage { alignas(std::max(alignof(T), alignof(std::exception_ptr))) char data[std::max(sizeof(T), sizeof(std::exception_ptr))]; retblock_storage() = default; retblock_storage(retblock_storage&&) = delete; void operator=(retblock_storage&&) = delete; T* as_value() noexcept { return (T*)+data; } std::exception_ptr* as_ex() noexcept { return (std::exception_ptr*)+data; } const T* as_value() const noexcept { return (const T*)+data; } const std::exception_ptr* as_ex() const noexcept { return (const std::exception_ptr*)+data; } }; } // namespace dd::noexport namespace dd { constexpr std::size_t hardware_constructive_interference_size = 64; constexpr std::size_t hardware_destructive_interference_size = 64; struct not_movable { constexpr not_movable() noexcept = default; not_movable(not_movable&&) = delete; void operator=(not_movable&&) = delete; }; struct rvo_tag_t { // gcc 12 workaround for co_return {} struct do_not_break_construction { explicit do_not_break_construction() = default; }; explicit constexpr rvo_tag_t(do_not_break_construction) noexcept {}; }; // Optimization for returning objects in dd::task // // Example of potential memory duplication for BigT: // There are two BigT: // * in coroutine frame (local variable) // * in coroutine promise for co_return // // dd::task<BigT> foo() { // BigT t; // fill(&t); // co_return t; // } // // Optimized version (only one BigT in coroutine promise): // // dd::task<BigT> foo() { // BigT& t = co_await dd::this_coro::return_place; // fill(&t); // co_return dd::rvo; // } // constexpr inline const rvo_tag_t rvo = rvo_tag_t{rvo_tag_t::do_not_break_construction{}}; // 'teaches' promise to return // Note - promise must implement exception logic itself template <typename T> struct return_block { private: noexport::retblock_storage<T> data; noexport::retkind_e kind = noexport::retkind_e::EMPTY; public: return_block() = default; ~return_block() { switch (kind) { case noexport::retkind_e::VAL: std::destroy_at(data.as_value()); break; case noexport::retkind_e::EX: std::destroy_at(data.as_ex()); break; case noexport::retkind_e::EMPTY: break; } } bool has_exception() const noexcept { return kind == noexport::retkind_e::EX; } void set_exception(std::exception_ptr&& e) noexcept { assert(kind != noexport::retkind_e::EX); assert(e != nullptr); if (kind == noexport::retkind_e::VAL) std::destroy_at(data.as_value()); std::construct_at(data.as_ex(), std::move(e)); kind = noexport::retkind_e::EX; } // precondition: has_exception() std::exception_ptr take_exception() noexcept { assert(has_exception()); std::exception_ptr p = std::move(*data.as_ex()); std::destroy_at(data.as_ex()); kind = noexport::retkind_e::EMPTY; return p; } void unhandled_exception() = delete; // force promise implement it template <typename U = T> constexpr void return_value(U&& value) noexcept(std::is_nothrow_constructible_v<T, U&&>) { assert(kind == noexport::retkind_e::EMPTY); std::construct_at(data.as_value(), std::forward<U>(value)); kind = noexport::retkind_e::VAL; } constexpr void return_value(rvo_tag_t) noexcept { assert(kind != noexport::retkind_e::EMPTY); } constexpr T&& result() noexcept KELCORO_LIFETIMEBOUND { assert(kind == noexport::retkind_e::VAL); return std::move(*data.as_value()); } // args for case when T is not default contructible // must be used with co_return dd::rvo template <typename... Args> constexpr T& return_place(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>) { assert(kind == noexport::retkind_e::EMPTY); T* p = std::construct_at(data.as_value(), std::forward<Args>(args)...); kind = noexport::retkind_e::VAL; return *p; } }; template <typename T> struct return_block<T&> { private: T* storage = nullptr; std::exception_ptr ex; public: return_block() = default; ~return_block() = default; bool has_exception() const noexcept { return ex != nullptr; } void set_exception(std::exception_ptr&& e) noexcept { ex = std::move(e); } // precondition: has_exception() std::exception_ptr take_exception() noexcept { assert(has_exception()); // same preconditon as in other specializations return std::move(ex); } void unhandled_exception() = delete; // force promise implement it constexpr void return_value(T& value) noexcept { assert(storage == nullptr); storage = std::addressof(value); } constexpr void return_value(rvo_tag_t) noexcept { assert(storage != nullptr); } constexpr T& result() noexcept { assert(storage != nullptr); return *storage; } constexpr T*& return_place(T* p = nullptr) { return storage = p; } }; template <> struct return_block<void> { private: std::exception_ptr ex; public: return_block() = default; ~return_block() = default; bool has_exception() const noexcept { return ex != nullptr; } void set_exception(std::exception_ptr&& e) noexcept { ex = std::move(e); } // precondition: has_exception() std::exception_ptr take_exception() noexcept { assert(has_exception()); // same preconditon as in other specializations return std::move(ex); } void unhandled_exception() = delete; // force promise implement it constexpr void return_void() const noexcept { } static void result() noexcept { } static void return_place() noexcept { } }; // if transfers to nullptr, then behaves as suspend_never struct [[nodiscard("co_await it!")]] transfer_control_to { std::coroutine_handle<> who_waits; bool await_ready() const noexcept { return !who_waits; } KELCORO_ASSUME_NOONE_SEES std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept { return who_waits; // symmetric transfer here } static constexpr void await_resume() noexcept { } }; template <std::invocable<> F> struct [[nodiscard("Dont forget to name it!")]] scope_exit { KELCORO_NO_UNIQUE_ADDRESS F todo; scope_exit(F todo) : todo(std::move(todo)) { } constexpr ~scope_exit() noexcept(std::is_nothrow_invocable_v<F&>) { todo(); } }; // destroys coroutine in which awaited for struct KELCORO_CO_AWAIT_REQUIRED destroy_coro_t { static bool await_ready() noexcept { return false; } static void await_suspend(std::coroutine_handle<> handle) noexcept { handle.destroy(); } static void await_resume() noexcept { KELCORO_UNREACHABLE; } }; template <typename F> struct KELCORO_CO_AWAIT_REQUIRED suspend_and_t { KELCORO_NO_UNIQUE_ADDRESS F fn; constexpr static bool await_ready() noexcept { return false; } template <typename P> constexpr auto await_suspend(std::coroutine_handle<P> handle) noexcept { return fn(handle); } constexpr static void await_resume() noexcept { } }; template <typename F> suspend_and_t(F&&) -> suspend_and_t<std::remove_cvref_t<F>>; namespace this_coro { struct KELCORO_CO_AWAIT_REQUIRED get_handle_t { explicit get_handle_t() = default; template <typename PromiseType> struct awaiter { std::coroutine_handle<PromiseType> handle_; static constexpr bool await_ready() noexcept { return false; } KELCORO_ASSUME_NOONE_SEES bool await_suspend(std::coroutine_handle<PromiseType> handle) noexcept { handle_ = handle; return false; } [[nodiscard]] std::coroutine_handle<PromiseType> await_resume() const noexcept { return handle_; } }; awaiter<void> operator co_await() const noexcept { return awaiter<void>{}; } }; struct get_context_t { explicit get_context_t() = default; template <typename Ctx> struct awaiter { Ctx* ctx; static bool await_ready() noexcept { return false; } template <typename T> bool await_suspend(std::coroutine_handle<T> h) { ctx = std::addressof(h.promise().ctx); return false; } [[nodiscard]] Ctx& await_resume() const noexcept { return *ctx; } }; }; // provides access to inner handle of coroutine constexpr inline get_handle_t handle = get_handle_t{}; constexpr inline destroy_coro_t destroy = destroy_coro_t{}; // co_awaiting on this function suspends coroutine and invokes 'fn' with coroutine handle. // await suspend returns what 'fn' returns! constexpr auto suspend_and(auto&& fn) { return suspend_and_t(std::forward<decltype(fn)>(fn)); } struct [[nodiscard("co_await it!")]] destroy_and_transfer_control_to { std::coroutine_handle<> who_waits; #if !KELCORO_AGGREGATE_PAREN_INIT destroy_and_transfer_control_to() = default; explicit destroy_and_transfer_control_to(std::coroutine_handle<> h) noexcept : who_waits(h) { } #endif static bool await_ready() noexcept { return false; } // ASAN produces false positive here (understandable) #if defined(__has_feature) #if __has_feature(address_sanitizer) [[gnu::no_sanitize_address]] #else KELCORO_ASSUME_NOONE_SEES #endif #else KELCORO_ASSUME_NOONE_SEES #endif std::coroutine_handle<> await_suspend(std::coroutine_handle<> self) noexcept { // move it to stack memory to save from destruction auto w = who_waits; self.destroy(); return w ? w : std::noop_coroutine(); // symmetric transfer here } [[noreturn]] static void await_resume() noexcept { KELCORO_UNREACHABLE; } }; } // namespace this_coro template <typename T> concept has_member_co_await = requires(T (*value)()) { value().operator co_await(); }; template <typename T> concept has_global_co_await = requires(T (*value)()) { operator co_await(value()); }; template <typename T> concept ambigious_co_await_lookup = has_global_co_await<T> && has_member_co_await<T>; template <typename T> concept co_awaiter = requires(T value) { { value.await_ready() } -> std::same_as<bool>; // cant check await_suspend here because: // case: value.await_suspend(coroutine_handle<>{}) - may be non convertible to concrete // T in signature of await_suspend case: value.await_suspend(nullptr) - may be template // signature, compilation error(cant deduct type) another case - signature with // requires, impossible to know how to call it value.await_resume(); }; template <typename T> concept co_awaitable = has_member_co_await<T> || has_global_co_await<T> || co_awaiter<T>; // imitating compiler behaviour for co_await expression mutation into awaiter(without await_transform) template <co_awaitable T> [[nodiscard]] constexpr decltype(auto) build_awaiter(T&& value) { static_assert(!ambigious_co_await_lookup<T>); if constexpr (co_awaiter<T&&>) // first bcs can have operator co_await too return std::forward<T>(value); else if constexpr (has_global_co_await<T&&>) return operator co_await(std::forward<T>(value)); else if constexpr (has_member_co_await<T&&>) return std::forward<T>(value).operator co_await(); } namespace noexport { template <typename T> consteval auto do_await_result() { static_assert(!ambigious_co_await_lookup<T>); if constexpr (has_global_co_await<T>) { return std::type_identity< decltype(std::declval<decltype(operator co_await(std::declval<T>()))>().await_resume())>{}; } else if constexpr (has_member_co_await<T>) { return std::type_identity< decltype(std::declval<decltype(std::declval<T>().operator co_await())>().await_resume())>{}; } else { // co_awaiter return std::type_identity<decltype(std::declval<decltype(std::declval<T>())>().await_resume())>{}; } } } // namespace noexport // Note: ignores await_transform! template <co_awaitable T> using await_result_t = typename decltype(noexport::do_await_result<T>())::type; } // namespace dd #include <concepts> #include <array> #include <bit> #include <memory_resource> #include <cassert> #include <utility> #include <coroutine> #include <memory> namespace dd { // operation hash helps identify same logical operation // from different tasks, its used to improve thread pool performance using operation_hash_t = size_t; namespace noexport { // copy from one of stdlibcs, because hash for void* on linux gcc/clang really bad #if UINTPTR_MAX > UINT_LEAST32_MAX constexpr inline size_t fnv_offset_basis = 14695981039346656037ULL; constexpr inline size_t fnv_prime = 1099511628211ULL; #else // 32 bit or smth like constexpr inline size_t fnv_offset_basis = 2166136261U; constexpr inline size_t fnv_prime = 16777619U; #endif static size_t do_hash(const void* ptr) noexcept { size_t val = fnv_offset_basis; const unsigned char* bytes = reinterpret_cast<const unsigned char*>(&ptr); for (int i = 0; i < sizeof(void*); ++i) { val ^= static_cast<size_t>(bytes[i]); val *= fnv_prime; } return val; } struct KELCORO_CO_AWAIT_REQUIRED op_hash_t { operation_hash_t hash; static constexpr bool await_ready() noexcept { return false; } template <typename P> constexpr bool await_suspend(std::coroutine_handle<P> handle) noexcept { hash = calculate_operation_hash(handle); return false; } constexpr operation_hash_t await_resume() noexcept { return hash; } }; } // namespace noexport // precondition: operation can be executed // e.g. it is not empty coroutine handle or empty std::function template <typename T> struct operation_hash { static_assert(std::is_same_v<T, std::decay_t<T>>); // default version operation_hash_t operator()(const T& op) const noexcept { return noexport::do_hash(std::addressof(op)); } }; template <typename P> struct operation_hash<std::coroutine_handle<P>> { operation_hash_t operator()(std::coroutine_handle<P> handle) const noexcept { // heuristic #1 - the same coroutine is executed on the same thread // and same coroutine produces max 1 task at one time return noexport::do_hash(handle.address()); } }; template <typename O> [[gnu::pure]] constexpr operation_hash_t calculate_operation_hash(const O& operation) noexcept { return operation_hash<std::decay_t<O>>()(operation); } namespace this_coro { constexpr inline noexport::op_hash_t operation_hash = {}; } } // namespace dd namespace dd { consteval size_t coroframe_align() { // Question: what will be if coroutine local contains alignas(64) int i; ? // Answer: (quote from std::generator paper) // "Let BAlloc be allocator_traits<A>::template rebind_alloc<U> where U denotes an unspecified type // whose size and alignment are both _STDCPP_DEFAULT_NEW_ALIGNMENT__" return __STDCPP_DEFAULT_NEW_ALIGNMENT__; } template <typename T> concept memory_resource = !std::is_reference_v<T> && requires(T value, size_t sz, void* ptr) { // align of result must be atleast aligned as dd::coroframe_align() // align arg do not required, because standard do not provide interface // for passing alignment to promise_type::new { value.allocate(sz) } -> std::convertible_to<void*>; // if type is trivially destructible it must handle case when `value` lays in memory under `ptr` { value.deallocate(ptr, sz) } -> std::same_as<void>; requires std::is_nothrow_move_constructible_v<T>; requires alignof(T) <= alignof(std::max_align_t); requires !(std::is_empty_v<T> && !std::default_initializable<T>); }; // typical usage: // using with_my_resource = dd::with_resource<chunk_from<MyResource>>; // coro foo(int, double, with_my_resource); // ... // foo(5, 3.14, my_resource); template <typename R> struct chunk_from { private: // bytes used to guarantee aligment == 1, its removes calculations of aligment in operator new/delete KELCORO_NO_UNIQUE_ADDRESS std::conditional_t<std::is_empty_v<R>, R, std::array<char, sizeof(R*)>> _resource; public: R& resource() noexcept { if constexpr (std::is_empty_v<R>) return _resource; else return *std::bit_cast<R*>(_resource); } chunk_from() requires(std::is_empty_v<R>) = default; // implicit chunk_from(R& r) noexcept { if constexpr (!std::is_empty_v<R>) _resource = std::bit_cast<decltype(_resource)>(std::addressof(r)); } [[nodiscard]] void* allocate(size_t sz) { return std::assume_aligned<dd::coroframe_align()>(resource().allocate(sz)); } void deallocate(void* p, std::size_t sz) noexcept { resource().deallocate(p, sz); } }; // default resource for with_memory_resource struct new_delete_resource { static void* allocate(size_t sz) { // not malloc because of memory alignment requirement return new char[sz]; } static void deallocate(void* p, std::size_t) noexcept { delete[] static_cast<char*>(p); } }; // when passed into coroutine as last argument coro will allocate/deallocate memory using this resource // example: // generator<int> gen(int, float, with_resource<MyResource>); template <memory_resource R> struct with_resource { KELCORO_NO_UNIQUE_ADDRESS R resource; // implicit template <typename... Args> with_resource(Args&&... args) noexcept(std::is_nothrow_constructible_v<R, Args&&...>) : resource(std::forward<Args>(args)...) { } // do not overlap with first ctor with_resource(with_resource&&) = default; with_resource(const with_resource&) = default; with_resource(const with_resource&& o) : with_resource(o) { } with_resource(with_resource& o) : with_resource(std::as_const(o)) { } }; template <typename X> with_resource(X&&) -> with_resource<std::remove_cvref_t<X>>; with_resource(std::pmr::memory_resource&) -> with_resource<chunk_from<std::pmr::memory_resource>>; namespace pmr { struct polymorphic_resource { private: std::pmr::memory_resource* passed; public: polymorphic_resource() noexcept : passed(std::pmr::get_default_resource()) { } polymorphic_resource(std::pmr::memory_resource& m) noexcept : passed(&m) { } void* allocate(size_t sz) { return passed->allocate(sz, coroframe_align()); } void deallocate(void* p, std::size_t sz) noexcept { passed->deallocate(p, sz, coroframe_align()); } }; } // namespace pmr using with_default_resource = with_resource<new_delete_resource>; using with_pmr_resource = with_resource<chunk_from<std::pmr::memory_resource>>; namespace noexport { template <typename T> struct type_identity_special { using type = T; template <typename U> type_identity_special<U> operator=(type_identity_special<U>); }; // TODO optimize when pack indexing will be possible // void when Args is empty template <typename... Args> using last_type_t = typename std::remove_cvref_t<decltype((type_identity_special<void>{} = ... = type_identity_special<Args>{}))>::type; template <typename T> struct memory_resource_info : std::false_type { using resource_type = void; }; template <typename R> struct memory_resource_info<with_resource<R>> : std::true_type { using resource_type = R; }; } // namespace noexport template <typename... Args> using get_memory_resource_info = noexport::memory_resource_info<std::remove_cvref_t<noexport::last_type_t<Args...>>>; template <typename... Args> constexpr inline bool last_is_memory_resource_tag = get_memory_resource_info<Args...>::value; template <typename... Args> using resource_type_t = typename get_memory_resource_info<Args...>::resource_type; namespace noexport { template <size_t RequiredPadding> constexpr size_t padding_len(size_t sz) noexcept { enum { P = RequiredPadding }; if constexpr (P == 1) return 0; // for constant folding earlier else { static_assert(P != 0); return (P - sz % P) % P; } } } // namespace noexport // inheritor(coroutine promise) may be allocated with 'R' // using 'with_resource' tag or default constructed 'R' template <memory_resource R> struct overload_new_delete { private: static void* do_allocate(size_t frame_sz, R& r) { if constexpr (std::is_empty_v<R>) return (void*)r.allocate(frame_sz); else { frame_sz += noexport::padding_len<alignof(R)>(frame_sz); std::byte* p = (std::byte*)r.allocate(frame_sz + sizeof(R)); new (p + frame_sz) R(std::move(r)); return p; } } public: static void* operator new(size_t frame_sz) requires(std::default_initializable<R>) { R r{}; return do_allocate(frame_sz, r); } template <typename... Args> requires(last_is_memory_resource_tag<Args...> && std::is_same_v<R, resource_type_t<Args...>>) static void* operator new(std::size_t frame_sz, Args&&... args) { static_assert(std::is_same_v<std::remove_cvref_t<noexport::last_type_t<Args...>>, with_resource<R>>); // old-style // return do_allocate(frame_sz, (args...[sizeof...(Args) - 1]).resource); auto voidify = [](auto& x) { return const_cast<void*>((const void volatile*)std::addressof(x)); }; void* p = (voidify(args), ...); return do_allocate(frame_sz, static_cast<with_resource<R>*>(p)->resource); } static void operator delete(void* ptr, std::size_t frame_sz) noexcept { if constexpr (std::is_empty_v<R>) { R r{}; r.deallocate(ptr, frame_sz); } else { frame_sz += noexport::padding_len<alignof(R)>(frame_sz); R* onframe_resource = (R*)((std::byte*)ptr + frame_sz); assert((((uintptr_t)onframe_resource % alignof(R)) == 0)); if constexpr (std::is_trivially_destructible_v<R>) { // `deallocate` must not touch memory after deallocating (semantic requirement on concept )) onframe_resource->deallocate(ptr, frame_sz + sizeof(R)); } else { // save to stack from deallocated memory R r = std::move(*onframe_resource); std::destroy_at(onframe_resource); r.deallocate(ptr, frame_sz + sizeof(R)); } } } }; struct enable_resource_deduction {}; // creates type of coroutine which may be allocated with resource 'R' // disables resource deduction // typical usage: aliases like generator_r/channel_r/etc // for not duplicating code and not changing signature with default constructible resources // see dd::generator_r as example template <typename Coro, memory_resource R> struct KELCORO_ELIDE_CTX KELCORO_MSVC_EBO resourced : Coro { using resource_type = R; using Coro::Coro; using Coro::operator=; constexpr resourced(auto&&... args) requires(std::constructible_from<Coro, decltype(args)...>) : Coro(std::forward<decltype(args)>(args)...) { } constexpr Coro& decay() & noexcept { return *this; } constexpr Coro&& decay() && noexcept { return std::move(*this); } constexpr const Coro& decay() const& noexcept { return *this; } constexpr const Coro&& decay() const&& noexcept { return std::move(*this); } }; template <typename Promise, memory_resource R> struct KELCORO_MSVC_EBO resourced_promise : Promise, overload_new_delete<R> { using Promise::Promise; using Promise::operator=; constexpr resourced_promise(auto&&... args) requires(std::constructible_from<Promise, decltype(args)...>) : Promise(std::forward<decltype(args)>(args)...) { } using overload_new_delete<R>::operator new; using overload_new_delete<R>::operator delete; // assume sizeof and alignof of *this is equal with 'Promise' // its formal UB, but its used in reference implementation, // standard wording goes wrong }; template <typename Promise, memory_resource R> struct operation_hash<std::coroutine_handle<resourced_promise<Promise, R>>> { size_t operator()(std::coroutine_handle<resourced_promise<Promise, R>> h) const { return operation_hash<std::coroutine_handle<Promise>>()( // assume addresses are same (dirty hack for supporting allocators) std::coroutine_handle<Promise>::from_address(h.address())); } }; } // namespace dd namespace std { template <typename Coro, ::dd::memory_resource R, typename... Args> struct coroutine_traits<::dd::resourced<Coro, R>, Args...> { using promise_type = ::dd::resourced_promise<typename Coro::promise_type, R>; }; // enable_resource_deduction always uses last argument if present (memory_resource<R>) template <typename Coro, typename... Args> requires(derived_from<Coro, ::dd::enable_resource_deduction> && dd::last_is_memory_resource_tag<Args...> && !std::is_same_v<dd::noexport::last_type_t<Args...>, dd::with_default_resource>) struct coroutine_traits<Coro, Args...> { using promise_type = ::dd::resourced_promise<typename Coro::promise_type, ::dd::resource_type_t<Args...>>; }; } // namespace std namespace dd { // concept of type which can be returned from function or yielded from generator // that is - not function, not array, not cv-qualified (its has no ) // additionally reference is not yieldable(std::ref exists...) template <typename T> concept yieldable = std::same_as<std::decay_t<T>, T> || std::is_lvalue_reference_v<T>; // just helper to disambigue two yield_value overloads in case when Yield is reference template <typename T> concept choose_me_if_ambiguous = requires(T& x) { x; }; template <typename R> struct elements_of { KELCORO_NO_UNIQUE_ADDRESS R rng; #if !KELCORO_AGGREGATE_PAREN_INIT // may be clang will never support aggregate () initialization... constexpr elements_of(std::type_identity_t<R> rng) noexcept : rng(static_cast<R&&>(rng)) { } #endif }; template <typename R> elements_of(R&&) -> elements_of<R&&>; // tag for yielding from generator/channel by reference. // This means, if 'value' will be changed by caller it will be // observable from coroutine // example: // int i = 0; // co_yield ref{i}; // -- at this point 'i' may be != 0, if caller changed it template <typename Yield> struct by_ref { Yield& value; }; template <typename Yield> by_ref(Yield&) -> by_ref<Yield&>; template <yieldable> struct generator_promise; template <yieldable> struct channel_promise; template <yieldable> struct generator_iterator; template <yieldable> struct channel_iterator; template <yieldable> struct generator; template <yieldable> struct channel; } // namespace dd namespace dd::noexport { template <typename Leaf> struct attach_leaf { Leaf leaf; bool await_ready() const noexcept { return leaf.empty(); } KELCORO_ASSUME_NOONE_SEES std::coroutine_handle<> await_suspend( typename Leaf::handle_type owner) const noexcept { assert(owner != leaf.top); auto& leaf_p = leaf.top.promise(); auto& root_p = *owner.promise().root; leaf_p.current_worker.promise().root = &root_p; leaf_p._owner = owner; root_p.current_worker = leaf_p.current_worker; return leaf_p.current_worker; } // support yielding generators with different resource template <memory_resource R> KELCORO_ASSUME_NOONE_SEES auto await_suspend( std::coroutine_handle<resourced_promise<typename Leaf::promise_type, R>> handle) { return await_suspend(handle.promise().self_handle()); } static constexpr void await_resume() noexcept { } }; template <yieldable Y, typename Generator> Generator to_generator(auto&& rng) { // 'rng' captured by ref because used as part of 'co_yield' expression if constexpr (!std::ranges::borrowed_range<decltype(rng)> && std::is_same_v<std::ranges::range_rvalue_reference_t<decltype(rng)>, Y&&>) { using std::begin; using std::end; auto&& b = begin(rng); auto&& e = end(rng); for (; b != e; ++b) co_yield std::ranges::iter_move(b); } else { for (auto&& x : rng) co_yield static_cast<Y&&>(std::forward<decltype(x)>(x)); } } template <typename, yieldable, template <typename> typename> struct extract_from; template <typename Rng, yieldable Y> struct extract_from<Rng, Y, generator> { static generator<Y> do_(generator<Y>* g) { return std::move(*g); } template <memory_resource R> static generator<Y> do_(resourced<generator<Y>, R>* g) { return do_(&g->decay()); } static generator<Y> do_(auto* r) { return to_generator<Y, generator<Y>>(static_cast<Rng&&>(*r)); } }; template <typename Rng, yieldable Y> struct extract_from<Rng, Y, channel> { static channel<Y> do_(channel<Y>* g) { return std::move(*g); } template <memory_resource R> static channel<Y> do_(resourced<channel<Y>, R>* g) { return do_(&g->decay()); } template <yieldable OtherY> static channel<Y> do_(channel<OtherY>* g) { auto& c = *g; // note: (void)(co_await) (++b)) only because gcc has bug, its not required for (auto b = co_await c.begin(); b != c.end(); (void)(co_await (++b))) co_yield static_cast<Y&&>(*b); } template <yieldable OtherY, memory_resource R> static channel<Y> do_(resourced<channel<OtherY>, R>* g) { return do_(&g->decay()); } static channel<Y> do_(auto* r) { return to_generator<Y, channel<Y>>(static_cast<Rng&&>(*r)); } }; template <yieldable Y, template <typename> typename G, typename Rng> attach_leaf<G<Y>> create_and_attach_leaf(elements_of<Rng>&& e) { return attach_leaf<G<Y>>{extract_from<Rng, Y, G>::do_(std::addressof(e.rng))}; } template <typename Yield> struct hold_value_until_resume { Yield value; static constexpr bool await_ready() noexcept { return false; } KELCORO_ASSUME_NOONE_SEES void await_suspend( std::coroutine_handle<generator_promise<Yield>> handle) noexcept { handle.promise().set_result(std::addressof(value)); } KELCORO_ASSUME_NOONE_SEES std::coroutine_handle<> await_suspend( std::coroutine_handle<channel_promise<Yield>> handle) noexcept { handle.promise().set_result(std::addressof(value)); return handle.promise().consumer_handle(); } template <memory_resource R> KELCORO_ASSUME_NOONE_SEES void await_suspend( std::coroutine_handle<resourced_promise<generator_promise<Yield>, R>> handle) noexcept { // decay handle return await_suspend(handle.promise().self_handle()); } template <memory_resource R> KELCORO_ASSUME_NOONE_SEES std::coroutine_handle<> await_suspend( std::coroutine_handle<resourced_promise<channel_promise<Yield>, R>> handle) noexcept { // decay handle return await_suspend(handle.promise().self_handle()); } static constexpr void await_resume() noexcept { } }; } // namespace dd::noexport #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-attributes" #endif #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" #endif namespace dd { // generator with this type is optimized struct nothing_t { nothing_t() = default; constexpr nothing_t(std::nullptr_t) noexcept { } }; template <typename Yield> using pointer_to_yielded_t = std::conditional_t<std::is_same_v<nothing_t, Yield>, nothing_t, std::add_pointer_t<Yield>>; template <typename CRTP, yieldable Yield> struct yield_block { std::suspend_always yield_value(Yield&& rvalue) noexcept requires(!std::is_reference_v<Yield> && choose_me_if_ambiguous<Yield>) { static_cast<CRTP&>(*this).set_result(std::addressof(rvalue)); return {}; } std::suspend_always yield_value(Yield& lvalue) noexcept requires(std::is_reference_v<Yield>) { return yield_value(by_ref{lvalue}); } noexport::hold_value_until_resume<Yield> yield_value(const Yield& clvalue) noexcept( std::is_nothrow_copy_constructible_v<Yield>) requires(!std::is_reference_v<Yield>) { return noexport::hold_value_until_resume<Yield>{Yield(clvalue)}; } template <typename U> std::suspend_always yield_value(by_ref<U> r) noexcept { static_cast<CRTP&>(*this).set_result(std::addressof(r.value)); return {}; } }; template <typename CRTP> struct yield_block<CRTP, nothing_t> { static std::suspend_always yield_value(nothing_t = {}) { return {}; } }; template <yieldable Yield> struct generator_promise : not_movable, yield_block<generator_promise<Yield>, Yield> { using handle_type = std::coroutine_handle<generator_promise>; // invariant: root != nullptr generator_promise* root = this; handle_type current_worker = self_handle(); union { generator<Yield>* _consumer; // setted only in root handle_type _owner; // setted only in leafs }; handle_type owner() const noexcept { KELCORO_ASSUME(root != this); return _owner; } void set_result(pointer_to_yielded_t<Yield> p) const noexcept { if constexpr (!std::is_same_v<nothing_t, Yield>) root->_consumer->current_result = p; } KELCORO_PURE handle_type self_handle() noexcept { return handle_type::from_promise(*this); } void skip_this_leaf() const noexcept { KELCORO_ASSUME(root != this); generator_promise& owner_p = _owner.promise(); KELCORO_ASSUME(&owner_p != this); owner_p.root = root; root->current_worker = _owner; } public: constexpr generator_promise() noexcept { } generator<Yield> get_return_object() noexcept { return generator<Yield>(self_handle()); } KELCORO_DEFAULT_AWAIT_TRANSFORM; auto await_transform(this_coro::get_handle_t) noexcept { return this_coro::get_handle_t::awaiter<generator_promise>{}; } using yield_block<generator_promise, Yield>::yield_value; template <typename R> noexport::attach_leaf<generator<Yield>> yield_value(elements_of<R> e) noexcept { return noexport::create_and_attach_leaf<Yield, generator>(std::move(e)); } static constexpr std::suspend_always initial_suspend() noexcept { return {}; } struct final_awaiter { const generator_promise& p; static constexpr bool await_ready() noexcept { return false; } static constexpr void await_resume() noexcept { } KELCORO_ASSUME_NOONE_SEES constexpr std::coroutine_handle<> await_suspend( std::coroutine_handle<>) const noexcept { if (p.root != &p) { p.skip_this_leaf(); return p.owner(); } p.set_result(nullptr); return std::noop_coroutine(); } }; final_awaiter final_suspend() const noexcept { return final_awaiter{*this}; } static constexpr void return_void() noexcept { } [[noreturn]] void unhandled_exception() { if (root != this) skip_this_leaf(); set_result(nullptr); throw; } }; // no default ctor, because its input iterator template <yieldable Yield> struct generator_iterator { private: // invariant: != nullptr, ptr for trivial copy/move generator<Yield>* self; public: // do not resumes 'g' constexpr explicit generator_iterator(generator<Yield>& g KELCORO_LIFETIMEBOUND) noexcept : self(std::addressof(g)) { } using iterator_category = std::input_iterator_tag; using value_type = std::decay_t<Yield>; using reference = std::conditional_t<std::is_same_v<nothing_t, Yield>, Yield, Yield&&>; using difference_type = ptrdiff_t; // return true if they are attached to same 'generator' object constexpr bool equivalent(const generator_iterator& other) const noexcept { return self == other.self; } generator<Yield>& owner() const noexcept { return *self; } constexpr bool operator==(std::default_sentinel_t) const noexcept { if constexpr (!std::is_same_v<nothing_t, Yield>) return self->current_result == nullptr; else return self->top.done(); } constexpr reference operator*() const noexcept { KELCORO_ASSUME(*this != std::default_sentinel); if constexpr (!std::is_same_v<nothing_t, Yield>) return static_cast<reference>(*self->current_result); else return reference{}; } constexpr std::add_pointer_t<reference> operator->() const noexcept requires(!std::is_same_v<nothing_t, Yield>) { auto&& ref = operator*(); return std::addressof(ref); } // * after invoking references to value from operator* are invalidated generator_iterator& operator++() KELCORO_LIFETIMEBOUND { KELCORO_ASSUME(!self->empty()); const auto* const self_before = self; const auto* const top_address_before = self->top.address(); self->top.promise().current_worker.resume(); KELCORO_ASSUME(self_before == self); const auto* const top_address_after = self->top.address(); KELCORO_ASSUME(top_address_before == top_address_after); return *this; } void operator++(int) { ++(*this); } // converts to iterator which can be used as output iterator auto out() const&& noexcept; }; template <yieldable Yield> struct generator_output_iterator : generator_iterator<Yield> { using base_t = generator_iterator<Yield>; constexpr Yield& operator*() const noexcept { static_assert(!std::is_same_v<nothing_t, Yield>); static_assert(std::is_reference_v<decltype(base_t::operator*())>); Yield&& i = base_t::operator*(); // avoid C++23 automove in return expression Yield& j = i; return j; } constexpr generator_output_iterator& operator++() { base_t::operator++(); return *this; } constexpr generator_output_iterator& operator++(int) { base_t::operator++(); return *this; } }; template <yieldable Yield> auto generator_iterator<Yield>::out() const&& noexcept { return generator_output_iterator<Yield>{*this}; } // * produces first value when .begin called // * recursive (co_yield dd::elements_of(rng)) // * default constructed generator is an empty range // notes: // * generator ignores fact, that 'destroy' may throw exception from destructor of object in coroutine, it // will lead to std::terminate // * if exception was thrown from recursivelly co_yielded generator, then this leaf just skipped and caller // can continue iterating after catch(requires new .begin call) // * its caller responsibility to not use co_await with real suspend in generator // (or you must know what you do) template <yieldable Yield> struct KELCORO_ELIDE_CTX generator : enable_resource_deduction { using promise_type = generator_promise<Yield>; using handle_type = std::coroutine_handle<promise_type>; using value_type = std::decay_t<Yield>; using iterator = generator_iterator<Yield>; private: friend generator_iterator<Yield>; friend generator_promise<Yield>; friend noexport::attach_leaf<generator>; // invariant: == nullptr when top.done() KELCORO_NO_UNIQUE_ADDRESS pointer_to_yielded_t<Yield> current_result = nullptr; handle_type top = nullptr; // precondition: 'handle' != nullptr, handle does not have other owners // used from promise::get_return_object constexpr explicit generator(handle_type top) noexcept : top(top) { prepare_to_start(); } void prepare_to_start() noexcept { assert(raw_handle() != nullptr); if constexpr (!std::is_same_v<nothing_t, Yield>) top.promise()._consumer = this; } public: // postcondition: empty(), 'for' loop produces 0 values constexpr generator() noexcept = default; constexpr generator(generator&& other) noexcept { swap(other); } constexpr generator& operator=(generator&& other) noexcept { swap(other); return *this; } // iterators to 'other' and 'this' are swapped too constexpr void swap(generator& other) noexcept { std::swap(current_result, other.current_result); std::swap(top, other.top); if (top) prepare_to_start(); if (other.top) other.prepare_to_start(); } friend constexpr void swap(generator& a, generator& b) noexcept { a.swap(b); } constexpr void reset(handle_type handle) noexcept { clear(); top = handle; } // postcondition: .empty() // its caller responsibility to correctly destroy handle [[nodiscard]] constexpr handle_type release() noexcept { return std::exchange(top, nullptr); } [[nodiscard]] constexpr handle_type raw_handle() const noexcept { return top; } // postcondition: .empty() constexpr void clear() noexcept { if (top) { top.destroy(); top = nullptr; } } constexpr ~generator() { clear(); } // observers KELCORO_PURE constexpr bool empty() const noexcept { return !top || top.done(); } constexpr explicit operator bool() const noexcept { return !empty(); } // * if .empty(), then begin() == end() // * produces next value(often first) // iterator invalidated only when generator dies iterator begin() KELCORO_LIFETIMEBOUND { iterator it(*this); if (!empty()) [[likely]] ++it; return it; } static constexpr std::default_sentinel_t end() noexcept { return std::default_sentinel; } // precondition: !raw_handle() != nullptr // if generator is not started, its 'before_begin' iterator, which may not be dereferenced iterator cur_iterator() noexcept { assert(raw_handle() != nullptr); return iterator(*this); } }; template <yieldable Y, memory_resource R> using generator_r = resourced<generator<Y>, R>; namespace pmr { template <yieldable Y> using generator = ::dd::generator_r<Y, polymorphic_resource>; } } // namespace dd #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif using wnr = dd::with_resource<dd::chunk_from<dd::new_delete_resource>>; dd::generator<int> gen(int x, wnr) { co_yield x; } void foo() { for (int x : gen(5, wnr{})) { ; } }
Become a Patron
Sponsor on GitHub
Donate via PayPal
Compiler Explorer Shop
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
CE on Bluesky
Statistics
Changelog
Version tree