Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3b07015
first implementation
Tsche Apr 20, 2024
ddb963a
refactor
Tsche Apr 29, 2024
8c50541
fix CI scripts
Tsche Apr 29, 2024
652d336
fix lifetime tests
Tsche Apr 29, 2024
ec1530c
yes to all when installing dependencies
Tsche Apr 29, 2024
0ba2f40
help conan
Tsche Apr 29, 2024
3bfa54e
try fixing conan profile
Tsche Apr 29, 2024
6b641ea
visit test, variant refactor
Tsche May 1, 2024
9b055ee
refactor InvertedStorage
Tsche May 1, 2024
709c871
macro visit
Tsche May 3, 2024
6bbe0b2
benchmark improvements, implement macro visit benchmark
Tsche May 6, 2024
c9149ff
implement macro visit
Tsche May 8, 2024
b7f3232
Rerun codegen
May 8, 2024
fdfb1fd
Merge pull request #3 from Tsche/codegen/uncommitted
Tsche May 8, 2024
137c0b2
converting constructor
Tsche May 10, 2024
27f26e7
refactor impl::Variant, require `alternatives` alias for storage types
Tsche May 11, 2024
99c1cae
implement proxy storage, restructure
Tsche May 14, 2024
116f31f
rename alias in tests
Tsche May 14, 2024
a7e73d7
ensure trivial destructors are used in generated unions
Tsche May 15, 2024
b02ad07
triviality, opting out of special members if alternative types don't …
Tsche May 15, 2024
d4863ed
fix converting contructor
Tsche May 15, 2024
bb9145f
noexcept specifiers on variant constructors
Tsche May 15, 2024
2eff438
experimental padding reduction, some documentation
Tsche May 15, 2024
41fbf83
Rerun codegen
May 15, 2024
e81abdd
Merge pull request #4 from Tsche/codegen/uncommitted
Tsche May 16, 2024
2ca5406
multi variant visitation
Tsche May 24, 2024
eecbfca
zero variant visitation, amalgamate tool, fix fptr_array visit
Tsche May 24, 2024
7f20714
remove commented out code
Tsche May 24, 2024
3054c20
optimize type list
Tsche May 25, 2024
9cd4910
helper tests
Tsche May 25, 2024
62d2ef9
fix variant_size<variant<>>
Tsche May 25, 2024
5f65fd4
holds_alternative implementation
Tsche May 25, 2024
f1740e0
more work on tests
Tsche May 25, 2024
440da5b
add readme
Tsche May 25, 2024
28dfe28
add more tests
Tsche Jun 9, 2024
9ad9a53
clean up tests
Tsche Jun 9, 2024
5fc36d2
run tests against all kinds of variants
Tsche Jun 9, 2024
fa8f02d
remove nodiscard from functions returning void
Tsche Jun 9, 2024
9c3de0c
cleanup
Tsche Jun 9, 2024
c7cf9b8
remove doodle
Tsche Jun 10, 2024
2d2f31a
implement hashing
Tsche Jun 10, 2024
bf46f6f
implement visit<R>
Tsche Jun 21, 2024
b01c203
relops, partial swap
Tsche Jul 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BasedOnStyle: Chromium
IndentWidth: 2
ColumnLimit: 120

AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowAllArgumentsOnNextLine: false
SortIncludes: false

# Available starting from clang_format 18
# AllowShortCompoundRequirementOnASingleLine: true

AccessModifierOffset: '-2'
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Left
AlignTrailingComments: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
BinPackArguments: true
BinPackParameters: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
BreakStringLiterals: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IndentPPDirectives: AfterHash
SortUsingDeclarations: true
25 changes: 25 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
Checks: "*,
-abseil-*,
-altera-*,
-android-*,
-fuchsia-*,
-google-*,
-llvm*,
-modernize-use-trailing-return-type,
-zircon-*,
-readability-else-after-return,
-readability-static-accessed-through-instance,
-readability-avoid-const-params-in-decls,
-cppcoreguidelines-non-private-member-variables-in-classes,
-misc-non-private-member-variables-in-classes,
-hicpp-named-parameter,
-clang-diagnostic-unknown-attributes,
-readability-named-parameter,
-*-avoid-c-arrays,
-*-magic-numbers,
-*-union-access
"
WarningsAsErrors: ''
HeaderFilterRegex: ''
FormatStyle: none
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Y:\che\palliate codebase based on best match to current usage at 16/01/2022
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference

