Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
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#
Go
Haskell
HLSL
Hook
Hylo
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Nim
Objective-C
Objective-C++
OCaml
OpenCL C
Pascal
Pony
Python
Racket
Ruby
Rust
Snowball
Scala
Solidity
Spice
Swift
LLVM TableGen
Toit
TypeScript Native
V
Vala
Visual Basic
Zig
Javascript
GIMPLE
c++ source #2
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 13.1.0
ARM GCC 13.2.0
ARM GCC 13.2.0 (unknown-eabi)
ARM GCC 14.1.0
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 (WINE)
ARM msvc v19.10 (WINE)
ARM msvc v19.14 (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 13.1.0
ARM64 gcc 13.2.0
ARM64 gcc 14.1.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 (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 13.1.0
AVR gcc 13.2.0
AVR gcc 14.1.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 gcc 13.1.0
BPF gcc 13.2.0
BPF gcc trunk
EDG (experimental reflection)
EDG 6.5
EDG 6.5 (GNU mode gcc 13)
EDG 6.6
EDG 6.6 (GNU mode gcc 13)
FRC 2019
FRC 2020
FRC 2023
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)
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 14.1.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 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 14.1.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 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 14.1.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 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 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 13.1.0
SPARC LEON gcc 13.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 14.1.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 14.1.0
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 14.1.0
TI CL430 21.6.1
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
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 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 8.4.0 (2020r3)
Xtensa ESP32-S3 gcc 8.4.0 (2021r1)
Xtensa ESP32-S3 gcc 8.4.0 (2021r2)
arm64 msvc v19.28 VS16.9
arm64 msvc v19.29 VS16.10
arm64 msvc v19.29 VS16.11
arm64 msvc v19.30
arm64 msvc v19.31
arm64 msvc v19.32
arm64 msvc v19.33
arm64 msvc v19.34
arm64 msvc v19.35
arm64 msvc v19.36
arm64 msvc v19.37
arm64 msvc v19.38
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 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 9.0.0
armv8-a clang 9.0.1
ellcc 0.1.33
ellcc 0.1.34
ellcc 2017-07-16
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 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 14.1.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 gcc 11.2.0
mips gcc 12.1.0
mips gcc 12.2.0
mips gcc 12.3.0
mips gcc 13.1.0
mips gcc 13.2.0
mips gcc 14.1.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 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 14.1.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 gcc 11.2.0
mips64 gcc 12.1.0
mips64 gcc 12.2.0
mips64 gcc 12.3.0
mips64 gcc 13.1.0
mips64 gcc 13.2.0
mips64 gcc 14.1.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
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 gcc 12.1.0
mipsel gcc 12.2.0
mipsel gcc 12.3.0
mipsel gcc 13.1.0
mipsel gcc 13.2.0
mipsel gcc 14.1.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 13.1.0
power gcc 13.2.0
power gcc 14.1.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 13.1.0
power64 gcc 13.2.0
power64 gcc 14.1.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 13.1.0
power64le gcc 13.2.0
power64le gcc 14.1.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
s390x gcc 11.2.0
s390x gcc 12.1.0
s390x gcc 12.2.0
s390x gcc 12.3.0
s390x gcc 13.1.0
s390x gcc 13.2.0
s390x gcc 14.1.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 14.1.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (WINE)
x64 msvc v19.10 (WINE)
x64 msvc v19.14
x64 msvc v19.14 (WINE)
x64 msvc v19.15
x64 msvc v19.16
x64 msvc v19.20
x64 msvc v19.21
x64 msvc v19.22
x64 msvc v19.23
x64 msvc v19.24
x64 msvc v19.25
x64 msvc v19.26
x64 msvc v19.27
x64 msvc v19.28
x64 msvc v19.28 VS16.9
x64 msvc v19.29 VS16.10
x64 msvc v19.29 VS16.11
x64 msvc v19.30
x64 msvc v19.31
x64 msvc v19.32
x64 msvc v19.33
x64 msvc v19.34
x64 msvc v19.35
x64 msvc v19.36
x64 msvc v19.37
x64 msvc v19.38
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 (WINE)
x86 msvc v19.10 (WINE)
x86 msvc v19.14
x86 msvc v19.14 (WINE)
x86 msvc v19.15
x86 msvc v19.16
x86 msvc v19.20
x86 msvc v19.21
x86 msvc v19.22
x86 msvc v19.23
x86 msvc v19.24
x86 msvc v19.25
x86 msvc v19.26
x86 msvc v19.27
x86 msvc v19.28
x86 msvc v19.28 VS16.9
x86 msvc v19.29 VS16.10
x86 msvc v19.29 VS16.11
x86 msvc v19.30
x86 msvc v19.31
x86 msvc v19.32
x86 msvc v19.33
x86 msvc v19.34
x86 msvc v19.35
x86 msvc v19.36
x86 msvc v19.37
x86 msvc v19.38
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.3
x86-64 Zapcc 190308
x86-64 clang (amd-stg-open)
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 P2996)
x86-64 clang (experimental metaprogramming - P2632)
x86-64 clang (experimental pattern matching)
x86-64 clang (old concepts branch)
x86-64 clang (reflection)
x86-64 clang (resugar)
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 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 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 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.4
x86-64 gcc 10.5
x86-64 gcc 11.1
x86-64 gcc 11.2
x86-64 gcc 11.3
x86-64 gcc 11.4
x86-64 gcc 12.1
x86-64 gcc 12.2
x86-64 gcc 12.3
x86-64 gcc 13.1
x86-64 gcc 13.2
x86-64 gcc 14.1
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 (latest)
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
zig c++ 0.10.0
zig c++ 0.11.0
zig c++ 0.12.0
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 <concepts> #include <iostream> #include <numbers> #include <vector> // Shape // Первое, что нужно понимать. В С++ используется, если в привычных для скриптухи базвордах, структурная типизация. Либо утиная типизация. // Причина тому очень проста - ничего иное не работает. // предположим, что мы бы захотели использовать самый примитивный подход - номинативный. Но типы входящие разные, у них разные имена. // Мы хотим, что-бы функция принимала только Circle, Rectangle. // Для решения это проблемы тысячи лет существует самый простой подход. Мы создаём какую-то группу, называем её каким-то именем и далее Circle, Rectangle привязываем к ним. // Проблемы этого подхода разобраны тысячи раз. Причём, в том числе, самими же адептами раста при критики динамического полиморфизма в С++. // Свести их можно к одному. Всегда нужно привязываться к какой-то группе явно. Это не всегда возможно, либо не всегда удобно. Но самая жесть начинается тогда, одному и тому же типу нужно быть частью двух групп, но не во всём. Все эти группы плодятся бесконечно и в них можно запутаться. // Для решения создаются всякие элементры структурной типизации. Те же интерфейсы. Уходит проблема с именами, но приходит другая. Эти интерфесы плодятся бесконечно и рано или поздно они сводятся к одному/двум методам. // Просто пример. Есть функция, которой нужен метод area(), а другой нужен perimeter(). А третей обе. Сколько интерфейсов нужно создать? На каждую функцию свой? Либо использовать shape, но тогда мы не сможем передать объект только с area(). И все эти подходы рано или поздно начинают жертвовать обобщённостью ради упрощения. Поэтому никакого обобщённого программирования нигде и нет, кроме С++. // трейты - это по-сути теже интерфейсы с той же проблемой. Скорее это pure virtual class из С++. Они имеют как первую, так и вторую проблему. // Попытатки решения проблемы в рамках трейтов заключаются в следующем. Раздробить на максимально мелкие трейты(опять же, проблема никуда не уходит - она просто уменьшается). А уже комбинировать для каждой функции. // arg: Area + Perimeter // Но это пораждает новую проблему. // fa(arg: A) // fb(arg: B) // fc(arg: C) // f(arg: ?) { // fa(arg); // fb(arg); // fc(arg); // } //с каждым добавлением нового контекста использования arg - на него вешается всё больше требований. За этим постоянно нужно следить. // Поэтому с данным подходом так же не приходится говорить о каком-то обобщении. Его адепты так же ограничиваются примитивщиной, потому как что-то сложнее просто поломает и список трейтов станет бесконечным. // Именно поэтому решение одно - научить язык автоматически генерировать все эти интерфейсы для каждой функции. // Этот подход используется в С++. Почему он не используется в остальных языках? Всё очень просто - не осилили. // Очень часто можно слышать, что пропаганда рассказываем своим адептам о том, что какие-то ограничения к генериках нужны для какого-то урощения. Что это какой-то осознанный выбор. Нет. // Возьмём данную функцию // f(arg: ?) { // fa(arg); // fb(arg); // fc(arg); // } // Подобный автоматический вывод существует во всякой скритпухи типа хаскеля. Но там просто другой порядок типизации. Там не входящий аргумент типизирует функциюю, а функция сама типизирует входящий аргумент. Т.е. опять же нужно всё аннотировать трейтами(либо их аналогом), а уже потом будет что-то выведено. Необходимость всё аннотировать опять нас возвращает к той же проблеме. // К тому же, здесь можно выйти ещё на одну проблему. Сигнатуры никак не могут описать поведение чего-либо. Если у какого-то объекта существует метод x, то это не значит, что его тип подход. Если методы не генерик - никаких проблем. Но вот дальше всё та же проблема. // Нам нужны генерик-интерфейсы/трейты, но проблема всё та же. Методы в рамках интерфейса/трейта могут не зависеть друг от друга, но генерик-трейт/интерфейс параметризуется одним типом/набор типов сразу для всех мтеодов в рамках него. Опять всё нужно дробить. И так до бесконечности. // Но, всё это может существовать только в рамках примитивной системы типов. Зачастую в с криптухи она такая, но она недостаточная. Допустим, рано или поздно дойдёт до того, что параметризовать генерики нужно не только типами, но и значениями. А потом на базе этих значений нужно строить какую-то логику и корректность типа уже будет зависеть того, удовлетворяет ли конкретное значение типу, либо нет. // Допустим, если наш трейт/интерфейс можно параметризовать только чётными числами, то нужна поддержка какой-то логики. И она должна выполнятся на уровне тайпчека. Ничто из этого недоязычки не могут себе позволить. И этого там нет и никогда не будет. Оправдывают они это какой-то декларативностью и прочими базвордами. // В конечном итоге мы вынуждены отказаться от каких-либо интерфейсов/трейтов и прочих аннотаций. Мы вынуждены отказаться от какой-то проверки уровня сигнатур. Об этом я ещё расскажу, вернёмся к той функции: // f(arg: ?) { // fa(arg); // fb(arg); // fc(arg); // } // Почему раст(и прочие недоязычки) работают как работают. Они не могут себе позволить полноценный тайпчек. Поэтому им нужно каким-то образом тайпчекать тело функции относительно её сигнатуры, но сигнатуры генерик-функции ничего не знает о типе. Она не параметризована. Поэтому что? // Правильно, тайпчек начинает игнорировать передаваемый тип. Он начинает считать T за все типы одновременно, т.е. and от всех типов. А общее там только копирование и может ещё пару свойств. Именно поэтому этот мусор и ломается. // И решается эта проблема просто. Недоязычок заставляет своих адептов руками заного типизировать arg. А далее недоязычок просто сравнивает эти аннотации в T и функции, в которую он передаются. Если они совпадают - тайпчек проходит. Если нет - нет. // C++ работает не так. Если проще, то работает он следующим образом. Он пытается вывести типы всего, что используется. А далее рекурсивно тайпчекает/инстанцирует всё, что от них зависит. // Среди адептов скриптухи существует поверье, что С++ не тайпчекает шаблоны - это полная чушь. В целом здесь можно видеть весь уровень адептов, которые не могут даже проверить банальную вещь. //********************************** // Таким образом получается такая система, которой ненужны никакие аннотации, никакие интерфейсы. Если код собирается - он весь удовлетворяет друг другу. Адепты всякого мусора будут пытаться вас убедить, что какие-то концепты нужны в С++ для каких-то ограничений. Что какие-то ограничения нужны для чего-то. Нет. Они нужны скриптухи - С++ же может без них. И может максимально хорошо. Добиться подобного используя мусорные подходы в принципе невозможно. Почему я уже говорил. // Но, для чего же в С++ всё же нужны концепты? Ответ очень прост. В С++ есть такая штука как перегрузка. Подобного нет нигде, особенно в недоязычке типа раста. В другой скриптухи существует какая-то примитивный перегрузка, но это лишь жалкое подобие. // Поэтому, те проблемы, что есть в С++ - вообще не могут сравниваться с говнорастом. Их там нет просто потому, что он не может решать те задачи, в которых и возникают проблемы. // Дак вот, если упростить С++ работает так. Существует множество функций(не генерик, а отдельных. Причём каждая отедельный тоже может быть генерик). Язык не знает какая функция нужна в данный момент, поэтому существуют специальные правила выбора. Существуют простые правила подбора по типам, но для генерик-функций это не работает. // Поэтому генерик(шаблонную)-функцию(либо что-либо ещё. Но в говнрасте есть только функции - поэтому для доступности я говорю только о функциях) язык пытается инстанцировать. При инстанцировании может возникнуть ошибка. Но это не может восприниматься за ошибку, потому как существуют ещё функции, которые не проверены и которые могут подойти. // Поэтому существует специальное правило, которое говорит, что ошибка инстанцирования - не ошибка. Это то самое sfinae. И ошибка будет только тогда, когда ничего не пододойдёт. Либо подойдёт больше одной. // Но, это бы не вызывало проблем, но в данном случае у С++ есть одно ограничение. Они чисто уловное. С++ при поиске не инстанцирует тела функций - он инстанцирует только сигнатуры. И это ломает ту сказку, которую описывал я. И если тело инстанцировано с ошибкой - всё ломается. // Простой пример, который я уже несколько раз показывал. // auto f(auto x, auto y) { return x + y; } // auto f(auto x, auto y) { return x - y; } // Логически две эти функции не идентичны, ведь может существовать тип у котого есть +, но нет - и наоборот. Но из-за того, что тела не учитываются - они идентичны. // Мы можем написать так: auto f(auto x, auto y) -> decltype(x + y) { return x + y; } auto f(auto x, auto y) -> decltype(x - y) { return x - y; } // Это работает очень просто. Типы x и y неизвестны, а значит сигнатуру инстанцировать нельзя. И проверить нельзя. Но сигнатуры разные. // Всякие адепты недоязычков не понимания зачем это нужно и почему так пишут - пытаются это повторять, в жалких нелепых потугах. Это можно часто увидеть. // Таким образом и работают всякие enable_if. Они выносят условия из функции на уровень сигнатуры. // Проблема в том, что если заставить язык инстанцировать тела при каждом разрешении перегрузки - это очень сильно всё замедлит. Но сам язык это может даже сейчас. // И первостепенная задача концептов - это именно решение этой проблемы. Нельзя разрешить инстанцировать всё, поэтому концепты - это такие куски, которые уже инстанцируются(вместо тел). // Расширить сигнатуру недостаточно. Мы не хотим писать для каждой сигнатуры правила заного. Поэтому они выносятся отдельно. // Так же концепты можно использовать для ручного прописывания ограничений. Почти никогда этого ненужно делать - ошибка возникает всегда рядом с проблемой и всегда понятно как её решать. Но бывают ситуации, когда хочется инкапсулировать внутреннюю логику внутри какого-то условного модуля. Допустим, я хочу что-бы моя либа просто писала "не удовлетворяет", а не писала ненужны подробности в кишках. // Обычно всякие сектанты выдают это за назначение концептов, но потуги эти ничего не стоят. Они ничего не знают о С++. Причём даже иногда и адекватные адепты С++ говорят это, но в основном это связано с тем, что это доступное рядовому обывателю назначение. А рядовые адепты С++ такие же обыватели с такими же представления, поэтому они просто несут то, что доступно им. // ******** // Теперь немного про концепты. Хотя можно прочитать и на cppreference, но похоже все этого делать лень. Потому как мало кто понимает что это вообще такое. // Я объясню кратко. struct a {}; struct b {}; // фундементальное - это requires - это тот самый enable_if на уровне языка. auto f(auto x) requires(std::is_same_v<decltype(x), a>) {} auto f(auto x) requires std::is_same_v<decltype(x), b> {} // это некое условие, которое отвечает за то - существует ли эта функция или нет. Ничего более. // так же сами концепты. Это по-сути шаблонный bool с некоторым уникальным свойством - он концепт. // Т.е. его можно использова для аннотирования auto, либо typename. Его параметризация произойдёт автоматически. Тем типом, что выведено auto, либо что пришло как аргумент в typename template<typename T> concept all = true;// свойство шаблоного була + свойства концепта. bool x = all<int>; all auto xx = false; template<all T> constexpr bool allb = true; auto xxx = allb<int>; // Если в результате инстанцирования концепта получается true - он ничего не делает. Есть false - происходит ошибка подстановки типа. template<typename T> concept not_all = false;// свойство шаблоного була + свойства концепта. bool not_x = not_all<int>; // not_all auto not_xx = false;//ошибка template<not_all T> constexpr bool not_allb = true; // auto not_xxx = not_allb<int>;//так же // Так же существует конструкция requires constexpr bool t0 = requires(int x) {//в качестве параметров описываются используемые в рамках конструкици объекты. // они существуют магическим образом с тем типом, с которым их определили. Они не создаются(т.е. конструкторы не вызываются)/не умирают. //здесь описание требования/ограничения requires true;//тот самый requires как в сигнатуре. Местный static_assert typename a;//тоже самое для типов. x + x;//произвольные выражения. Если они корректны - всё хорошо. Нет - requires возвращает сразу false {x + x} -> all;//тоже самое, только результат выражения проверяется другим концептом. //если все требования выше соблюдены - возвращается true. }; // Пример выше мы можем записатьт так: auto f(auto x, auto y) requires requires { x + y;} { return x + y; } auto f(auto x, auto y) requires requires { x - y;} { return x - y; } // Т.е. концепты в С++ вообще никак не соотносятся со всякой нелепой скриптухой типа раста. И когда какой-то адепт говорит о том, что это как-то связано - // он не совсем вменяем. Соотносится она как-то с мусорной скриптухой только тем, что т.к. это произвольная логика - в рамках неё можно реализовать // и не нелепые огрызки, что существуют в скриптухе. // ***************** // Теперь разберу базовые потуги сектантов оправдать нелпое существование своих недоязычков. // https://github.com/contextfreecode/static-dynamic-generics/blob/master/shapes.cpp - вот типичный потуги сектант. // Эти потуги на 99% состоят из вранья, манипуляций. Всё это имеет природу ту, что рядовый наблюдатель не разбирается в теме настолько, // что-бы идентифицировать эти манипуляции. И пропаганда шаг за шагом внушает необходимые установки наблюдателю. // Ситуация - недоязычок имеет ограничения, которые требуют создания всякого мусора и описания ограничений. И тут в С++ появляются механизмы // для удобного(это важно. Эти механизмы были тогда, когда раста не существовало и его адепты по горшкам седели) описания ограничений. // И у сектантов родился план. Убедить наблюдателя в том, что описание ограничения является необходимостью(а не ограничением недоязычка). // начнём с динамического диспатча. struct DynShape { // virtual auto area() const -> double {return 0;} // = 0; // virtual auto perimeter() const -> double {return 0;} // = 0; // И сразу видим то, как сектант пытается специально засирать С++-код. // Первое, сектант использует auto и стрелочки, которые ненужны. Мотивацию убого я опишу ниже. // Второе - const. Сектант использует перегрузку функций по this. Здесь можно оправдать сектанта, потому базовые методы требуют не контантного this // Так же, сектант определяет тела функций, хотя в его скриптухи он определяет только сигнатуры. Значит поведения по умолчанию в его скриптухи нет, а значит тел быть не должно // Причём он(либо ему кто-то сказал) написал = 0;, но в любом случае это мусорная манипуляция. // Перепишем этот мусор правильно. virtual double area() const = 0; virtual double perimeter() const = 0; }; // auto area_per_perimeter(const Shape auto& shape) -> double { // return shape.area() / shape.perimeter(); // } // Здесь мы опять видим нелепые потуги сектанта. И так же видим мотивацию убого замусоривать С++-код. // Дело в том, что его недоязычке нету вывода типа возврата, поэтому сектант боится, что наблюдатель это увидит. //перепишем этот мусор нормально auto area_per_perimeter(const auto & shape) { return shape.area() / shape.perimeter(); }; // Как я уже объяснял, у убогих нету перегрузки. А значит Никакие концепты здесь ненужны. Но сектант пытается манипулятивно убедить вас в обратном. // struct CircleData { // double radius; // }; // Далее мы видим самое убогое, что сектант мог родить. В связи с тем, что в его недоязычки существуют только ворованные структуры из си и классов не существует. // он попытался тоже самое сделать и для C++, хотя это ненужно, очевидно. // struct Circle: CircleData, virtual DynShape { // Circle(CircleData data): CircleData(data) {} // auto area() const -> double override { // return std::numbers::pi * radius * radius; // } // auto perimeter() const -> double override { // return std::numbers::pi * radius * 2; // } // }; // А здесь мы видим том, что сектант не знает что такое витуальное наследование и пытается как можно больше раздуть С++-код кейвордами. // Так же мы видим как сектант пытается описывать какие-то конструкторы, которые здесь ненужны. В убогой скриптухе есть только поды и никакого управления видимостью полей/инициализации объекта нет. // Далее сектант снова пытается впаривать нам вывод руками, да ещё и через стрелку. А так же использует форфан кейворды. Никакого override в его скриптухи нет - зачем он его использует? // Именно для этого сектант и добавил тело в базовый класс, потому как с чисто виртуальными методами код не соберётся и всё будет как в его недоязычке. // но использовал переопределение методов, чего нет в его убогой скриптухи. // Перепишем нормально: struct Circle { auto area() const { return std::numbers::pi * radius * radius; } auto perimeter() const { return std::numbers::pi * radius * 2; } double radius; }; // тоже самое с Rectangle struct Rectangle { auto area() const { return width * height; } auto perimeter() const { return 2 * (width + height); } double width, height; }; // Сектант там ниже пытался родить какую-то херню - на С++ это так не работает. Это ещё один момент, который нужно объяснить. // Дело в том, что на самом деле во всей это скриптухи такого понятия как статический диспатч нет. Там везде и всюду vtable. // А то, что они называют статическим диспатчем - это условный девиртуализатор на уровне кодогена. // Т.е. при кодогенарации может случиться два случая: // f<dyn T>(x: T) { x.f(); } // f<T>(x: T) { x.f(); } // На уровне языка эти два случая равнозначны. Именно поэтому сетканты всегда показывают эту их идентичность. // Но проблема в том, что имея маня-динамический диспатч недоязычок не имеет семантики статического диспатча. // дак вот, когда кодоген увидит второй случай - он подставит там конечный тип. И таким образом вызов произойдёт не через vtable // Далее истории нас отсылает к проблемам наследования. Это уже тысячи раз обсуждалось, но кратко. // Из-за мусорной парадигмы наследования каждый предок должен существовать сам по себе. Технически нам ничего не мешало бы написать что-то типа: // struct: DynShape, Circle{} - но мы не можем. // В раст-скриптухи это реализовано именно как-то так. На этом базируется вся критика подхода С++(хотя эта критика существовала до того, как адепты раста родились). #include <functional> struct dyn_shape { dyn_shape(auto & obj): area([&] { return obj.area(); }), perimeter([&] { return obj.perimeter(); }) {}; const std::function<double()> area; const std::function<double()> perimeter; }; // правда такой подход, что бы о нём не говорила адепты, такое же говно как и наследование. Проблема фатальна - он затирает типы. В скриптухе на типы покласть, но не в С++. // По этой причине в С++ везде где можно используется статический диспатч, а где нельзя - почти везде он заменяется на вариант, который типы не затирает. У него так же есть своя проблема - нужно описывать руками все варианты. int main() {// здесь сектант опять пытался замусорить код. // auto c1 = Circle({.radius = 1.0}); // Сектант опять пытается делать вид, что С++ такой же мусор как его недоязычок. // дело в том, что в его скриптухе есть только ворованные из C списки инициализации. Причём даже множество фич пока воровали потеряли. // В отличии от паскалятинке в С++ нету let-костыля. T x; - в С++, let x: T; - в мусоре. // В паскалятине непонятно где использование, а где инициализация. Поэтому там нужен либо лет-костыль, либо специальный оператор дерьма(типа :=) Circle c1{.radius = 1};// Здесь так же понятно почему он родил дополнительный класс. И почему использует (). // В сектантских методичках написано, что недоязычок изобрёл супер-новый способ инициализации. Вы не должны осознать то, что дефолтный способ инициализации из си. И что он есть и в С++. Circle c2{.radius = 2.5}; // std::cout << "c1: " << c1.area() << ", " << c1.perimeter() << "\n"; // Здесь сектант пытается юзать стримы из C++ показывая ворованный printf из си/пистона. Причём даже его сектанты не смогли родить и родили через убогий, тормозящий макрос. // Где printf? // auto circles = std::vector<Circle>{c1, c2}; // Здесь сектант опять пытается показывать свой мусорный недомакрос(опять же, эта скриптуха не может даже сама инициализировать вектор. Потому как нет ни вариадиков, ни initializer_list) std::vector circles{c1, c2}; Rectangle r1{.width = 3.0, .height = 3.0}; Rectangle r2{.width = 4.0, .height = 6.0}; // auto shapes = std::vector<DynShape*>{&c1, &c2, &r1, &r2}; // Здесь сектант опять пытается эмулировать всё недоязычок в С++, при этом используя указатели, которые здесь ненужны. std::vector<dyn_shape> shapes{c1, c2, r1, r2}; // И теперь мы можем заметить ещё один момент. Сектант использовал хоть один константный this? Получается, что перегрузку он использовал форфан. for(auto shape: shapes) std::cerr << area_per_perimeter(shape) << std::endl; } //Так же напишу про потуги в комментах struct MyType { int d; }; template<typename T> concept ord = requires(T x) { x < x; }; void max_value(std::vector<ord auto> &&) {} auto operator<(MyType, MyType) { return true; } void test0() { max_value(std::vector<MyType>{{1}, {2}, {3}}); } // Опять же, не забываем. Что всё это нужно для перегрузок. В данном случае, когда max_value одно, ничего этого ненужно. И самое смешное то, что в недоязычке типа говнораста никаких перегрузок и не появится. // Модули, концепты (считай трейты), ranges, fmt. Это всё уже 5 лет как есть в расте. Почему бы и не из него. // Разберу потугу обезьяны выше. // Модули - в говнрасте нет никаких модулей подобных С++. Эти модули там были и 5 лет назад. Никаких модули С++ ненужны, поэтому они валяются на помойке уже более 10 лет(тогда никакого раста не было, а данный адепт под партой). И самое важное, модули есть в каждом недоязычке. Каком образом обезьяна выдаёт примитивную херню за что-то уникальное недоязычку? // Концепты - что такое концепты я объяснил выше. Здесь мы видим в очередной раз то, что обезьяна ничего не знает о теме. Трейты в этой скриптухи примитивная херня, концепты ни в какой вселенной трейтами не являются. // ranges - этим ренжам лет больше, чем говнорасту. И уж тем более более пяти лет. К тому же, обезьяна врёт. Никакие ренжей в недоязычке нет даже близко. Их просто на подобном говне реализовать нельзя. Кому интересна тема - https://habr.com/ru/post/482318/#comment_21072142 - типичный пример, где я в дерьме убогих валял. // fmt - никакого отношения к говнорасту не имеет. И что самое интересное, в этом мусоре не осилил даже ворованный printf из си реализовать. Там это убогий макрос. Так же, этому fmt лет больше, чем говнорасту. Так же, как всем известно - printf создан си. Обозначение объектов через {} - тоже. Огрызок из говнораста ворован из питона. // В очередной раз видим то, что обезьяна просто несёт рандомную херню. Причём это именно обезьяна, потому как человек как минимум осилит зайти на гитхаб и посмотреть когда были созданы текущие реализации ренжей/fmt.
Become a Patron
Sponsor on GitHub
Donate via PayPal
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
About the author
Statistics
Changelog
Version tree