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
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
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
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)
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
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.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)
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.latest
x86 djgpp 4.9.4
x86 djgpp 5.5.0
x86 djgpp 6.4.0
x86 djgpp 7.2.0
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.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.3
x86 nvc++ 25.5
x86 nvc++ 25.7
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 P2998)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
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 gcc (P2034 lambdas)
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.2.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.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 <algorithm> #include <coroutine> #include <iostream> #include <functional> #include <optional> #include <string_view> #include <tuple> #include <typeinfo> #include <type_traits> #include <utility> // #region General utilities, not related to std::execution // Preprocessing help until clang implements "structured binding can introduce a pack" //////////////////////////////////////////////////////////////////////////////// // _FOR_EACH // Inspired by "Recursive macros with C++20 __VA_OPT__", by David Mazières // https://www.scs.stanford.edu/~dm/blog/va-opt.html #define _EXPAND(...) __VA_ARGS__ #define _EXPAND_R(...) \ _EXPAND_R1(_EXPAND_R1(_EXPAND_R1(_EXPAND_R1(__VA_ARGS__)))) \ /**/ #define _EXPAND_R1(...) \ _EXPAND_R2(_EXPAND_R2(_EXPAND_R2(_EXPAND_R2(__VA_ARGS__)))) \ /**/ #define _EXPAND_R2(...) \ _EXPAND_R3(_EXPAND_R3(_EXPAND_R3(_EXPAND_R3(__VA_ARGS__)))) \ /**/ #define _EXPAND_R3(...) \ _EXPAND(_EXPAND(_EXPAND(_EXPAND(__VA_ARGS__)))) \ /**/ #define _PARENS () #define _FOR_EACH(_MACRO, ...) \ __VA_OPT__(_EXPAND_R(_FOR_EACH_HELPER(_MACRO, __VA_ARGS__))) \ /**/ #define _FOR_EACH_HELPER(_MACRO, _A1, ...) \ _MACRO(_A1) __VA_OPT__(_FOR_EACH_AGAIN _PARENS(_MACRO, __VA_ARGS__)) /**/ #define _FOR_EACH_AGAIN() _FOR_EACH_HELPER //////////////////////////////////////////////////////////////////////////////////////////////////// // General utilities template <class..., class... Ts> [[deprecated]] void print(Ts&&...) {} template <class T> using _declval = T&& (*)() noexcept; template <class T> using _identity_t = T; template <class T> using _add_const_t = const T; template <class T> using _add_lvalue_ref_t = T&; template <class T> using _add_rvalue_ref_t = T&&; template <template <class> class C> struct _mquote1 { template <class T> using _apply = C<T>; }; template <template <class> class... Cs> struct _mcompose1; template <> struct _mcompose1<> : _mquote1<_identity_t> { }; template <template <class> class C> struct _mcompose1<C> : _mquote1<C> { }; template <template <class> class C0, template <class> class C1> struct _mcompose1<C0, C1> { template <class T> using _apply = C0<C1<T>>; }; template <class Fn, class... Ts> using _mapply = Fn::template _apply<Ts...>; template <class T> extern _mquote1<_identity_t> _copy_cvref_v; template <class T> extern _mquote1<_add_const_t> _copy_cvref_v<T const>; template <class T> extern _mquote1<_add_lvalue_ref_t> _copy_cvref_v<T&>; template <class T> extern _mquote1<_add_rvalue_ref_t> _copy_cvref_v<T&&>; template <class T> extern _mcompose1<_add_lvalue_ref_t, _add_const_t> _copy_cvref_v<T const&>; template <class T> extern _mcompose1<_add_rvalue_ref_t, _add_const_t> _copy_cvref_v<T const&&>; template <class From, class To> using _copy_cvref_t = _mapply<decltype(_copy_cvref_v<From>), To>; template <size_t I> using _size_t = std::integral_constant<size_t, I>; template <class T, template <class...> class> inline constexpr bool _is_specialization_of_helper = false; template <class... Ts, template <class...> class C> inline constexpr bool _is_specialization_of_helper<C<Ts...>, C> = true; template <class T, template <class...> class C> concept _is_specialization_of = _is_specialization_of_helper<T, C>; template <template <class...> class C, class... Ts> concept _can_be_instantiated_with = requires { typename C<Ts...>; }; template <class A, class B> concept _decays_to = std::same_as<std::decay_t<A>, B>; template <auto> concept _is_constant = true; template <class... Ts> concept _decay_copyable = requires (Ts&&(*...ts)()) { (auto(ts()), ...); }; template <class... Ts> concept _nothrow_decay_copyable = requires (Ts&&(*...ts)()) { {(auto(ts()), ...)} noexcept; }; template <class Fn, class... Ts> concept _callable_with = requires (Fn&& fn, Ts&&... ts) { std::forward<Fn>(fn)(std::forward<Ts>(ts)...); }; template <class Fn, class... Ts> concept _nothrow_callable_with = requires (Fn&& fn, Ts&&... ts) { { std::forward<Fn>(fn)(std::forward<Ts>(ts)...) } noexcept; }; template <class Fn, class... Ts> concept _meta_callable_with = requires (Fn&& fn) { std::forward<Fn>(fn).template operator()<Ts...>(); }; template <class Fn, class... Ts> using _call_result_t = decltype(std::declval<Fn>()(std::declval<Ts>()...)); struct _none_such {}; struct _ignore { _ignore() = default; constexpr _ignore(auto&&, auto&&...) noexcept { } constexpr const _ignore& operator=(auto&&) const noexcept { return *this; } }; template <auto Constant> struct _constant_wrapper { using type = decltype(Constant); static constexpr auto value = Constant; }; // efficient std::conditional and std::enable_if template <bool Cond, class... Ts> using _if_helper = Ts...[!Cond]; template <bool Cond, class Then = void, class... Else> using _if = _if_helper<Cond, Then, Else...>; // _typelist: a list of types template <class... Ts> struct _typelist { template <template <class...> class C> using _apply = C<Ts...>; }; // _typeset: a set of unique types template <class T> struct _typeset_element { _typeset_element() = default; constexpr explicit _typeset_element(T) noexcept { } }; template <class...> struct _typeset : _typeset_element<_ignore> { template <class... Ts> constexpr size_t size(this _typeset<Ts...>) noexcept { return sizeof...(Ts); } template <class... Ts, class U> constexpr auto operator+(this _typeset<Ts...> set, _typeset_element<U>) noexcept { if constexpr (__is_base_of(_typeset_element<U>, _typeset<Ts...>)) { return set; } else { return _typeset<U, Ts...>(); } } template <class... Ts, template <class...> class C, class... Us> constexpr auto operator+(this _typeset<Ts...> set, C<Us...>) noexcept { return (set + ... + _typeset_element<Us>()); } template <class... Ts, _meta_callable_with<Ts...> Fn> constexpr decltype(auto) _apply(this _typeset<Ts...>, Fn fn) { return fn.template operator()<Ts...>(); } }; template <class T, class... Ts> struct _typeset<T, Ts...> : _typeset_element<T> , _typeset<Ts...> { }; template <class... Ts> using _mk_typeset = decltype((_typeset() +...+ _typeset_element<Ts>())); // A simple structural tuple type template <class T, size_t I> struct _tuple_element { T value; }; template <auto, class... Ts> struct _tupl; template <size_t... Is, std::index_sequence<Is...>* Indices, class... Ts> struct _tupl<Indices, Ts...> : _tuple_element<Ts, Is>... { template <template <class...> class Fn> using _apply = Fn<Ts...>; template <size_t I, class Self> requires (I < sizeof...(Ts)) constexpr decltype(auto) get(this Self&& self) noexcept { return (std::forward<Self>(self)._tuple_element<Ts...[I], I>::value); } template <class Self, _callable_with<_copy_cvref_t<Self, Ts>...> Fn> constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(_nothrow_callable_with<Fn, _copy_cvref_t<Self, Ts>...>) { return std::forward<Fn>(fn)(std::forward<Self>(self)._tuple_element<Ts, Is>::value...); } }; template <class... Ts> _tupl(Ts...) -> _tupl<(std::index_sequence_for<Ts...>*) nullptr, Ts...>; template <class... Ts> using _tuple = _tupl<(std::index_sequence_for<Ts...>*) nullptr, Ts...>; // Generic utilities for tuple-like types constexpr size_t _max_structured_tuple_size = 16; namespace _apply_cpo { template <class Fn> struct _std_apply_probe { template <class... Ts> constexpr auto operator()(Ts&&...) const noexcept { return std::bool_constant<_callable_with<Fn, Ts...>>(); } }; template <class Fn, class Tupl> concept _member_apply = requires (_declval<Fn> fn, _declval<Tupl> tup) { tup().apply(fn()); }; template <class Fn, class Tupl> concept _non_member_apply = requires (_declval<Tupl> tup) { { apply(_std_apply_probe<Fn>(), tup()) } -> std::same_as<std::true_type>; }; struct _fn { template <class Fn, class Tupl> static constexpr bool _nothrow(_declval<Fn> fn = {}, _declval<Tupl> tupl = {}) noexcept { if constexpr (_member_apply<Fn, Tupl>) { return noexcept(tupl().apply(fn())); } else if constexpr (_non_member_apply<Fn, Tupl>) { return noexcept(apply(fn(), tupl())); } return true; } template <class Fn, class Tupl> requires _member_apply<Fn, Tupl> || _non_member_apply<Fn, Tupl> constexpr decltype(auto) operator()(Fn&& fn, Tupl&& tup) const noexcept(_nothrow<Fn, Tupl>()) { if constexpr (_member_apply<Fn, Tupl>) { return std::forward<Tupl>(tup).apply(std::forward<Fn>(fn)); } else { return apply(std::forward<Fn>(fn), std::forward<Tupl>(tup)); } } }; } struct _count_fn { constexpr auto operator()(auto&&... ts) const noexcept { return _size_t<sizeof...(ts)>(); } }; struct _any { template <class T> constexpr operator T&& () const; }; template <class T> struct _any_base { template <class U, class To = std::remove_cvref_t<U>> requires std::derived_from<T, To> && std::is_empty_v<To> constexpr operator U&&() const; }; // A "structured" tuple-like type is an ordinary struct that is usable as the // initializer for a structured binding. There is no way in standard C++ to // detect such types. Below we approximate it using the "magic tuple" trick. // This code is easily confused. template <class> constexpr size_t _structured_tuple_size = ~size_t(0); template <class Tupl, class... Any> constexpr size_t _struct_bases() noexcept { if constexpr (requires { Tupl{_any_base<Tupl>(), Any()...}; }) return _struct_bases<Tupl, Any..., _any_base<Tupl>>(); return sizeof...(Any); } template <size_t N, class Tupl, class... Any> consteval size_t _struct_size() noexcept { if constexpr (N != 0) if (size_t size = _struct_size<N-1, Tupl, Any..., _any>(); size != ~size_t(0)) return size; return requires { Tupl{Any()...}; } ? sizeof...(Any) - _struct_bases<Tupl>() : ~size_t(0); } template <size_t N, class Tupl, class... Any> requires (_structured_tuple_size<Tupl> != ~size_t(0)) consteval size_t _struct_size() noexcept { static_assert(_structured_tuple_size<Tupl> <= _max_structured_tuple_size); return _structured_tuple_size<Tupl>; } template <class Tupl> concept _has_tuple_protocol = requires { { std::tuple_size<std::remove_cvref_t<Tupl>>::value } -> std::integral; }; template <class Tupl> concept _has_tuple_apply = requires (_declval<Tupl> tup) { _apply_cpo::_fn()(_count_fn(), tup()); }; template <class Tupl> concept _is_structured_type = std::is_aggregate_v<std::remove_cvref_t<Tupl>> && (_struct_size<_max_structured_tuple_size, std::remove_cvref_t<Tupl>>() != ~size_t(0)); template <class Tupl> concept _tuple_like = _has_tuple_protocol<Tupl> || _has_tuple_apply<Tupl> || _is_structured_type<Tupl>; template <_tuple_like Tupl> consteval size_t _tuple_size() { if constexpr (_has_tuple_protocol<Tupl>) { return std::tuple_size<std::remove_cvref_t<Tupl>>::value; } else if constexpr (_has_tuple_apply<Tupl>) { return decltype(_apply_cpo::_fn()(_count_fn(), std::declval<Tupl>()))::value; } else { return _struct_size<_max_structured_tuple_size, std::remove_cvref_t<Tupl>>(); } } template <class... As, _callable_with<As...> Fn> constexpr auto _struct_apply(Fn&& fn, As&&... args) noexcept -> _call_result_t<Fn, As...> { return std::forward<Fn>(fn)(std::forward<As>(args)...); } struct _invalid_function_call { }; // Below we use structured binding to unpack a struct into its members so we can pass // them as arguments to a callable. We handle up to 16 members until clang implements // "Structured Bindings Can Introduce a Pack". #define _M0(_A) , static_cast<_copy_cvref_t<Tupl, decltype(_A)>&&>(_A) #define _M1(_A) , _copy_cvref_t<Tupl, decltype(_A)> #define _STRUCTURED_APPLY_FN(...) \ template <class Fn, class Tupl> \ constexpr decltype(auto) operator()(Fn&& fn, Tupl&& tup) const { \ auto&& [__VA_ARGS__] = tup; \ if constexpr (_callable_with<Fn _FOR_EACH(_M1, __VA_ARGS__)>) \ return ::_struct_apply(std::forward<Fn>(fn) _FOR_EACH(_M0, __VA_ARGS__)); \ else \ return _invalid_function_call(); \ } template <size_t Size> struct _structured_apply; template <> struct _structured_apply<0> { template <class Fn> constexpr decltype(auto) operator()(Fn&& fn, _ignore) const { if constexpr (_callable_with<Fn>) return std::forward<Fn>(fn)(); else return _invalid_function_call(); } }; template <> struct _structured_apply<1> { _STRUCTURED_APPLY_FN(_0) }; template <> struct _structured_apply<2> { _STRUCTURED_APPLY_FN(_0,_1) }; template <> struct _structured_apply<3> { _STRUCTURED_APPLY_FN(_0,_1,_2) }; template <> struct _structured_apply<4> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3) }; template <> struct _structured_apply<5> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4) }; template <> struct _structured_apply<6> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5) }; template <> struct _structured_apply<7> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6) }; template <> struct _structured_apply<8> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7) }; template <> struct _structured_apply<9> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8) }; template <> struct _structured_apply<10> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9) }; template <> struct _structured_apply<11> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) }; template <> struct _structured_apply<12> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11) }; template <> struct _structured_apply<13> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12) }; template <> struct _structured_apply<14> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13) }; template <> struct _structured_apply<15> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14) }; template <> struct _structured_apply<16> { _STRUCTURED_APPLY_FN(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15) }; #undef _M0 #undef _M1 #undef _STRUCTURED_APPLY_FN template <class Fn, class Tupl> using _structured_apply_result_t = _call_result_t<_structured_apply<_tuple_size<Tupl>()>, Fn, Tupl>; template <size_t N> struct _get_nth { template <class... Ts> constexpr decltype(auto) operator()(Ts&&... ts) const noexcept { return static_cast<Ts...[N]&&>(ts...[N]); } }; inline constexpr struct _apply_t : _apply_cpo::_fn { template <class Fn> struct _nothrow_fn { template <class... Ts> constexpr auto operator()(Ts&&...) const noexcept { return std::bool_constant<_nothrow_callable_with<Fn, Ts...>>(); } }; using _apply_cpo::_fn::operator(); template <class Fn, _is_structured_type Tupl> requires (!_callable_with<_apply_cpo::_fn, Fn, Tupl>) && (!std::same_as<_structured_apply_result_t<Fn, Tupl>, _invalid_function_call>) constexpr auto operator()(Fn&& fn, Tupl&& tup) const noexcept(_structured_apply_result_t<_nothrow_fn<Fn>, Tupl>::value) { return _structured_apply<_tuple_size<Tupl>()>()(std::forward<Fn>(fn), std::forward<Tupl>(tup)); } } _apply {}; template <class Fn, class Tup> concept _applicable_with = _callable_with<_apply_t, Fn, Tup>; template <class Fn, class Tup> concept _nothrow_applicable_with = _nothrow_callable_with<_apply_t, Fn, Tup>; template <class Fn, class Tup> using _apply_result_t = _call_result_t<_apply_t, Fn, Tup>; template <size_t I> struct _get_t { template <_tuple_like Tupl> requires (_tuple_size<Tupl>() > I) constexpr decltype(auto) operator()(Tupl&& tup) const noexcept { if constexpr (requires { std::forward<Tupl>(tup).template get<I>(); }) { return std::forward<Tupl>(tup).template get<I>(); } else if constexpr (requires { get<I>(std::forward<Tupl>(tup)); }) { return get<I>(std::forward<Tupl>(tup)); } else if constexpr (requires { _apply_cpo::_fn()(_get_nth<I>(), std::forward<Tupl>(tup)); }) { return _apply_cpo::_fn()(_get_nth<I>(), std::forward<Tupl>(tup)); } else { return _structured_apply<_tuple_size<Tupl>()>()(_get_nth<I>(), std::forward<Tupl>(tup)); } } }; template <size_t I> inline constexpr _get_t<I> _get {}; // For creating overload sets from a set of lambdas template <class... Fns> struct _overload_set : Fns... { using Fns::operator()...; }; // Awaitable helpers: template <class T> concept _await_suspend_result = std::same_as<T, void> || std::same_as<T, bool> || _is_specialization_of<T, std::coroutine_handle>; template <class A, class... Promise> concept _is_awaiter = requires (A& a, std::coroutine_handle<Promise...> h) { a.await_ready() ? 1 : 0; { a.await_suspend(h) } -> _await_suspend_result; a.await_resume(); }; template <class Awaitable> constexpr auto _get_awaiter(Awaitable&& _awaitable, _ignore = {}) -> decltype(auto) { if constexpr (requires { std::forward<Awaitable>(_awaitable).operator co_await(); }) { return std::forward<Awaitable>(_awaitable).operator co_await(); } else if constexpr (requires { operator co_await(std::forward<Awaitable>(_awaitable)); }) { return operator co_await(std::forward<Awaitable>(_awaitable)); } else { return std::forward<Awaitable>(_awaitable); } } template <class Awaitable, class Promise> constexpr auto _get_awaiter(Awaitable&& _awaitable, Promise& _promise) -> decltype(auto) requires requires { _promise.await_transform(std::forward<Awaitable>(_awaitable)); } { if constexpr (requires { _promise.await_transform(std::forward<Awaitable>(_awaitable)).operator co_await(); }) { return _promise.await_transform(std::forward<Awaitable>(_awaitable)).operator co_await(); } else if constexpr (requires { operator co_await(_promise.await_transform(std::forward<Awaitable>(_awaitable))); }) { return operator co_await(_promise.await_transform(std::forward<Awaitable>(_awaitable))); } else { return _promise.await_transform(std::forward<Awaitable>(_awaitable)); } } template <class C, class... Promise> concept _is_awaitable = requires (C (*fc)() noexcept, Promise&... p) { { ::_get_awaiter(fc(), p...) } -> _is_awaiter<Promise...>; }; template <class Awaitable, class... Promise> using _awaiter_of_t = decltype(::_get_awaiter(std::declval<Awaitable>(), std::declval<Promise&>()...)); template <class Awaitable, class... Promise> requires _is_awaitable<Awaitable, Promise...> using _await_result_t = decltype(std::declval<_awaiter_of_t<Awaitable, Promise...>&>().await_resume()); // #endregion template<class T, class Promise> concept _has_as_awaitable = requires (T&& t, Promise& p) { { std::forward<T>(t).as_awaitable(p) } -> _is_awaitable<Promise&>; }; template <class Derived> struct _with_await_transform { template <class T> T&& await_transform(T&& value) noexcept { return std::forward<T>(value); } template<_has_as_awaitable<Derived> T> decltype(auto) await_transform(T&& value) noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>()))) { return std::forward<T>(value).as_awaitable(static_cast<Derived&>(*this)); } }; template <class Env> struct _env_promise : _with_await_transform<_env_promise<Env>> { std::suspend_never get_return_object() noexcept; std::suspend_never initial_suspend() noexcept; std::suspend_never final_suspend() noexcept; void unhandled_exception() noexcept; void return_void() noexcept; std::coroutine_handle<> unhandled_stopped() noexcept; const Env& get_env() const noexcept; }; // Exception types struct std_exception { std_exception() = default; virtual constexpr const char* what() const noexcept { return "unknown exception"; } }; template <class Derived> struct compile_time_error : std_exception { compile_time_error() = default; constexpr const char* what() const noexcept override { return typeid(Derived*).name(); } }; template <class Data, class... What> struct _sender_type_check_failure : compile_time_error<_sender_type_check_failure<Data, What...>> { _sender_type_check_failure() requires std::default_initializable<Data> = default; explicit constexpr _sender_type_check_failure(Data data) : data_(data) { } Data data_; }; struct _dependent_sender_error_base : std_exception { constexpr char const* what() const noexcept override { return what_; } char const* what_; }; template <class Sndr> struct _dependent_sender_error : _dependent_sender_error_base { constexpr _dependent_sender_error() noexcept { what_ = "This sender needs to know its execution environment before it can know how it will complete."; } }; // Concept tag types struct sender_t { using sender_concept = sender_t; }; struct receiver_t { using receiver_concept = receiver_t; }; struct operation_state_t { using operation_state_concept = operation_state_t; }; struct scheduler_t { using scheduler_concept = scheduler_t; }; // Completion tags (set_value, set_error, set_stopped): enum class _disposition { _value, _error, _stopped }; template <class Rcvr, _disposition Disp, class... Ts> concept _has_set_xxx = bool( requires(_declval<Rcvr> rcvr, _declval<Ts>... ts) { requires (Disp == _disposition::_value && requires { rcvr().set_value(ts()...); }) || (Disp == _disposition::_error && sizeof...(Ts) == 1 && requires { rcvr().set_error(ts()...); }) || (Disp == _disposition::_stopped && sizeof...(Ts) == 0 && requires { rcvr().set_stopped(ts()...); }); } ); template <_disposition Disp> struct _set_xxx_t { template <enum _disposition Other> constexpr bool operator==(_set_xxx_t<Other>) const noexcept { return Disp == Other; } template <class... Ts, _has_set_xxx<Disp, Ts...> Rcvr> constexpr void operator()(Rcvr&& rcvr, Ts&&... ts) const noexcept { if constexpr (Disp == _disposition::_value) std::move(rcvr).set_value(std::forward<Ts>(ts)...); if constexpr (Disp == _disposition::_error) std::move(rcvr).set_error(std::forward<Ts>(ts)...); if constexpr (Disp == _disposition::_stopped) std::move(rcvr).set_stopped(std::forward<Ts>(ts)...); } static constexpr bool _valid_arg_count(size_t count) noexcept { if constexpr (Disp == _disposition::_error) return count == 1; if constexpr (Disp == _disposition::_stopped) return count == 0; return true; } static constexpr _disposition _disposition = Disp; }; inline constexpr struct set_value_t : _set_xxx_t<_disposition::_value> {} set_value{}; inline constexpr struct set_error_t : _set_xxx_t<_disposition::_error> {} set_error{}; inline constexpr struct set_stopped_t : _set_xxx_t<_disposition::_stopped> {} set_stopped{}; template <class Sig> extern std::false_type _is_completion_signature_v; template <class... Ts> extern std::true_type _is_completion_signature_v<set_value_t(Ts...)>; template <class Error> extern std::true_type _is_completion_signature_v<set_error_t(Error)>; template<> std::true_type _is_completion_signature_v<set_stopped_t()>; template <class Sig> concept _completion_signature = decltype(_is_completion_signature_v<Sig>)::value; template <_completion_signature... Sigs> struct completion_signatures; inline constexpr auto _to_completions = []<class... Sigs>() { return completion_signatures<Sigs...>(); }; template <class Tag> inline constexpr auto _default_transform_fn = []<class... Ts>() -> completion_signatures<Tag(Ts...)> { return {}; }; template <class Tag, class... As> auto _normalize2(As&&...) -> Tag(*)(As...); template <class Tag, class... As> extern decltype(::_normalize2<Tag>(std::declval<As>()...)) _normalize2_v; template <class Tag, class... As> using _normalized_args_t = decltype(auto(_normalize2_v<Tag, As...>)); template <class Tag, class... As> auto _normalize1(Tag(*)(As...)) -> decltype(::_normalize2<Tag>(std::declval<As>()...)); template <class Sig> extern decltype(::_normalize1(static_cast<Sig*>(nullptr))) _normalize1_v; template <class Sig> using _normalized_sig_t = decltype(auto(_normalize1_v<Sig>)); template <class... Sigs> constexpr auto _unique(Sigs*...) { return (_typeset() + completion_signatures<Sigs...>())._apply(_to_completions); } template <auto Tag, class... Ts> using signature = decltype(Tag)(*)(Ts...); template <class... Ts> inline constexpr auto value = signature<set_value, Ts...>(); template <> inline constexpr auto value<void> = signature<set_value>(); template <class Err = std::exception_ptr> inline constexpr auto error = signature<set_error, Err>(); inline constexpr auto stopped = signature<set_stopped>(); template <class... Sigs> auto _mk_sigset(Sigs*...) -> _mk_typeset<Sigs...>; // completion_signatures template <_completion_signature... Sigs> struct completion_signatures { private: struct _checked { using _typeset = decltype(::_mk_sigset(_normalized_sig_t<Sigs>()...)); static_assert(_typeset().size() == sizeof...(Sigs)); }; template <class Fn, class Tag, class... As> static constexpr auto _filter1(Tag(*)(As...)) -> _typeset_element<_if<_callable_with<Fn, Tag(*)(As...)>, Tag(As...), _ignore>>; public: completion_signatures() = default; explicit constexpr completion_signatures(auto*, auto*...) noexcept { } static constexpr std::size_t size() noexcept { return sizeof...(Sigs); } template <std::size_t I> static constexpr auto get() noexcept { return _normalized_sig_t<Sigs...[I]>(); } template <std::size_t I> friend constexpr auto get(completion_signatures) noexcept { return _normalized_sig_t<Sigs...[I]>(); } template <class... Other> constexpr auto operator+(completion_signatures<Other...> cs) const noexcept { return (typename _checked::_typeset() + cs)._apply(_to_completions); } template <class Tag, class... As> constexpr friend auto operator+(completion_signatures, Tag(*)(As...)) noexcept { using Sig = std::remove_pointer_t<_normalized_args_t<Tag, As...>>; return (typename _checked::_typeset() + _typeset_element<Sig>())._apply(_to_completions); } template <class Tag, class... As> constexpr friend auto operator+(Tag(*sig)(As...), completion_signatures sigs) noexcept { return sigs + sig; } template <_callable_with<Sigs*...> Fn> static constexpr decltype(auto) apply(Fn fn) noexcept(_nothrow_callable_with<Fn, Sigs*...>) { return fn(_normalized_sig_t<Sigs>()...); } template <class Fn> static constexpr auto filter(Fn) { return (_typeset() +...+ decltype(_filter1<Fn>(_normalized_sig_t<Sigs>()))())._apply(_to_completions); } template <class Tag> static constexpr auto select(Tag) noexcept { return filter([]<class... Ts>(Tag(*)(Ts...)) { }); } template <class MapFn, class ReduceFn = decltype([](auto... ts) { return (ts +...); })> static constexpr auto transform(MapFn map, ReduceFn reduce = {}) { return reduce(map(_normalized_sig_t<Sigs>())...); } template <class... OtherSigs> static constexpr bool contains(completion_signatures<OtherSigs...>) noexcept { using Self = typename _checked::_typeset; return (__is_base_of(_typeset_element<_normalized_sig_t<OtherSigs>>, Self) &&...); } template <class... OtherSigs> constexpr bool operator==(completion_signatures<OtherSigs...> other) const noexcept { if constexpr (sizeof...(Sigs) == sizeof...(OtherSigs)) return contains(other); else return false; } }; template <class... Sigs> completion_signatures(Sigs*...) -> completion_signatures<std::remove_pointer_t<_normalized_sig_t<Sigs>>...>; template <class... Sigs> struct std::tuple_size<completion_signatures<Sigs...>> : _size_t<sizeof...(Sigs)> {}; template <size_t I, class... Sigs> struct std::tuple_element<I, completion_signatures<Sigs...>> { using type = Sigs...[I]*; }; //! @brief builds a completion_signatures specialization by normalizing //! all the signatures and then removing duplicates. To normalize a signature //! is to remove rvalue references from arguments. For example, //! set_value_t(int&&, float&) normalizes to set_value_t(int, float&). template <class... Sigs> inline constexpr auto _completions_v = ::_unique(_normalized_sig_t<Sigs>()...); template <bool PotentiallyThrowing> inline constexpr auto eptr_completion_if = _if<PotentiallyThrowing, completion_signatures<set_error_t(std::exception_ptr)>, completion_signatures<>>(); template <class T> concept _valid_completion_signatures = _is_specialization_of<T, completion_signatures>; template <class... Sigs1, class... Sigs2> constexpr auto make_completion_signatures(Sigs2*... sigs2) noexcept { return _completions_v<Sigs1..., Sigs2...>; } template <class... What, class... Values> [[noreturn, nodiscard]] consteval completion_signatures<> invalid_completion_signature(Values... values) { if constexpr (sizeof...(Values) == 1) { throw _sender_type_check_failure<Values...[0], What...>(values...); } else { (void) ::invalid_completion_signature<What...>([...data = values]{}); } } template <class... As, _meta_callable_with<As...> Fn> constexpr auto _transform_expr(const Fn& fn) requires (sizeof...(As) != 0) { return fn.template operator()<As...>(); } template <_callable_with Fn> constexpr auto _transform_expr(const Fn& fn) { return fn(); } // transform_completion_signatures: template <class... As, class Fn> consteval auto _apply_transform(const Fn& fn) { if constexpr (requires { { ::_transform_expr<As...>(fn) } -> _valid_completion_signatures; }) { return ::_transform_expr<As...>(fn); } else { if constexpr (requires { ::_transform_expr<As...>(fn); }) { (void) ::_transform_expr<As...>(fn); // potentially throwing } return ::invalid_completion_signature< struct IN_TRANSFORM_COMPLETION_SIGNATURES, struct A_TRANSFORM_FUNCTION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION, struct WITH_FUNCTION(Fn), struct WITH_ARGUMENTS(As...)>(); } } template <_valid_completion_signatures Completions, class ValueFn = decltype(_default_transform_fn<set_value_t>), class ErrorFn = decltype(_default_transform_fn<set_error_t>), class StoppedFn = decltype(_default_transform_fn<set_stopped_t>), _valid_completion_signatures ExtraSigs = completion_signatures<>> constexpr auto transform_completion_signatures(Completions cs, ValueFn value_fn = {}, ErrorFn error_fn = {}, StoppedFn stopped_fn = {}, ExtraSigs extra = {}) { auto transform1 = [=]<class Tag, class... Ts>(Tag(*)(Ts...)) { if constexpr (Tag() == set_value) return _apply_transform<Ts...>(value_fn); else if constexpr (Tag() == set_error) return _apply_transform<Ts...>(error_fn); else return _apply_transform<Ts...>(stopped_fn); }; auto transform_all = [=](auto*... sigs) { return (transform1(sigs) +...+ completion_signatures()); }; return _apply(transform_all, cs) + extra; } // clang crashes if this is a lambda variable template instead of a class template template <_valid_completion_signatures Completions, template <class...> class Tuple, template <class...> class Variant> using _value_types = _constant_wrapper< Completions().select(set_value).transform( []<class...Ts>(set_value_t(*)(Ts...)) { return _typelist<Ts...>{}; }, []<class...Ts>(Ts...) { return std::type_identity<Variant<typename Ts::template _apply<Tuple>...>>{}; }) >::type::type; template <_valid_completion_signatures Completions, template <class...> class Variant> using _error_types = _constant_wrapper< Completions().select(set_error).transform( []<class Error>(set_error_t(*)(Error)) { return std::type_identity<Error>{}; }, []<class...Ts>(Ts...) { return std::type_identity<Variant<typename Ts::type...>>{}; }) >::type::type; // Environments template <class Env> concept queryable = std::destructible<Env>; template <class Env, class Query> using _query_result_t = decltype(std::declval<Env>().query(std::declval<Query>())); template <class Env, class Query> concept _has_query = _can_be_instantiated_with<_query_result_t, Env, Query>; template <class Query, queryable Env, class Default> constexpr decltype(auto) _query_or_default(Query q, Env&& env, Default def) { if constexpr (_callable_with<Query, Env>) { return q(std::forward<Env>(env)); } else { return auto(std::move(def)); } } template <class Query, class Value> struct prop : Query { constexpr const Value& query(Query) const noexcept { return value; } Value value; }; template <queryable... Envs> struct env : _tuple<Envs...> { template <class Query> static constexpr size_t _first_index() noexcept { bool _map[]{_has_query<Envs, Query>...}; return std::ranges::find(_map, true) - _map; } template <class Query> requires (_has_query<Envs, Query> ||...) constexpr decltype(auto) query(Query q) const noexcept { return this->template get<_first_index<Query>()>().query(q); } }; template <class... Envs> env(Envs...) -> env<std::unwrap_reference_t<Envs>...>; // Forwarding queries inline constexpr struct forwarding_query_t { template <class Query> constexpr bool operator()(Query q) const noexcept { if constexpr (q.query(*this)) { static_assert(requires { {q.query(*this)} noexcept -> std::same_as<bool>; }); } else { return std::derived_from<Query, forwarding_query_t>; } } } forwarding_query {}; template <class Query> concept _forwarding_query = forwarding_query(Query()); template <class Env> struct _fwd_env { Env env_; template <_forwarding_query Query> requires _has_query<Env, Query> constexpr decltype(auto) operator()(Query q) const noexcept(noexcept(env_.query(q))) { return env_.query(q); } }; template <class Env> constexpr _fwd_env<Env> _FWD_ENV(Env&& env) noexcept(noexcept(_fwd_env<Env>(std::forward<Env>(env)))) { return _fwd_env<Env>(std::forward<Env>(env)); } template <class Env> requires _is_specialization_of<std::remove_cvref_t<Env>, _fwd_env> constexpr Env _FWD_ENV(Env&& env) noexcept(noexcept(Env(std::forward<Env>(env)))) { return std::forward<Env>(env); } template <class Env> using _FWD_ENV_T = decltype(_FWD_ENV(std::declval<Env>())); // get_env and env_of_t inline constexpr struct get_env_t { template <class EP> constexpr decltype(auto) operator()(EP&& ep) const noexcept { if constexpr (requires { ep.get_env(); }) { static_assert(requires { { ep.get_env() } noexcept -> queryable; }); return ep.get_env(); } else { return env(); } } } get_env {}; template <class EnvProvider> using env_of_t = decltype(get_env(std::declval<EnvProvider>())); // concept: sender template <class Sndr> inline constexpr bool enable_sender = false; template<class Sndr> concept _is_sender = std::derived_from<typename Sndr::sender_concept, sender_t> || enable_sender<Sndr>; template<class Sndr> concept _sender_or_awaitable = _is_sender<Sndr> || _is_awaitable<Sndr, _env_promise<env<>>>; template<class Sndr> concept sender = bool(_sender_or_awaitable<std::remove_cvref_t<Sndr>>) && requires (const std::remove_cvref_t<Sndr>& sndr) { { get_env(sndr) } -> queryable; } && std::move_constructible<std::remove_cvref_t<Sndr>> && std::constructible_from<std::remove_cvref_t<Sndr>, Sndr>; // basic_sender struct _transformable_sender_fn { void operator()(auto, auto&&, sender auto&&...) const noexcept {} }; // Non-standard template <class Sndr> concept _transformable_sender = sender<Sndr> && _tuple_like<Sndr> && requires (_declval<Sndr> sndr) { ((decltype(_apply(_transformable_sender_fn(), sndr()))*) nullptr); }; // tag_of_t: for getting the algorithm tag type of a transformable sender template <_transformable_sender Sndr> using tag_of_t = decltype(auto(_get<0>(std::declval<Sndr>()))); // domains struct default_domain { template <class Sndr, class... Env> static constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) { if constexpr (requires { tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...); }) { return tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...); } else { return static_cast<Sndr>(std::forward<Sndr>(sndr)); } } }; inline constexpr struct get_domain_t : forwarding_query_t { template <_has_query<get_domain_t> Env> constexpr decltype(auto) operator()(const Env& env) const noexcept { static_assert(noexcept(env.query(*this))); return env.query(*this); } } get_domain {}; template<class Sndr> constexpr auto _get_domain_early(const Sndr& sndr) noexcept { if constexpr (requires { get_domain(get_env(sndr)); }) { return decltype(get_domain(get_env(sndr)))(); } // else if constexpr (requires { _completion_domain(sndr); }) { // return _completion_domain(sndr); // } else { return default_domain(); } } template <class Sndr> using _early_domain_of_t = decltype(::_get_domain_early(std::declval<Sndr>())); template<class Sndr, class Env> constexpr auto _get_domain_late(const Sndr& sndr, const Env& env) noexcept { // if constexpr (_sender_for<Sndr, continues_on_t>) { // auto& [_, sched, _] = sndr; // return _query_or_default(get_domain, sched, default_domain()); // } else if constexpr (requires { get_domain(get_env(sndr)); }) { return decltype(get_domain(get_env(sndr))){}; } // else if constexpr (requires { _completion_domain(sndr); }) { // return _completion_domain(sndr); // } else if constexpr (requires { get_domain(env); }) { return decltype(get_domain(env)){}; } else if constexpr (requires { get_domain(get_scheduler(env)); }) { return decltype(get_domain(get_scheduler(env))){}; } else { return default_domain(); } } template <class Sndr, class Env> using _late_domain_of_t = decltype(::_get_domain_late(std::declval<Sndr>(), std::declval<Env>())); // transform_sender struct _transform_sender_fn { template<class Self = _transform_sender_fn, class Domain, sender Sndr, queryable... Env> constexpr decltype(auto) operator()(Domain dom, Sndr&& sndr, const Env&... env) const { //noexcept(see below); constexpr bool has_transform = requires { dom.transform_sender(std::forward<Sndr>(sndr), env...); }; using NewDomain = _if<has_transform, Domain, default_domain>; using NewSndr = decltype(NewDomain().transform_sender(std::forward<Sndr>(sndr), env...)); if constexpr (std::same_as<std::remove_cvref_t<Sndr>, std::remove_cvref_t<NewSndr>>) { return NewDomain().transform_sender(std::forward<Sndr>(sndr), env...); } else { return Self()(dom, NewDomain().transform_sender(std::forward<Sndr>(sndr), env...), env...); } } }; template<class Domain, sender Sndr, queryable... Env> constexpr sender decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) { //noexcept(see below); return _transform_sender_fn()(dom, std::forward<Sndr>(sndr), env...); } template <class Domain, class Sndr, class... Env> using _transform_sender_result_t = decltype(::transform_sender(Domain(), std::declval<Sndr>(), std::declval<Env>()...)); // get_completion_signatures #define _GET_COMPLSIGS(Sndr, ...) \ std::remove_reference_t<Sndr>::template get_completion_signatures<Sndr __VA_OPT__(,) __VA_ARGS__>() #define _CHECKED_COMPLSIGS(...) (__VA_ARGS__, ::_checked_complsigs<decltype(__VA_ARGS__)>()) template <class Sndr> using _nested_complsigs_t = std::remove_reference_t<Sndr>::completion_signatures; template <class Completions> consteval auto _checked_complsigs() { if constexpr (_valid_completion_signatures<Completions>) return Completions(); else return invalid_completion_signature</*TODO*/>(); } template <class Sndr, class... Env> consteval auto _get_completion_signatures_helper() { if constexpr (requires { _GET_COMPLSIGS(Sndr, Env...); }) { return _CHECKED_COMPLSIGS(_GET_COMPLSIGS(Sndr, Env...)); } else if constexpr (requires { _GET_COMPLSIGS(Sndr); }) { return _CHECKED_COMPLSIGS(_GET_COMPLSIGS(Sndr)); } else if constexpr (requires { _nested_complsigs_t<Sndr>(); }) { return _CHECKED_COMPLSIGS(_nested_complsigs_t<Sndr>()); } else if constexpr (_is_awaitable<Sndr, _env_promise<Env>...>) { using Result = _await_result_t<Sndr, _env_promise<Env>...>; return completion_signatures{value<Result>, error<>, stopped}; } else if constexpr (sizeof...(Env) == 0) { return (throw _dependent_sender_error<Sndr>(), _completions_v<>); } else { return invalid_completion_signature</*TODO*/>(); } } template <class Sndr> consteval auto get_completion_signatures() -> _valid_completion_signatures auto { return _get_completion_signatures_helper<Sndr>(); } template <class Sndr, class Env> consteval auto get_completion_signatures() -> _valid_completion_signatures auto { // Apply a lazy sender transform if one exists before computing the completion signatures: using NewSndr = _transform_sender_result_t<_late_domain_of_t<Sndr, Env>, Sndr, Env>; return _get_completion_signatures_helper<NewSndr, Env>(); } template <class Parent, class Child, class... Env> consteval auto get_child_completion_signatures() { return get_completion_signatures<_copy_cvref_t<Parent, Child>, _FWD_ENV_T<Env>...>(); } #undef _GET_COMPLSIGS #undef _CHECKED_COMPLSIGS // Receiver concepts template <class Rcvr> concept receiver = std::derived_from<typename std::remove_reference_t<Rcvr>::receiver_concept, receiver_t> && requires (Rcvr& rcvr) { get_env(rcvr); } && std::move_constructible<Rcvr>; template <class Rcvr, class Signature> struct _missing_completion_signature : compile_time_error<_missing_completion_signature<Rcvr, Signature>> {}; template <class Rcvr> constexpr auto _has_completion_helper = []<class Tag, class... As>(Tag(*)(As...)) { return _callable_with<Tag, Rcvr, As...> ? true : (throw _missing_completion_signature<Rcvr, Tag(As...)>(), false); }; template <class Rcvr> constexpr auto _has_completions = [](auto... sigs) { return (_has_completion_helper<Rcvr>(sigs) &&...); }; template <class Rcvr, class Completions> concept receiver_of = receiver<Rcvr> && std::bool_constant<_apply(_has_completions<Rcvr>, Completions())>::value; // start extern const struct start_t start; inline constexpr struct start_t { template <class Op> constexpr auto operator()(Op& op) const noexcept requires requires { op.start(); } { static_assert(noexcept(op.start())); op.start(); } } start {}; // Operation state concept template <class Op> concept operation_state = std::derived_from<typename Op::operation_state_concept, operation_state_t> && requires (Op& op) { start(op); }; // Sender concepts (incomplete) template <class Sndr, class... Env> concept sender_in = sender<Sndr> && (queryable<Env> &&...) && _is_constant<get_completion_signatures<Sndr, Env...>()>; template <class Sndr, class... Env> requires sender_in<Sndr, Env...> using completion_signatures_of_t = decltype(get_completion_signatures<Sndr, Env...>()); template <class Sndr> consteval bool _is_dependent_sender_helper() try { (void) get_completion_signatures<Sndr>(); return false; } catch (_dependent_sender_error_base&) { return true; } template <class Sndr> concept dependent_sender = sender<Sndr> && std::bool_constant<_is_dependent_sender_helper<Sndr>()>::value; // _single_sender: [execution.syn] p2-3 auto _single_sender_value_helper(_typelist<>) -> void; auto _single_sender_value_helper(_typelist<_typelist<>>) -> void; template <class T> auto _single_sender_value_helper(_typelist<_typelist<T>>) -> T; template <class... Ts> auto _single_sender_value_helper(_typelist<_typelist<Ts...>>) -> std::tuple<Ts...>; template <class Sndr, class... Env> requires sender_in<Sndr, Env...> using _single_sender_value_t = decltype(::_single_sender_value_helper(_value_types<completion_signatures_of_t<Sndr, Env...>, _typelist, _typelist>())); template <class Sndr, class... Env> concept _single_sender = requires { typename _single_sender_value_t<Sndr, Env...>; }; // connect awaitables template <class Rcvr> struct _connect_awaitable_promise; template <class Sndr, class Rcvr> using _connect_awaitable_result_t = _await_result_t<Sndr, _connect_awaitable_promise<Rcvr>>; template <class Sndr, class Rcvr> using _connect_awaitable_completions_t = decltype(completion_signatures{value<_connect_awaitable_result_t<Sndr, Rcvr>>, error<>, stopped}); struct _operation_state_task_base { using operation_state_concept = ::operation_state_t; explicit _operation_state_task_base(std::coroutine_handle<> h) noexcept : coro_(h) {} _operation_state_task_base(_operation_state_task_base&&) = delete; ~_operation_state_task_base() { coro_.destroy(); } void start() & noexcept { coro_.resume(); } private: std::coroutine_handle<> coro_; }; template <class Rcvr> struct _operation_state_task : _operation_state_task_base { using _operation_state_task_base::_operation_state_task_base; using promise_type = _connect_awaitable_promise<Rcvr>; }; struct _connect_awaitable_promise_base { std::suspend_always initial_suspend() noexcept { return {}; } [[noreturn]] std::suspend_always final_suspend() noexcept { std::terminate(); } [[noreturn]] void unhandled_exception() noexcept { std::terminate(); } [[noreturn]] void return_void() noexcept { std::terminate(); } }; template <class Rcvr> struct _connect_awaitable_promise : _with_await_transform<_connect_awaitable_promise<Rcvr>> , _connect_awaitable_promise_base { _connect_awaitable_promise(_ignore, Rcvr& rcvr) noexcept : rcvr_(rcvr) {} std::coroutine_handle<> unhandled_stopped() noexcept { set_stopped(std::move(rcvr_)); return std::noop_coroutine(); } _operation_state_task<Rcvr> get_return_object() noexcept { return _operation_state_task<Rcvr>{ std::coroutine_handle<_connect_awaitable_promise>::from_promise(*this)}; } env_of_t<Rcvr> get_env() const noexcept { return ::get_env(rcvr_); } private: Rcvr& rcvr_; }; struct _suspend_complete_awaiter_base { static constexpr bool await_ready() noexcept { return false; } [[noreturn]] void await_resume() noexcept { std::unreachable(); } }; template <class Fun> struct _suspend_complete_awaiter : _suspend_complete_awaiter_base { Fun fn; void await_suspend(std::coroutine_handle<>) noexcept { fn(); } }; template <class Fun, class... Ts> auto _suspend_complete(Fun fun, Ts&&... as) noexcept { return _suspend_complete_awaiter{{}, [&, fun]() noexcept { fun(std::forward<Ts>(as)...); }}; } template <class Sndr, class Rcvr> requires receiver_of<Rcvr, _connect_awaitable_completions_t<Sndr, Rcvr>> _operation_state_task<Rcvr> _connect_awaitable(Sndr sndr, Rcvr rcvr) { std::exception_ptr ep; try { if constexpr (std::same_as<void, _connect_awaitable_result_t<Sndr, Rcvr>>) { co_await std::move(sndr); co_await ::_suspend_complete(set_value, std::move(rcvr)); } else { co_await ::_suspend_complete(set_value, std::move(rcvr), co_await std::move(sndr)); } } catch(...) { ep = std::current_exception(); } co_await _suspend_complete(set_error, std::move(rcvr), std::move(ep)); } // connect template <class Sndr, class Rcvr> concept _has_connect = requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { transform_sender(domain, sndr(), get_env(rcvr())).connect(rcvr()); }; template <class Sndr, class Rcvr> concept _has_nothrow_connect = requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { { transform_sender(domain, sndr(), get_env(rcvr())).connect(rcvr()) } noexcept; }; template <class Sndr, class Rcvr> concept _has_connect_awaitable = requires (_declval<Sndr> sndr, _declval<Rcvr> rcvr, _late_domain_of_t<Sndr, env_of_t<Rcvr>> domain) { _connect_awaitable(transform_sender(domain, sndr(), get_env(rcvr())), rcvr()); }; inline constexpr struct connect_t { template <class Sndr, class Rcvr> requires _has_connect<Sndr, Rcvr> || _has_connect_awaitable<Sndr, Rcvr> constexpr auto operator()(Sndr&& sndr, Rcvr&& rcvr) const noexcept(_has_nothrow_connect<Sndr, Rcvr>) { static_assert(sender_in<Sndr, env_of_t<Rcvr>>); static_assert(receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>>); using Domain = _late_domain_of_t<Sndr, env_of_t<Rcvr>>; using NewSndr = _transform_sender_result_t<Domain, Sndr, env_of_t<Rcvr>>; const auto& env = get_env(rcvr); if constexpr (_has_connect<Sndr, Rcvr>) { return transform_sender(Domain(), std::forward<Sndr>(sndr), env).connect(std::forward<Rcvr>(rcvr)); } else { return ::_connect_awaitable(transform_sender(Domain(), std::forward<Sndr>(sndr), env), std::forward<Rcvr>(rcvr)); } } } connect {}; template <class Sndr, class Rcvr> concept sender_to = sender_in<Sndr, env_of_t<Rcvr>> && receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> && requires (Sndr&& sndr, Rcvr&& rcvr) { connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr)); }; template <class Sndr, class Rcvr> requires sender_to<Sndr, Rcvr> using connect_result_t = decltype(connect(std::declval<Sndr>(), std::declval<Rcvr>())); //! _not_a_sender is a type to be returned from `transform_sender` when the //! the given sender and environment do not type-check. //! //! @tparam ThrowFn is a nullary callable that should cause a meaningful exception //! to be thrown. template <auto ThrowFn> struct _not_a_sender { static_assert(_callable_with<decltype(ThrowFn)>); using sender_concept = sender_t; template <class Self> static constexpr auto get_completion_signatures() { ThrowFn(); // This _should_ exit with a meaningful exception return invalid_completion_signature<struct NOT_A_SENDER>(); } }; template <class Scheduler> concept _has_schedule = requires (_declval<Scheduler> sched) { sched().schedule(); }; template <class Scheduler> concept _has_nothrow_schedule = requires (_declval<Scheduler> sched) { {sched().schedule()} noexcept; }; // schedule inline constexpr struct schedule_t { template <_has_schedule Scheduler> constexpr decltype(auto) operator()(Scheduler&& sched) const noexcept(_has_nothrow_schedule<Scheduler>) { using Sndr = decltype(std::forward<Scheduler>(sched).schedule()); static_assert(sender<Sndr>); return std::forward<Scheduler>(sched).schedule(); } } schedule {}; template <class Scheduler> using schedule_result_t = decltype(schedule(std::declval<Scheduler>())); // Scheduler concept (incomplete) template <class Sched> concept scheduler = std::derived_from<typename std::remove_reference_t<Sched>::scheduler_concept, scheduler_t> && queryable<Sched> && requires (_declval<Sched> sched) { { schedule(sched()) } -> sender; } && std::equality_comparable<Sched> && std::copyable<Sched>; // Stop tokens struct never_stop_token { private: struct _callback_type { // exposition only explicit _callback_type(never_stop_token, auto&&) noexcept {} }; public: template<class> using callback_type = _callback_type; static constexpr bool stop_requested() noexcept { return false; } static constexpr bool stop_possible() noexcept { return false; } bool operator==(const never_stop_token&) const = default; }; template <class Token> concept stoppable_token = true; // TODO // Queries inline constexpr struct get_stop_token_t : forwarding_query_t { template <class Env> constexpr decltype(auto) operator()(const Env& env) const noexcept { if constexpr (_has_query<Env, get_stop_token_t>) { static_assert(requires { { env.query(*this) } noexcept -> stoppable_token; }); return env.query(*this); } else { return never_stop_token(); } } } get_stop_token {}; inline constexpr struct get_scheduler_t : forwarding_query_t { template <_has_query<get_scheduler_t> Env> constexpr decltype(auto) operator()(const Env& env) const noexcept { static_assert(requires { { env.query(*this) } noexcept -> scheduler; }); return env.query(*this); } } get_scheduler {}; // basic-sender template <class Sndr> using _data_type = decltype(_get<1>(std::declval<Sndr>())); // non-standard template<class Sndr, size_t I = 0> using _child_type = decltype(_get<I+2>(std::declval<Sndr>())); template<class Sndr> using _indices_for = std::make_index_sequence<std::remove_reference_t<Sndr>::_children::size>; template <class Sndr, class Tag> concept _sender_for = _transformable_sender<Sndr> && std::same_as<tag_of_t<Sndr>, Tag>; struct _default_impls { static constexpr auto _get_attrs = [](const auto&, const auto&... child) noexcept -> decltype(auto) { if constexpr (sizeof...(child) == 1) return (_FWD_ENV(get_env(child)), ...); else return env(); }; static constexpr auto _get_env = [](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) { return _FWD_ENV(get_env(rcvr)); }; static constexpr auto _get_state = []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) { return std::forward<Sndr>(sndr).data; }; static constexpr auto _start = [](auto&, auto&, auto&... ops) noexcept -> void { (start(ops), ...); }; static constexpr auto _complete = []<class Index, class Rcvr, class Tag, class... As>( Index, auto& state, Rcvr& rcvr, Tag, As&&... args) noexcept -> void requires _callable_with<Tag, Rcvr, As...> { static_assert(Index::value == 0); Tag()(std::move(rcvr), std::forward<As>(args)...); }; template <class Sndr, class... Env> static constexpr void _check_types() { []<size_t... Is>(std::index_sequence<Is...>) { ((void) get_completion_signatures<_child_type<Sndr, Is>, _FWD_ENV_T<Env>...>(), ...); }(_indices_for<Sndr>()); } }; template<class Tag> struct _impls_for : _default_impls {}; template <class Sndr> using _get_state_t = decltype(_impls_for<tag_of_t<Sndr>>::_get_state); template <class Sndr> using _get_env_t = decltype(_impls_for<tag_of_t<Sndr>>::_get_env); template<class Sndr, class Rcvr> using _state_type = std::decay_t<_call_result_t<_get_state_t<Sndr>, Sndr, Rcvr&>>; template<class Index, class Sndr, class Rcvr> using _env_type = _call_result_t<_get_env_t<Sndr>, Index, _state_type<Sndr, Rcvr>&, const Rcvr&>; template<class Sndr, class Rcvr> struct _basic_state { _basic_state(Sndr&& sndr, Rcvr&& rcvr) : rcvr(std::move(rcvr)) , state(_impls_for<tag_of_t<Sndr>>::_get_state(std::forward<Sndr>(sndr), rcvr)) { } Rcvr rcvr; _state_type<Sndr, Rcvr> state; }; template<class Sndr, class Rcvr, class Index> requires requires { typename _env_type<Index, Sndr, Rcvr>; } struct _basic_receiver { using receiver_concept = receiver_t; using _tag_t = tag_of_t<Sndr>; using _state_t = _state_type<Sndr, Rcvr>; static constexpr const auto& _complete = _impls_for<_tag_t>::_complete; using _complete_t = decltype(_complete); template<class... As> requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_value_t, As...> void set_value(As&&... args) && noexcept { _complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<As>(args)...); } template<class Error> requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_error_t, Error> void set_error(Error&& err) && noexcept { _complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err)); } void set_stopped() && noexcept requires _callable_with<_complete_t, Index, _state_t&, Rcvr&, set_stopped_t> { _complete(Index(), op->state, op->rcvr, set_stopped_t()); } auto get_env() const noexcept -> _env_type<Index, Sndr, Rcvr> { return _impls_for<_tag_t>::_get_env(Index(), op->state, op->rcvr); } _basic_state<Sndr, Rcvr>* op; }; inline constexpr struct _connect_all_t { private: template <class Sndr, class Rcvr, size_t... Is> static constexpr auto _mk_connect_fn(_basic_state<Sndr, Rcvr>* op, std::index_sequence<Is...>) noexcept { return [op]<sender_to<_basic_receiver<Sndr, Rcvr, _size_t<Is>>>... Child>(auto, auto&&, Child&&... child) noexcept((_has_nothrow_connect<Child, _basic_receiver<Sndr, Rcvr, _size_t<Is>>> &&...)) { return _tuple{connect(std::forward<Child>(child), _basic_receiver<Sndr, Rcvr, _size_t<Is>>{op})...}; }; } template <class Sndr, class Rcvr> using _connect_fn_t = decltype(_mk_connect_fn(static_cast<_basic_state<Sndr, Rcvr>*>(nullptr), _indices_for<Sndr>())); public: template <class Sndr, class Rcvr> requires _applicable_with<_connect_fn_t<Sndr, Rcvr>, Sndr> constexpr auto operator()(_basic_state<Sndr, Rcvr>* op, Sndr&& sndr) const noexcept(_nothrow_applicable_with<_connect_fn_t<Sndr, Rcvr>, Sndr>) -> decltype(auto) { return _apply(_mk_connect_fn(op, _indices_for<Sndr>()), sndr); } } _connect_all {}; template<class Sndr, class Rcvr> using _connect_all_result = _call_result_t<_connect_all_t, _basic_state<Sndr, Rcvr>*, Sndr>; template <class Sndr, class Rcvr> concept _can_connect = _callable_with<_get_state_t<Sndr>, Sndr, Rcvr&> && _callable_with<_connect_all_t, _basic_state<Sndr, Rcvr>*, Sndr>; template<class Sndr, class Rcvr> requires _can_connect<Sndr, Rcvr> struct _basic_operation : _basic_state<Sndr, Rcvr> { using operation_state_concept = operation_state_t; using _tag_t = tag_of_t<Sndr>; _connect_all_result<Sndr, Rcvr> _inner_ops; _basic_operation(Sndr&& sndr, Rcvr&& rcvr) : _basic_state<Sndr, Rcvr>(std::forward<Sndr>(sndr), std::move(rcvr)) , _inner_ops(_connect_all(this, std::forward<Sndr>(sndr))) {} void start() & noexcept { auto fn = [this](auto&... ops) noexcept { _impls_for<_tag_t>::_start(this->state, this->rcvr, ops...); }; _apply(fn, _inner_ops); } }; #define _CHECK_TYPES _impls_for<Tag>::template _check_types<Self, Env...> template <auto... Values> struct _value_list { static constexpr size_t size = sizeof...(Values); template <size_t I> static constexpr auto value = Values...[I]; }; template <class Tag> struct _basic_sender_base { using sender_concept = sender_t; template <size_t I, class Self> constexpr decltype(auto) get(this Self&& self) noexcept { if constexpr (I == 0) return auto(self.tag); else if constexpr (I == 1) return (std::forward<Self>(self).data); else return (std::forward<Self>(self).*std::remove_reference_t<Self>::_children::template value<I-2>); } template <class Self> decltype(auto) get_env(this const Self& self) noexcept { auto fn = [](auto, auto& data, auto&... child) { return _impls_for<Tag>::_get_attrs(data, child...); }; return _apply(fn, self); } template <receiver Rcvr, _can_connect<Rcvr> Self> auto connect(this Self&& self, Rcvr rcvr) noexcept(std::is_nothrow_constructible_v<_basic_operation<Self, Rcvr>, Self, Rcvr>) { return _basic_operation<Self, Rcvr>{std::forward<Self>(self), std::move(rcvr)}; } template<class Self, class... Env> requires requires { _CHECK_TYPES(); } static constexpr auto get_completion_signatures() { _CHECK_TYPES(); // don't try to compute the completion signatures if _check_types is not a // core constant expression. if constexpr (requires { requires _is_constant<(_CHECK_TYPES(), true)>; }) { return decltype(_impls_for<Tag>::template _get_complsigs<Self, Env...>())(); } else { return (_CHECK_TYPES(), invalid_completion_signature<struct HERE/**/>()); } } }; template<class Tag, class Data, class... Child> struct _basic_sender; template<class Tag, class Data> struct _basic_sender<Tag, Data> : _basic_sender_base<Tag> { [[no_unique_address]] Tag tag; Data data; using _children = _value_list<>; }; template<class Tag, class Data, class Child0> struct _basic_sender<Tag, Data, Child0> : _basic_sender_base<Tag> { [[no_unique_address]] Tag tag; Data data; Child0 child0; using _children = _value_list<&_basic_sender::child0>; }; template<class Tag, class Data, class Child0, class Child1> struct _basic_sender<Tag, Data, Child0, Child1> : _basic_sender_base<Tag> { [[no_unique_address]] Tag tag; Data data; Child0 child0; Child1 child1; using _children = _value_list<&_basic_sender::child0, &_basic_sender::child1>; }; template<class Tag, class Data, class Child0, class Child1, class Child2> struct _basic_sender<Tag, Data, Child0, Child1, Child2> : _basic_sender_base<Tag> { [[no_unique_address]] Tag tag; Data data; Child0 child0; Child1 child1; Child2 child2; using _children = _value_list<&_basic_sender::child0, &_basic_sender::child1, &_basic_sender::child2>; }; #undef _CHECK_TYPES template <class Tag, class Data, class... Child> constexpr size_t _structured_tuple_size<_basic_sender<Tag, Data, Child...>> = sizeof...(Child) + 2; template<class Tag, class Data = _none_such, class... Child> constexpr auto _make_sender(Tag tag, Data data, Child... child) { using Sender = _basic_sender<Tag, Data, Child...>; if constexpr (!dependent_sender<Sender>) (void) get_completion_signatures<Sender>(); return Sender{{}, tag, data, child...}; } struct _not_a_scheduler {}; // Pipeable sender adaptor CRTP base class template <const auto& Algorithm, size_t... ArgCount> struct _pipeable { static_assert(sizeof...(ArgCount) > 0); template <sender Sndr, class... As> requires ((sizeof...(As) == ArgCount) ||...) constexpr auto operator()(Sndr sndr, As... args) const { return sndr | Algorithm(args...); } template <class... As> requires ((sizeof...(As) == ArgCount) ||...) constexpr auto operator()(As... args) const { if constexpr (sizeof...(As) == 1) { return _closure{args...[0]}; } else { return _closure{_tuple{args...}}; } } private: template <sender Sndr> friend constexpr auto operator|(Sndr sndr, _pipeable) requires ((ArgCount == 0) ||...) { auto dom = _query_or_default(get_domain, sndr, default_domain()); return transform_sender(dom, _make_sender(Algorithm, {}, sndr)); } template <class Data> struct _closure { template <sender Sndr> friend constexpr auto operator|(Sndr sndr, _closure _self) { auto dom = _query_or_default(get_domain, sndr, default_domain()); return transform_sender(dom, _make_sender(Algorithm, std::move(_self.data_), sndr)); } Data data_; }; }; // sender algorithm: just template <const auto& Algorithm> struct IN_ALGORITHM; template <class Algorithm> struct IN_QUERY; template <> struct _impls_for<struct just_t> : _default_impls { static constexpr auto _start = [](auto& state, auto& rcvr) noexcept -> void { auto fn = [&](auto&... ts) noexcept -> void { set_value(std::move(rcvr), std::move(ts)...); }; _apply(fn, state); }; // Non-standard: template <class... Ts> using _value_completions = completion_signatures<set_value_t(Ts...)>; // Non-standard: template <class Sndr, class... Env> static constexpr auto _get_complsigs() { using Completions = std::remove_cvref_t<_data_type<Sndr>>::template _apply<_value_completions>; return Completions(); } }; inline constexpr struct just_t { template <class... Ts> constexpr auto operator()(Ts... ts) const { return _make_sender(just_t{}, _tuple{std::move(ts)...}); } } just{}; // sender algorithm: then extern const struct then_t then; template <> struct _impls_for<then_t> : _default_impls { static constexpr auto _complete = []<class Tag, class Fn, class... As>(auto, Fn& fn, auto& rcvr, Tag, As&&... args) noexcept { if constexpr (Tag() == set_value) { try { if constexpr (std::same_as<void, std::invoke_result_t<Fn, As...>>) { std::invoke(std::move(fn), std::forward<As>(args)...); set_value(std::move(rcvr)); } else { set_value(std::move(rcvr), std::invoke(std::move(fn), std::forward<As>(args)...)); } } catch(...) { if constexpr (!std::is_nothrow_invocable_v<Fn, As...>) { set_error(std::move(rcvr), std::current_exception()); } } } else { Tag()(std::move(rcvr), std::forward<As>(args)...); } }; // get_completion_signatures for the `then` sender transforms the // value completion signatures by reducing the value types with // the result of Fn when invoked with them. if the function is not // invocable with the given arguments, throw an exception with the // error information. template <class Fn> static constexpr auto transform_sig_fn = []<class... As>() { if constexpr (!std::invocable<Fn, As...>) { return invalid_completion_signature<IN_ALGORITHM<then>, struct WITH_FUNCTION(Fn), struct WITH_ARGUMENTS(As...)>( "The function passed to std::execution::then is not callable with the" " values sent by the predecessor sender."); } else { constexpr bool _nothrow = std::is_nothrow_invocable_v<Fn, As...>; return completion_signatures{value<std::invoke_result_t<Fn, As...>>} + eptr_completion_if<!_nothrow>; } }; // Non-standard: template <class Sndr, class... Env> static constexpr auto _get_complsigs() { using Fn = std::remove_cvref_t<_data_type<Sndr>>; auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); return transform_completion_signatures(cs, transform_sig_fn<Fn>); } template <class Sndr, class... Env> static constexpr void _check_types() { (void) _get_complsigs<Sndr, Env...>(); } }; inline constexpr struct then_t : _pipeable<then, 1> {} then {}; // sender algorithm: read_env extern const struct read_env_t read_env; template <> struct _impls_for<read_env_t> : _default_impls { static constexpr auto _start = [](auto query, auto& rcvr) noexcept -> void { try { set_value(std::move(rcvr), query(get_env(rcvr))); } catch(...) { if constexpr (!noexcept(query(get_env(rcvr)))) { set_error(std::move(rcvr), std::current_exception()); } } }; // Non-standard: template<class Sndr, class Env> static constexpr auto _get_complsigs() { using Query = std::decay_t<_data_type<Sndr>>; if constexpr (!_callable_with<Query, Env>) { return invalid_completion_signature<IN_ALGORITHM<read_env>, struct WITH_QUERY(Query), struct WITH_ENVIRONMENT(Env)>( "The receiver's environment does not have a value for the given query."); } else if constexpr (std::same_as<void, _call_result_t<Query, Env>>) { return invalid_completion_signature<IN_ALGORITHM<read_env>, struct WITH_QUERY(Query), struct WITH_ENVIRONMENT(Env)>( "The receiver's environment returned `void` for the given query."); } else { return completion_signatures<set_value_t(_call_result_t<Query, Env>)>(); } } template<class Sndr, class Env> static constexpr void _check_types() { (void) _get_complsigs<Sndr, Env>(); } }; inline constexpr struct read_env_t { template <class Query> constexpr auto operator()(Query query) const noexcept { return _make_sender(read_env, query); } } read_env {}; // let_value, let_error, and let_stopped sender adaptors: extern const struct let_value_t let_value; extern const struct let_error_t let_error; extern const struct let_stopped_t let_stopped; template <const auto& LetAlgorithm> struct _impls_for_let : _default_impls { template <class Fn, class... Env> static constexpr auto transform_sig_fn = []<class... As>() { if constexpr (!std::invocable<Fn, std::decay_t<As>&...>) { return invalid_completion_signature<IN_ALGORITHM<LetAlgorithm>, struct WITH_FUNCTION(Fn), struct WITH_ARGUMENTS(As...)>( "The function passed to std::execution::[let_value|let_error|let_stopped] is not callable with the" " values sent by the predecessor sender."); } else { using Result = std::invoke_result_t<Fn, std::decay_t<As>&...>; using Error = _sender_type_check_failure<IN_ALGORITHM<LetAlgorithm>, struct WITH_FUNCTION(Fn), struct WITH_ARGUMENTS(As...), struct WITH_RETURN_TYPE(Result), struct WITH_ENVIRONMENT(Env)...>; // BUGBUG: this assumes connect doesn't throw. :-/ constexpr bool Nothrow = _nothrow_decay_copyable<As...> && std::is_nothrow_invocable_v<Fn, std::decay_t<As>&...>; if constexpr (!sender<Result>) { return (throw Error("The function passed to std::execution::[let_value|let_error|let_stopped]" " does not return a sender when called with the values sent by the predecessor sender."), _completions_v<>); } else if constexpr (sizeof...(Env) == 1 && !sender_in<Result, Env...>) { return (throw Error("The function passed to std::execution::[let_value|let_error|let_stopped]" " returned a sender that cannot be used with the current execution environment."), _completions_v<>); } else if constexpr (!_decay_copyable<As...>) { return (throw Error("The value types produced by the predecessor sender cannot be decay " "copied into intermediate storage."), _completions_v<>); } else { return get_completion_signatures<Result, Env...>() + eptr_completion_if<!Nothrow>; } } }; // Non-standard: template <class Sndr, class... Env> static constexpr auto _get_complsigs() { using Fn = std::remove_cvref_t<_data_type<Sndr>>; auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); if constexpr (LetAlgorithm._set_tag == set_value) return transform_completion_signatures(cs, transform_sig_fn<Fn, Env...>, {}, {}); else if constexpr (LetAlgorithm._set_tag == set_error) return transform_completion_signatures(cs, {}, transform_sig_fn<Fn, Env...>, {}); else return transform_completion_signatures(cs, {}, {}, transform_sig_fn<Fn, Env...>); } template <class Sndr, class... Env> static constexpr void _check_types() { (void) _get_complsigs<Sndr, Env...>(); } }; template <> struct _impls_for<let_value_t> : _impls_for_let<let_value> {}; template <> struct _impls_for<let_error_t> : _impls_for_let<let_error> {}; template <> struct _impls_for<let_stopped_t> : _impls_for_let<let_stopped> {}; inline constexpr struct let_value_t : _pipeable<let_value, 1> { static constexpr const auto& _set_tag = set_value; } let_value {}; inline constexpr struct let_error_t : _pipeable<let_error, 1> { static constexpr const auto& _set_tag = set_error; } let_error {}; inline constexpr struct let_stopped_t : _pipeable<let_stopped, 1> { static constexpr const auto& _set_tag = set_stopped; } let_stopped {}; /// stopped_as_optional extern const struct stopped_as_optional_t stopped_as_optional; template <> struct _impls_for<stopped_as_optional_t> : _default_impls { template <class... Ts> using _value_sig = set_value_t(Ts...); template<class Sndr, class... Env> static constexpr void _check_types() { auto cs = get_completion_signatures<_child_type<Sndr>, Env...>(); using ValueSigs = _value_types<decltype(cs), _value_sig, completion_signatures>; static constexpr char what[] = "The predecessor sender of std::execution::stopped_as_optional" " must have exactly one value completion signatures with 1 or more values."; if constexpr (!requires { typename _single_sender_value_t<_child_type<Sndr>, Env...>; }) { (void) invalid_completion_signature<IN_ALGORITHM<stopped_as_optional>, struct WITH_VALUE_COMPLETIONS(ValueSigs)>(what); } else if constexpr (std::same_as<void, _single_sender_value_t<_child_type<Sndr>, Env...>>) { (void) invalid_completion_signature<IN_ALGORITHM<stopped_as_optional>, struct WITH_VALUE_COMPLETIONS(ValueSigs)>(what); } } // Non-standard: template<class Sndr, class... Env> static constexpr auto _get_complsigs() { using V = _single_sender_value_t<_child_type<Sndr>, Env...>; auto cs = get_completion_signatures<_child_type<Sndr>, _FWD_ENV_T<Env>...>(); return transform_completion_signatures(cs, []<class...>(){ return _completions_v<set_value_t(std::optional<V>)>; }, {}, [] { return _completions_v<set_value_t(std::optional<V>)>; }); } }; inline constexpr struct stopped_as_optional_t : _pipeable<stopped_as_optional, 0> { template <_sender_for<stopped_as_optional_t> Sndr, class Env> constexpr auto transform_sender(Sndr&& sndr, const Env& env) { if constexpr (!sender_in<_child_type<Sndr>, Env>) { return _not_a_sender<[]{ (void) get_completion_signatures<_child_type<Sndr>, Env>(); }>(); } else { auto&& [_, _, child] = sndr; using V = _single_sender_value_t<_child_type<Sndr>, Env>; return let_stopped( then(std::forward_like<Sndr>(child), []<class... Ts>(Ts&&... ts) noexcept(std::is_nothrow_constructible_v<V, Ts...>) { return std::optional<V>(std::in_place, std::forward<Ts>(ts)...); }), []() noexcept { return just(std::optional<V>()); }); } } } stopped_as_optional {}; // Some simple tests: template <class... Sigs> struct test_sender { using sender_concept = sender_t; using completion_signatures = ::completion_signatures<Sigs...>; }; template <class... Sigs> struct test_dependent_sender : sender_t { template <class, class Env> static constexpr auto get_completion_signatures() { return ::completion_signatures<Sigs...>(); } }; void test_stop_as_optional() { { auto sndr = just(42) | stopped_as_optional(); using Sndr = decltype(sndr); static_assert(sender_in<Sndr>); auto cs = get_completion_signatures<Sndr>(); static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); } { auto sndr = just(42, 3.14) | stopped_as_optional(); using Sndr = decltype(sndr); static_assert(sender_in<Sndr>); auto cs = get_completion_signatures<Sndr>(); static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<std::tuple<int, double>>)>>); } { test_sender<set_value_t(int), set_stopped_t()> s0; auto sndr = s0 | stopped_as_optional(); using Sndr = decltype(sndr); static_assert(sender_in<Sndr>); auto cs = get_completion_signatures<Sndr>(); static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); } { test_dependent_sender<set_value_t(int), set_stopped_t()> s0; auto sndr = s0 | stopped_as_optional(); using Sndr = decltype(sndr); static_assert(!sender_in<Sndr>); auto cs = get_completion_signatures<Sndr, env<>>(); static_assert(std::same_as<decltype(cs), completion_signatures<set_value_t(std::optional<int>)>>); } { auto sndr = read_env(get_domain) | stopped_as_optional(); using Sndr = decltype(sndr); static_assert(!sender_in<Sndr>); static_assert(!sender_in<Sndr, env<>>); // auto cs = get_completion_signatures<Sndr, env<>>(); } } struct sink_receiver : receiver_t { template <class... As> void set_value(As&&... args) noexcept { ((std::cout << "Done! Result: ") <<...<< args) << '\n'; } template <class Err> void set_error(Err&& err) noexcept { } void set_stopped() noexcept { } }; int main(int argc, char* argv[]) { auto op = connect(just(42, 43) | then([](int a, int b) {return a+b;}), sink_receiver{}); start(op); auto op2 = connect(std::suspend_never{}, sink_receiver{}); start(op2); { auto x = just() | let_value([]() noexcept { return just(0,0); }); using X = decltype(x); static_assert(sender<X>); static_assert(sender_in<X>); static_assert(!dependent_sender<X>); } auto x = just(argc, 0) | then([](int, int){return 0;}) | then([](int i){return "hello world";}); static_assert(!dependent_sender<decltype(x)>); auto [tag, fun, child] = just(0,0) | then([](int,int){}); #ifdef ERROR auto y = just(0, 0) | then([](int, int*){return 0;}) | then([](int i){return "hello world";}); #endif auto z = read_env(get_domain); auto a = read_env(get_domain) | then([](){}); // OK static_assert(sender<decltype(z)>); static_assert(dependent_sender<decltype(z)>); static_assert(!sender_in<decltype(z), int>); static_assert(sender_in<decltype(z), decltype(prop{get_domain, default_domain()})>); static_assert(!sender_in<decltype(z)>); }
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