root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf

[*.py]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.h linguist-language=cpp

* text=auto eol=lf
11 changes: 11 additions & 0 deletions .github/conan.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[settings]
arch=x86_64
compiler=gcc
compiler.cppstd=23
compiler.libcxx=libstdc++11
compiler.version=13
os=Linux

[buildenv]
CC=/usr/bin/gcc
CXX=/usr/bin/g++
41 changes: 41 additions & 0 deletions .github/workflows/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Run codegen

on:
push:
branches: ["master", "develop"]
workflow_dispatch:

# only run one at a time, discard unfinished runs
concurrency:
group: "codegen"
cancel-in-progress: true

jobs:
codegen:
permissions:
contents: write
pull-requests: write

runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup palgen
uses: palliate/palgen@master
with:
run: false
requirements: tools/requirements.txt

- name: Run codegen
run: palgen --debug codegen

- name: PR changes
uses: peter-evans/create-pull-request@v6
with:
commit-message: Rerun codegen
author: Codegen Bot <[email protected]>
branch: codegen/uncommitted
delete-branch: true
title: Codegen update
draft: false
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Tests

on:
push:
branches: ["master", "develop"]
pull_request:
branches: ["master"]
workflow_dispatch:

jobs:
run-tests:
runs-on: ubuntu-latest
container: fedora:latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install dependencies
run: dnf -y install conan

- name: Set up Conan profile
run: |
conan profile detect
cp .github/conan.profile `conan profile path default`

- uses: actions/checkout@v3
- name: Build
run: conan build . --build=missing -o sanitizers=True -s build_type=Debug

- name: Test Package
run: |
conan editable add .
conan test test_package slo/0.1 -s build_type=Debug

- name: Test
run: ./build/Debug/slo_test
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,21 @@
*.out
*.app

# Python
*.pyc
**/__pycache__/

# IDE files
.vscode

# Build files
**/build/

# misc
**/compile_commands.json
**/scratchpad*.*
**/CMakeUserPresets.json

# Caches
.cache
.pytest_cache
79 changes: 79 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.15)
include(cmake/warnings.cmake)

project(slo CXX)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_library(slo INTERFACE)

set_property(TARGET slo PROPERTY CXX_STANDARD 23)

target_include_directories(slo INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)


option(BUILD_TESTING "Enable tests" ON)
option(ENABLE_SANITIZERS "Enable asan and ubsan" OFF)
option(ENABLE_COVERAGE "Enable coverage instrumentation" OFF)
option(ENABLE_BENCHMARK "Enable benchmarks" OFF)

if(NOT BUILD_TESTING STREQUAL OFF)
message(STATUS "Building unit tests")

enable_testing()
add_executable(slo_test "")

enable_warnings(slo_test)

find_package(GTest REQUIRED)
target_link_libraries(slo_test PRIVATE slo)
target_link_libraries(slo_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock)

include(GoogleTest)
gtest_discover_tests(slo_test)

add_subdirectory(test)

if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(slo_test PRIVATE -g -O0 --coverage -fprofile-arcs -ftest-coverage -fprofile-abs-path)
target_link_libraries(slo_test PRIVATE gcov)
message(STATUS "Instrumenting for coverage")
else()
message(FATAL_ERROR "Currently only GCC is supported for coverage instrumentation.")
endif()
endif()

if(ENABLE_SANITIZERS AND WIN32)
message(STATUS "Enabling sanitizers")
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(slo_test INTERFACE -fsanitize=address)
target_link_options(slo_test INTERFACE -fsanitize=address)
endif()
elseif(ENABLE_SANITIZERS)
message(STATUS "Enabling sanitizers")
target_compile_options(slo_test INTERFACE -fsanitize=address,undefined)
target_link_options(slo_test INTERFACE -fsanitize=address,undefined)
endif()
endif()

if (ENABLE_BENCHMARK)
message(STATUS "Building runtime benchmarks")
add_executable(slo_benchmark "")
add_subdirectory(benchmark)
find_package(benchmark REQUIRED)
target_link_libraries(slo_benchmark PRIVATE benchmark::benchmark_main)
target_link_libraries(slo_benchmark PRIVATE slo)
endif()

## headers
set_target_properties(slo PROPERTIES PUBLIC_HEADER "include/slo")
install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/
DESTINATION include)

## binaries
install(TARGETS ${TARGET}
ARCHIVE DESTINATION lib # Windows import libraries (.lib)
RUNTIME DESTINATION bin # Windows DLLs
LIBRARY DESTINATION lib) # shared libraries (ie Linux .so)
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
# variant
# `slo::variant` - a fast variant

This C++23 library implements various tricks to speed up compile time of a variant type. While there are some slight differences, this library aims to fulfill most of the requirements specified in [[variant]](https://standards.pydong.org/c++23/variant). This was initially written as reference implementation for the techniques discussed in [a deep-dive blog post about unions](https://pydong.org/posts/implementing-variant/).

## Differences
### Storage
A variant is essentially just a record of a sum type and a discriminator. The most natural way to implement this in C++ is by defining variant as a non-union class-type that has two members: the actual union type and some small integer as discriminator. Unfortunately this can introduce additional, undesired padding.

However, if all alternative types are standard-layout types and the compiler has C++26 `std::is_within_lifetime` (or an intrinsic that can be used to implement it) we can potentially do a little better. If those conditions are met `slo::variant` will automatically prefer the inverted storage strategy. Essentially it works by wrapping every alternative in a struct type that has the tag as first data member - outside of constant evaluated context the tag can be inspected through any alternative (even inactive ones!) since they all share the same common initial sequence. Since we can't just access the tag through an inactive member In constant evaluated context, we need to do a bit more mental gymnastics. To retrieve the tag we must find the currently active alternative - `std::is_within_lifetime` can help with that. Once we have found the active member, we can access the tag through it.

### Underlying union layout
To generate the underlying union from a list of types, most implementations opt to define it as a recursive union type. Since this recursive type must be walked for a lot of operations (notably `std::get` and the in-place constructors), its recursion depth is a major contributor to bad compile times. Some libraries such as boost:variant2 alleviate that issue by partially specializing the recursive union for a bunch of alternatives at once if possible. This does flatten the recursion depth quite a bit, but it is still linear.

To get rid of crazy recursion depth with very large variants, `slo::Variant` switches strategy if instantiated with a lot of alternatives. The alternative strategy uses a complete binary tree layout for the underlying union instead. The trade-off is essentially a little bit of time to generate the tree once template gets instantiated for much quicker and also more consistently quick to compile `get`, in-place constructors and so on.

### Wrapping existing unions
Sometimes for some reason you already have a bare union. To allow you to use it safely, like you could've if it was a variant, the `slo::Union` alias template can be used to generate a variant from it. The `slo::Union` template takes pointers to data members instead of types - so instead of `slo::variant<int, char>` you would write `slo::Union<&Foo::member_1, &Foo::member_2>`.

The interesting thing about wrapping a bare union like this is that we now have a flat union type. None of the recursion worries matter anymore and as an added bonus we can (optionally) attempt to hide the discriminator in tail padding of the union member.

### Visitation
`slo::visit` can use one of 3 visitation strategies
- Generate a sufficiently large switch with macros, discard all unneeded cases. The Microsoft STL way.
- Trigger an optimization pass in GCC/Clang to turn a fold expression into a switch.
- Generate an array of pointers to generated dispatcher functions

The latter is the fall-back strategy. This is the only strategy that requires an indirect call. Interestingly at the time of writing libstdc++ and libc++ both _always_ generate an array of function pointers when visiting multiple variants - even if the total amount of possible states is very low. `slo::visit` does not - it will also generate jump tables when visiting multiple variants.


## Usage
TODO


## FAQ
### Why `slo`? Isn't this supposed to be fast?
**S**tandard **L**ibrary **O**ptimizations. Also turns out not many people use the `slo` namespace in their (public) projects.

## License

`slo::variant` is provided under the [MIT License](LICENSE). Feel free to use and modify it in your projects. The techniques used in this library are explained in the aforementioned blog post, which is published under the [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.


## Contributing

If you'd like to contribute to `slo::variant`, please fork the repository, make your changes, and submit a pull request. Contributions are highly welcomed - including but not limited to bug reports, bug fixes, new features, and improvements.


## Contact

For questions or issues related to this library, please [open an issue](https://github.com/tsche/variant/issues).
Loading
Loading