46 Commits

Author SHA1 Message Date
c6e86604be Implement Bazel Clang toolchain 2025-07-10 23:10:06 +02:00
00ea14788f Fix a bunch of warnings 2025-07-06 17:05:36 +02:00
bcdad5b876 Add numbers parsing 2025-07-04 20:44:04 +02:00
cca2e26724 Add integer traits 2025-07-04 18:22:03 +02:00
195f20ff17 Add SparseHandlePool 2025-07-03 18:35:25 +02:00
a78e24e9eb Add DenseHandlePool 2025-07-03 18:34:45 +02:00
43ab95880d Add pop method to chunked_buffer 2025-06-26 18:25:00 +02:00
47c6677405 Add IndexPool 2025-06-26 12:37:02 +02:00
92f908ee1b Add index_pool_handle 2025-06-18 00:05:33 +02:00
30237bb78f Add index_pool_config 2025-06-18 00:05:33 +02:00
8f59f113e8 Fix test execution after a fail 2025-06-18 00:05:33 +02:00
afb237c513 Add smallest_unsigned_integer_type_for_width 2025-06-14 12:56:11 +02:00
19e2164441 Use the new Bazel registry 2025-06-04 21:59:48 +02:00
b8a87223bb Add compile-time configuration for build settings 2025-05-26 22:38:04 +02:00
a1db1cd9e2 Implement chunked_buffer 2025-05-26 00:48:06 +02:00
54b95b1662 Buffer type has to be moveable, always 2025-05-17 23:01:51 +02:00
e7e7023340 Fix build on Linux 2025-05-16 22:59:09 +02:00
f7a2699ac0 Add and use copy_const_t 2025-05-14 00:41:51 +02:00
088e03708a Add array 2025-05-14 00:41:51 +02:00
5bca42b049 Version 0.4.0 2025-05-08 00:16:26 +02:00
e1ba7dd7a9 Add enum underlying utilities 2025-05-08 00:15:26 +02:00
837f696971 Update some Bazel rules 2025-04-11 19:57:37 +02:00
afbfd0e781 Add numeric library 2025-04-03 23:27:39 +02:00
0776012d09 Add bit library 2025-04-03 00:34:54 +02:00
4f8cbd442a Add invoke_r & co 2025-03-26 21:52:00 +01:00
2d309a2cff Separate allocator from memory 2025-03-26 21:35:18 +01:00
f19d93a69a Improve implementation of invoke 2025-03-26 18:54:54 +01:00
e034efe8bd Add member pointer stuff to meta lib 2025-03-26 18:54:53 +01:00
2c457c4275 Add address_of 2025-03-26 18:54:49 +01:00
95598e24e1 Add missing license headers 2025-03-22 19:36:26 +01:00
99c8ae9530 Enable platform specific config 2025-03-22 19:31:09 +01:00
781877bd26 Add function_ref 2025-03-22 19:24:40 +01:00
c692909ff3 Add function 2025-03-21 23:46:39 +01:00
a665c590d5 Add decay 2025-03-20 19:29:08 +01:00
7023c0cc9a Use launder to access buffer SSO storage 2025-03-18 23:42:30 +01:00
4884b59433 Make status implementation more correct wrt type punning 2025-03-18 22:39:35 +01:00
a7475b6af2 Remove redundant methods in hash_map 2025-03-14 18:59:32 +01:00
8b6f57dc6a Pass string_view, span, and iterators by value 2025-03-14 18:57:02 +01:00
d1bb5a83f6 Use inheritance for string implementation 2025-03-14 18:56:58 +01:00
4630cb5237 Add missing is_empty methods 2025-03-14 18:53:37 +01:00
dce2c187ec Upgrade to Clang 20.1.0 2025-03-14 18:53:35 +01:00
cbade33906 Some more shit 2025-03-12 22:32:38 +01:00
c8b73031d8 Remove convertible_from 2025-03-12 00:08:18 +01:00
af4e29f8c0 Remake deref 2025-03-11 23:51:51 +01:00
636882316b Some work on clang-tidy-ing things up 2025-03-07 00:00:43 +01:00
f0cccbe328 Replace ASL_MOVE, ASL_FWD, and ASL_FWD_LIKE by their std:: equivalent
This is because some compiler stuff and diagnostics tools rely on those
symboles being what they are.
2025-03-06 22:56:56 +01:00
107 changed files with 10815 additions and 897 deletions

View File

@ -1,18 +1,14 @@
common --registry=https://bcr.bazel.build/
common --registry=https://bazel.stevenlr.com/registry/
startup --windows_enable_symlinks
build:windows --enable_runfiles=true
build --enable_platform_specific_config
build --build_python_zip=false
build:windows --extra_execution_platforms=//:x64_windows-clang-cl
# @Todo(bazel) We should be able to use @local_config_cc...
build:windows --extra_toolchains=@@rules_cc++cc_configure_extension+local_config_cc//:cc-toolchain-x64_windows-clang-cl
build:linux --repo_env=CC=clang
build:windows --cxxopt=-Xclang=-std=c++23
build:linux --cxxopt=-std=c++23
build --cxxopt=-Wall
build --features=c++23
build --cxxopt=-Weverything
build --cxxopt=-Wno-c++98-compat
build --cxxopt=-Wno-c++98-compat-pedantic
build --cxxopt=-Wno-pre-c++17-compat
@ -24,21 +20,8 @@ build --cxxopt=-Wno-extra-semi
build --cxxopt=-Wno-global-constructors
build --cxxopt=-Wno-unsafe-buffer-usage
build --cxxopt=-Wno-covered-switch-default
build:windows_san --config=windows
build:windows_san --copt=-fno-sanitize-ignorelist
build:windows_san --copt=-fsanitize=address
build:windows_san --copt=-fsanitize=undefined
build:windows_san --copt=-fno-sanitize-recover=all
build:windows_san --linkopt=clang_rt.asan-x86_64.lib
build:windows_san --copt=/MT
build:linux_san --config=linux
build:linux_san --copt=-fsanitize=address
build:linux_san --linkopt=-fsanitize=address
build:linux_san --copt=-fsanitize=undefined
build:linux_san --copt=-fno-sanitize-recover=all
build:linux_san --linkopt=-fsanitize=undefined
build:linux_san --linkopt=-fsanitize-link-c++-runtime
build --cxxopt=-Wno-unused-command-line-argument
build --cxxopt=-Wno-padded
build --cxxopt=-Wno-weak-vtables
test --test_output=errors

View File

@ -1,34 +1,24 @@
Checks:
- "hicpp-*"
- "cppcoreguidelines-*"
- "misc-*"
- "clang-analyzer-*"
- "-misc-include-cleaner"
- "performance-*"
- "readability-*"
- "-*-named-parameter"
- "-*-avoid-do-while"
- "-*-magic-numbers"
- "-*-identifier-length"
- "-*-union-access"
- "-*-vararg"
- "-*-macro-usage"
- "-*-non-private-member-variables-in-classes"
- "-*-avoid-non-const-global-variables"
- "-*-missing-std-forward"
- "-*-owning-memory"
- "-*-no-malloc"
- "*"
- "-llvm*"
- "-altera*"
- "-fuchsia*"
- "-*-use-trailing-return-type"
- "-readability-identifier-length"
- "-cppcoreguidelines-macro-usage"
- "-*-avoid-c-arrays"
- "-*-non-private-member-variables-in-classes"
- "-*-use-anonymous-namespace"
- "-*-reinterpret-cast"
- "-*-noexcept-swap"
- "-*-noexcept-move"
- "-*-noexcept-move-constructor"
- "-*-noexcept-move-operations"
- "-*-bounds-array-to-pointer-decay"
- "-*-no-array-decay"
- "-cert-err58-cpp"
- "-*noexcept*"
- "-*-magic-numbers"
- "-*-named-parameter"
- "-misc-include-cleaner"
- "-google-runtime-int"
- "-bugprone-easily-swappable-parameters"
- "-*-signed-bitwise"
- "-readability-use-anyofallof"
- "-readability-function-cognitive-complexity"
- "-*-avoid-do-while"
- "-cppcoreguidelines-pro-type-union-access"
- "-*-copy-assignment-signature"
- "-*-unconventional-assign-operator"
- "-readability-math-missing-parentheses"
- "-*-rvalue-reference-param-not-moved"

7
.clangd Normal file
View File

@ -0,0 +1,7 @@
---
CompilerFlags:
Add:
- "-Wall"
Diagnostics:
ClangTidy:
FastCheckFilter: None

View File

@ -13,14 +13,3 @@ license(
package_name = "ASL",
)
platform(
name = "x64_windows-clang-cl",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
# @Todo(bazel) Bit weird to use a private thing.
# We used to use @bazel_tools//tools/cpp:clang-cl but it's deprecated
# in favor of... this?...
"@rules_cc//cc/private/toolchain:clang-cl",
],
)

View File

@ -2,19 +2,23 @@
#
# SPDX-License-Identifier: BSD-3-Clause
module(name = "asl")
module(name = "asl", version = "0.4.0")
bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "rules_cc", version = "0.0.17")
bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
commit = "4f28899228fb3ad0126897876f147ca15026151e",
)
bazel_dep(name = "rules_cc", version = "0.1.1")
bazel_dep(name = "rules_python", version = "1.1.0")
bazel_dep(name = "hedron_compile_commands", version = "0.1.0", dev_dependency = True)
bazel_dep(name = "rules_python", version = "1.3.0")
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.13", is_default = True)
clang_toolchain = use_extension("//bazel/clang_toolchain:config_detection.bzl", "clang_toolchain")
use_repo(clang_toolchain, "clang_toolchain")
register_toolchains(
"@clang_toolchain//:windows_x86_64_toolchain_def",
"@clang_toolchain//:linux_x86_64_toolchain_def",
)

96
MODULE.bazel.lock generated
View File

@ -1,6 +1,9 @@
{
"lockFileVersion": 18,
"registryFileHashes": {
"https://bazel.stevenlr.com/registry/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "5623ba8f732a01246c388bccebf924357e452314a178f179d3b375b623d5a359",
"https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/source.json": "c55f6caa3eb9fb027af66949c23ca537214eb32b0316ae95bcc496f3cd8406b9",
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
@ -17,7 +20,8 @@
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/source.json": "3e8379efaaef53ce35b7b8ba419df829315a880cb0a030e5bb45c96d6d5ecb5f",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
@ -39,11 +43,13 @@
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://bcr.bazel.build/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "not found",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
"https://bcr.bazel.build/modules/platforms/0.0.10/source.json": "f22828ff4cf021a6b577f1bf6341cb9dcd7965092a439f64fc1bb3b7a5ae4bd5",
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
@ -69,12 +75,12 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
@ -88,10 +94,10 @@
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2",
"https://bcr.bazel.build/modules/rules_java/8.6.1/source.json": "f18d9ad3c4c54945bf422ad584fa6c5ca5b3116ff55a5b1bc77e5c1210be5960",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
@ -121,8 +127,8 @@
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
"https://bcr.bazel.build/modules/rules_python/1.1.0/MODULE.bazel": "57e01abae22956eb96d891572490d20e07d983e0c065de0b2170cafe5053e788",
"https://bcr.bazel.build/modules/rules_python/1.1.0/source.json": "29f1fdfd23a40808c622f813bc93e29c3aae277333f03293f667e76159750a0f",
"https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
@ -134,53 +140,31 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@platforms//host:extension.bzl%host_platform": {
"//bazel/clang_toolchain:config_detection.bzl%clang_toolchain": {
"general": {
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
"usagesDigest": "SeQiIN/f8/Qt9vYQk7qcXp4I4wJeEC0RnQDiaaJ4tb8=",
"bzlTransitiveDigest": "xXTcNvgp9bMnw/1ZKi1vOOoz139EHr8Hw3Ugd9+eos0=",
"usagesDigest": "CRr/eSLZQiKHqCq8MWfzhslrj4Mqar1310aEoa4BzJw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"host_platform": {
"repoRuleId": "@@platforms//host:extension.bzl%host_platform_repo",
"clang_toolchain": {
"repoRuleId": "@@//bazel/clang_toolchain:config_detection.bzl%_config_detection",
"attributes": {}
}
},
"recordedRepoMappingEntries": []
}
},
"@@rules_java+//java:rules_java_deps.bzl%compatibility_proxy": {
"general": {
"bzlTransitiveDigest": "84xJEZ1jnXXwo8BXMprvBm++rRt4jsTu9liBxz0ivps=",
"usagesDigest": "jTQDdLDxsS43zuRmg1faAjIEPWdLAbDAowI1pInQSoo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"compatibility_proxy": {
"repoRuleId": "@@rules_java+//java:rules_java_deps.bzl%_compatibility_proxy_repo_rule",
"attributes": {}
}
},
"recordedRepoMappingEntries": [
[
"rules_java+",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": {
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=",
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -241,6 +225,42 @@
]
]
}
},
"@@rules_python+//python/uv:uv.bzl%uv": {
"general": {
"bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=",
"usagesDigest": "vJ5RHUxAnV24M5swNGiAnkdxMx3Hp/iOLmNANTC5Xc8=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"uv": {
"repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo",
"attributes": {
"toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'",
"toolchain_names": [
"none"
],
"toolchain_implementations": {
"none": "'@@rules_python+//python:none'"
},
"toolchain_compatible_with": {
"none": [
"@platforms//:incompatible"
]
},
"toolchain_target_settings": {}
}
}
},
"recordedRepoMappingEntries": [
[
"rules_python+",
"platforms",
"platforms"
]
]
}
}
}
}

17
asl/BUILD.bazel Normal file
View File

@ -0,0 +1,17 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
config_setting(
name = "debug",
values = {
"compilation_mode": "dbg",
},
)
config_setting(
name = "optimized",
values = {
"compilation_mode": "opt",
},
)

View File

@ -11,17 +11,26 @@ cc_library(
hdrs = [
"annotations.hpp",
"assert.hpp",
"bit.hpp",
"config.hpp",
"defer.hpp",
"float.hpp",
"functional.hpp",
"integers.hpp",
"meta.hpp",
"numeric.hpp",
"utility.hpp",
],
srcs = [
"assert.cpp",
],
defines = select({
"//asl:debug": ["ASL_DEBUG=1"],
"//conditions:default": ["ASL_DEBUG=0"],
}) + select({
"//asl:optimized": ["ASL_OPTIMIZED=1"],
"//conditions:default": ["ASL_OPTIMIZED=0"],
}),
visibility = ["//visibility:public"],
)
@ -37,10 +46,12 @@ cc_library(
"//asl/types:box",
],
) for name in [
"bit",
"defer",
"float",
"functional",
"integers",
"meta",
"numeric",
"utility",
]]

View File

@ -6,8 +6,8 @@
#include "asl/base/config.hpp"
#if ASL_COMPILER_CLANG_CL
#if defined(ASL_COMPILER_CLANG_CL)
#define ASL_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#elif ASL_COMPILER_CLANG
#elif defined(ASL_COMPILER_CLANG)
#define ASL_NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif

View File

@ -4,7 +4,12 @@
#include "asl/base/assert.hpp"
#include "asl/base/meta.hpp"
// NOLINTNEXTLINE(*-non-const-global-variables)
static asl::AssertFailureHandler* s_handler = nullptr;
// NOLINTNEXTLINE(*-non-const-global-variables)
static void* s_user = nullptr;
void asl::set_assert_failure_handler(AssertFailureHandler handler, void* user)

View File

@ -18,21 +18,23 @@ void report_assert_failure(const char* msg, const source_location& sl = source_l
} // namespace asl
#if ASL_COMPILER_CLANG_CL
#if defined(ASL_COMPILER_CLANG_CL)
#define ASL_DEBUG_BREAK() __debugbreak()
#elif ASL_COMPILER_CLANG
#elif defined(ASL_COMPILER_CLANG)
#define ASL_DEBUG_BREAK() __builtin_debugtrap()
#endif
// @Todo Configure asserts at build time
#define ASL_ASSERT(...) \
if (__VA_ARGS__) {} \
else \
{ \
::asl::report_assert_failure(#__VA_ARGS__); \
ASL_DEBUG_BREAK(); \
}
#if !ASL_OPTIMIZED
#define ASL_ASSERT(...) \
if (__VA_ARGS__) {} \
else \
{ \
::asl::report_assert_failure(#__VA_ARGS__); \
ASL_DEBUG_BREAK(); \
}
#else
#define ASL_ASSERT(...)
#endif
#define ASL_ASSERT_RELEASE(...) \
if (__VA_ARGS__) {} \

166
asl/base/bit.hpp Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/meta.hpp"
namespace asl
{
constexpr bool has_single_bit(is_unsigned_integer auto x)
{
return x != 0 && ((x - 1) & x) == 0;
}
constexpr int popcount(uint8_t v)
{
v = v - ((v >> 1) & 0x55);
v = (v & 0x33) + ((v >> 2) & 0x33);
return (v + (v >> 4)) & 0x0F;
}
constexpr int popcount(uint16_t v)
{
v = v - ((v >> 1) & 0x5555);
v = (v & 0x3333) + ((v >> 2) & 0x3333);
return static_cast<uint16_t>((v + (v >> 4) & 0x0F0F) * uint16_t{0x0101}) >> 8;
}
constexpr int popcount(uint32_t v)
{
v = v - ((v >> 1) & 0x5555'5555);
v = (v & 0x3333'3333) + ((v >> 2) & 0x3333'3333);
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F) * 0x0101'0101) >> 24);
}
constexpr int popcount(uint64_t v)
{
v = v - ((v >> 1) & 0x5555'5555'5555'5555);
v = (v & 0x3333'3333'3333'3333) + ((v >> 2) & 0x3333'3333'3333'3333);
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F'0F0F'0F0F) * 0x0101'0101'0101'0101) >> 56);
}
constexpr uint8_t propagate_right_one(uint8_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
return v;
}
constexpr uint16_t propagate_right_one(uint16_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
return v;
}
constexpr uint32_t propagate_right_one(uint32_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
v = v | (v >> 16);
return v;
}
constexpr uint64_t propagate_right_one(uint64_t v)
{
v = v | (v >> 1);
v = v | (v >> 2);
v = v | (v >> 4);
v = v | (v >> 8);
v = v | (v >> 16);
v = v | (v >> 32);
return v;
}
constexpr int countr_zero(is_unsigned_integer auto v)
{
v = ~v & (v - 1);
return popcount(v);
}
constexpr int countr_one(is_unsigned_integer auto v)
{
v = v & (~v - 1);
return popcount(v);
}
constexpr int countl_zero(is_unsigned_integer auto v)
{
v = ~propagate_right_one(v);
return popcount(v);
}
constexpr int countl_one(is_unsigned_integer auto v)
{
v = ~v;
v = ~propagate_right_one(v);
return popcount(v);
}
template<is_unsigned_integer T>
constexpr T rotr(T v, int s);
template<is_unsigned_integer T>
constexpr T rotl(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v << s) | (v >> ((N - s) % N)) : rotr(v, -s);
}
template<is_unsigned_integer T>
constexpr T rotr(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v >> s) | (v << ((N - s) % N)) : rotl(v, -s);
}
constexpr uint16_t byteswap(uint16_t v)
{
return static_cast<uint16_t>((v << 8U) | (v >> 8U));
}
constexpr uint32_t byteswap(uint32_t v)
{
return rotr(v & 0x00ff'00ff, 8) | rotl(v & 0xff00'ff00, 8);
}
constexpr uint64_t byteswap(uint64_t v)
{
return (uint64_t{byteswap(static_cast<uint32_t>(v))} << 32)
| uint64_t{byteswap(static_cast<uint32_t>(v >> 32))};
}
constexpr auto bit_ceil(is_unsigned_integer auto v)
{
v -= 1;
v = propagate_right_one(v);
v += 1;
return v;
}
constexpr auto bit_floor(is_unsigned_integer auto v)
{
v = propagate_right_one(v);
v = v - (v >> 1);
return v;
}
constexpr int bit_width(is_unsigned_integer auto v)
{
static constexpr int N = sizeof(decltype(v)) * 8;
return N - countl_zero(v);
}
} // namespace asl

203
asl/base/bit_tests.cpp Normal file
View File

@ -0,0 +1,203 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/base/bit.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(has_single_bit)
{
ASL_TEST_EXPECT(asl::has_single_bit(4U));
ASL_TEST_EXPECT(asl::has_single_bit(1024U));
ASL_TEST_EXPECT(asl::has_single_bit(0x8000'0000U));
ASL_TEST_EXPECT(asl::has_single_bit(uint64_t{0x0000'8000'0000'0000ULL}));
ASL_TEST_EXPECT(!asl::has_single_bit(0U));
ASL_TEST_EXPECT(!asl::has_single_bit(3U));
ASL_TEST_EXPECT(!asl::has_single_bit(341U));
int k = 0x7fffffff;
k += 1;
int *array = new int[100];
delete [] array;
ASL_TEST_EXPECT(array[1] == 0); // BOOM
}
ASL_TEST(popcount) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::popcount(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint8_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint16_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint16_t{65535}) == 16);
ASL_TEST_EXPECT(asl::popcount(uint16_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint32_t{0}) == 0);
ASL_TEST_EXPECT(asl::popcount(uint32_t{255}) == 8);
ASL_TEST_EXPECT(asl::popcount(uint32_t{65535}) == 16);
ASL_TEST_EXPECT(asl::popcount(uint32_t{65536}) == 1);
ASL_TEST_EXPECT(asl::popcount(uint32_t{46}) == 4);
ASL_TEST_EXPECT(asl::popcount(uint32_t{0xffff'ffff}) == 32);
ASL_TEST_EXPECT(asl::popcount(0x8421'1248'8421'1248) == 16);
ASL_TEST_EXPECT(asl::popcount(0xffff'ffff'ffff'ffff) == 64);
}
ASL_TEST(countr_zero)
{
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0}) == 8);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{1}) == 0);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{2}) == 1);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{4}) == 2);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{8}) == 3);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b00011100}) == 2);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101010}) == 1);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101001}) == 0);
}
ASL_TEST(countr_one)
{
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b00011100}) == 0);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101011}) == 2);
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101001}) == 1);
}
ASL_TEST(countl_zero)
{
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0}) == 8);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00011100}) == 3);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b10101010}) == 0);
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00001001}) == 4);
}
ASL_TEST(countl_one)
{
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{255}) == 8);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b00011100}) == 0);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b10101010}) == 1);
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b11101001}) == 3);
}
ASL_TEST(rotl)
{
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 1) == 0x8000'0200);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 3) == 0x0000'0802);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -1) == 0x2000'0080);
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -12) == 0x1004'0000);
}
ASL_TEST(rotr)
{
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -1) == 0x8000'0200);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -3) == 0x0000'0802);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 1) == 0x2000'0080);
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 12) == 0x1004'0000);
}
ASL_TEST(byteswap)
{
ASL_TEST_EXPECT(asl::byteswap(uint32_t{0x1234'5678}) == 0x7856'3412);
ASL_TEST_EXPECT(asl::byteswap(uint16_t{0x1234}) == 0x3412);
ASL_TEST_EXPECT(asl::byteswap(uint64_t{0x0123'4567'89ab'cdef}) == 0xefcd'ab89'6745'2301);
}
ASL_TEST(bit_ceil) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{3}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{5}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{6}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{127}) == 128);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{128}) == 128);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{254}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{3}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{5}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{6}) == 8);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32000}) == 32768);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32768}) == 32768);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{35000}) == 0);
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{65535}) == 0);
}
ASL_TEST(bit_floor) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{5}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{6}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{127}) == 64);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{128}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{254}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{255}) == 128);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{4}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{5}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{6}) == 4);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32000}) == 16384);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32768}) == 32768);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{35000}) == 32768);
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{65535}) == 32768);
}
ASL_TEST(bit_width) // NOLINT(*-cognitive-complexity)
{
ASL_TEST_EXPECT(asl::bit_width(uint8_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint8_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint16_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65536}) == 17);
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0xffff'ffff}) == 32);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0}) == 0);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{1}) == 1);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{2}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{3}) == 2);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{4}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{5}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{6}) == 3);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65535}) == 16);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65536}) == 17);
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0xffff'ffff}) == 32);
}

View File

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: BSD-3-Clause
// NOLINTBEGIN(*-macro-to-enum)
#pragma once
#if defined(_WIN32)
@ -19,3 +21,16 @@
#else
#error Unknown compiler
#endif
// ASL_DEBUG=1 for slow builds, with extra validation logic and such.
#if !defined(ASL_DEBUG)
#error ASL_DEBUG should be defined to 0 or 1
#endif
// ASL_OPTIMIZED=1 for fast builds, with minimal validation logic.
#if !defined(ASL_OPTIMIZED)
#error ASL_OPTIMIZED should be defined to 0 or 1
#endif
// NOLINTEND(*-macro-to-enum)

View File

@ -18,14 +18,16 @@ class DeferCallback
public:
template<typename T>
explicit DeferCallback(T&& callback) : m_callback(ASL_FWD(callback))
explicit DeferCallback(T&& callback)
requires (!same_as<un_cvref_t<T>, DeferCallback>)
: m_callback(std::forward<T>(callback))
{
}
ASL_DELETE_COPY(DeferCallback);
DeferCallback(DeferCallback&& other) :
m_callback(ASL_MOVE(other.m_callback)), m_moved(exchange(other.m_moved, true))
m_callback(std::move(other.m_callback)), m_moved(exchange(other.m_moved, true))
{
}
@ -42,7 +44,7 @@ struct DeferFactory
template<invocable Callback>
DeferCallback<Callback> operator<<(Callback&& callback) const
{
return DeferCallback<Callback>(ASL_FWD(callback));
return DeferCallback<Callback>(std::forward<Callback>(callback));
}
};

View File

@ -10,25 +10,25 @@ ASL_TEST(defer)
uint32_t a = 0;
{
ASL_DEFER [&a]() { a |= 1; };
ASL_DEFER [&a]() { a |= 1U; };
ASL_TEST_EXPECT(a == 0);
{
ASL_DEFER [&a]() { a |= 2; };
ASL_DEFER [&a]() { a |= 4; };
ASL_DEFER [&a]() { a |= 2U; };
ASL_DEFER [&a]() { a |= 4U; };
ASL_TEST_EXPECT(a == 0);
}
ASL_TEST_EXPECT(a == 6);
{
ASL_DEFER [&a]() { a |= 8; };
ASL_DEFER [&a]() { a |= 8U; };
ASL_TEST_EXPECT(a == 6);
}
ASL_TEST_EXPECT(a == 14);
ASL_DEFER [&a]() { a |= 16; };
ASL_DEFER [&a]() { a |= 16U; };
ASL_TEST_EXPECT(a == 14);
}

View File

@ -6,12 +6,42 @@
#include "asl/base/meta.hpp"
using float32_t = float;
using float64_t = double;
namespace asl
{
template<is_floating_point T> constexpr T infinity() { return __builtin_inf(); }
template<typename T> struct float_traits {};
template<is_floating_point T> constexpr T nan() { return static_cast<T>(__builtin_nanf("")); }
#define ASL_FLOAT_TRAITS(T, INF, NAN, EPS, SMALLEST) \
template<> struct float_traits<T> \
{ \
static constexpr T kInfinity{__builtin_bit_cast(T, INF)}; \
static constexpr T kNaN{__builtin_bit_cast(T, NAN)}; \
static constexpr T kEpsilon{EPS}; \
static constexpr T kSmallest{__builtin_bit_cast(T, SMALLEST)}; \
};
ASL_FLOAT_TRAITS(
float32_t,
0x7F800000,
0x7FC00000,
__builtin_bit_cast(float32_t, 0x3F800001) - float32_t{1},
0x00800000
);
ASL_FLOAT_TRAITS(
float64_t,
0x7FF0000000000000,
0x7FF8000000000000,
__builtin_bit_cast(float64_t, 0x3FF0000000000001) - float64_t{1},
0x0010000000000000
);
template<is_floating_point T> constexpr T infinity() { return float_traits<T>::kInfinity; }
template<is_floating_point T> constexpr T nan() { return float_traits<T>::kNaN; }
template<is_floating_point T> constexpr bool is_infinity(T f) { return __builtin_isinf(f); }

View File

@ -9,56 +9,62 @@
namespace asl {
template<typename... Args, typename C>
template<typename F, typename... Args>
constexpr auto invoke(F&& f, Args&&... args)
-> decltype(std::forward<F>(f)(std::forward<Args>(args)...))
requires (!is_member_ptr<un_cvref_t<F>>) && requires {
f(std::forward<Args>(args)...);
}
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
template<typename C>
constexpr auto&& invoke(auto C::* f, same_or_derived_from<C> auto&& arg)
{
return std::forward<decltype(arg)>(arg).*f;
}
template<typename C>
constexpr auto&& invoke(auto C::* f, auto&& arg)
requires (
!same_or_derived_from<decltype(arg), C>
&& requires { (*arg).*f; }
)
{
return (*std::forward<decltype(arg)>(arg)).*f;
}
template<typename C, typename... Args>
constexpr auto invoke(is_func auto C::* f, same_or_derived_from<C> auto&& self, Args&&... args)
-> decltype((std::forward<decltype(self)>(self).*f)(std::forward<Args>(args)...))
requires requires { (self.*f)(std::forward<Args>(args)...); }
{
return (std::forward<decltype(self)>(self).*f)(std::forward<Args>(args)...);
}
template<typename C, typename... Args>
constexpr auto invoke(is_func auto C::* f, auto&& self, Args&&... args)
-> decltype((self.*f)(ASL_FWD(args)...))
requires requires {
(self.*f)(ASL_FWD(args)...);
}
{
return (ASL_FWD(self).*f)(ASL_FWD(args)...);
}
template<typename... Args, typename C>
constexpr auto invoke(is_func auto C::* f, auto* self, Args&&... args)
-> decltype((self->*f)(ASL_FWD(args)...))
requires requires {
(self->*f)(ASL_FWD(args)...);
}
{
return (self->*f)(ASL_FWD(args)...);
}
template<typename... Args, typename C>
constexpr auto invoke(is_object auto C::* m, auto&& self, Args&&...)
-> decltype(self.*m)
-> decltype(((*std::forward<decltype(self)>(self)).*f)(std::forward<Args>(args)...))
requires (
sizeof...(Args) == 0 &&
requires { self.*m; }
!same_or_derived_from<decltype(self), C>
&& requires { ((*self).*f)(std::forward<Args>(args)...); }
)
{
return ASL_FWD(self).*m;
return ((*std::forward<decltype(self)>(self)).*f)(std::forward<Args>(args)...);
}
template<typename... Args, typename C>
constexpr auto invoke(is_object auto C::* m, auto* self, Args&&...)
-> decltype(self->*m)
requires (
sizeof...(Args) == 0 &&
requires { self->*m; }
)
template<typename R, typename F, typename... Args>
constexpr R invoke_r(F&& f, Args&&... args)
{
return self->*m;
}
template<typename... Args>
constexpr auto invoke(auto&& f, Args&&... args)
-> decltype(f(ASL_FWD(args)...))
requires requires {
f(ASL_FWD(args)...);
if constexpr (is_void<R>)
{
static_cast<void>(invoke(std::forward<F>(f), std::forward<Args>(args)...));
}
else
{
return invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
{
return ASL_FWD(f)(ASL_FWD(args)...);
}
template<typename Void, typename F, typename... Args>
@ -76,7 +82,11 @@ using invoke_result_t = _invoke_result_helper<void, F, Args...>::type;
template<typename F, typename... Args>
concept invocable = requires (F&& f, Args&&... args)
{
invoke(ASL_FWD(f), ASL_FWD(args)...);
invoke(std::forward<F>(f), std::forward<Args>(args)...);
};
template<typename R, typename F, typename... Args>
concept invocable_r = invocable<F, Args...>
&& (is_void<R> || convertible_to<invoke_result_t<F, Args...>, R>);
} // namespace asl

View File

@ -7,9 +7,12 @@
struct HasFunction
{
void do_something(int, float) {}
void do_something(int, float) const {}
int& do_something2(int, float) &;
};
struct HasFunction2 : public HasFunction {};
struct HasMember
{
int member{};
@ -17,6 +20,8 @@ struct HasMember
void (*member_fn)(){};
};
struct HasMember2 : public HasMember {};
struct Functor
{
int64_t operator()() { return 35; }
@ -34,7 +39,13 @@ static_assert(asl::same_as<asl::invoke_result_t<Functor>, int64_t>);
static_assert(asl::same_as<asl::invoke_result_t<Functor, int>, int>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(static_cast<float(*)(float)>(some_func1)), float>, float>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), HasFunction, int, float>, void>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember>, const int&>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), const HasFunction2&, int, float>, void>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), HasFunction*, int, float>, void>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something2), HasFunction2&, int, float>, int&>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something2), HasFunction*, int, float>, int&>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), HasMember>, int&&>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember&>, const int&>);
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember2*>, const int&>);
static_assert(asl::invocable<int()>);
static_assert(!asl::invocable<int(), int>);
@ -45,8 +56,20 @@ static_assert(asl::invocable<Functor, int>);
static_assert(!asl::invocable<Functor, void*>);
static_assert(asl::invocable<decltype(static_cast<float(*)(float)>(some_func1)), float>);
static_assert(asl::invocable<decltype(&HasFunction::do_something), HasFunction, int, float>);
static_assert(asl::invocable<decltype(&HasFunction::do_something), const HasFunction2&, int, float>);
static_assert(asl::invocable<decltype(&HasFunction::do_something), HasFunction*, int, float>);
static_assert(!asl::invocable<decltype(&HasFunction::do_something), HasFunction, int, int*>);
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember>);
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), HasFunction, int, float>);
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), const HasFunction2&, int, float>);
static_assert(asl::invocable<decltype(&HasFunction::do_something2), HasFunction2&, int, float>);
static_assert(asl::invocable<decltype(&HasFunction::do_something2), HasFunction*, int, float>);
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), HasFunction, int, int*>);
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember2>);
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember&>);
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember2*>);
static_assert(asl::invocable_r<void*, int*()>);
static_assert(!asl::invocable_r<int*, void*()>);
ASL_TEST(invoke_member_function)
{
@ -89,3 +112,9 @@ ASL_TEST(invoke_lambda)
ASL_TEST_EXPECT(asl::invoke([](){ return 35; }) == 35);
ASL_TEST_EXPECT(asl::invoke([](int x){ return x + 2; }, 6) == 8);
}
ASL_TEST(invoke_r)
{
ASL_TEST_EXPECT(asl::invoke_r<int>([]() { return 1ULL; }) == 1);
asl::invoke_r<void>([]() { return 1ULL; });
}

View File

@ -9,18 +9,18 @@
using int8_t = signed char;
using int16_t = signed short;
using int32_t = signed int;
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
using int64_t = signed long long;
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
using int64_t = signed long;
#endif
using uint8_t = unsigned char;
using uint16_t = unsigned short;
using uint32_t = unsigned int;
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
using uint64_t = unsigned long long;
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
using uint64_t = unsigned long;
#endif
@ -40,5 +40,25 @@ namespace asl
enum class byte : uint8_t {};
template<typename T> struct integer_traits {};
#define ASL_INTEGER_TRAITS(T, MIN, MAX) \
template<> struct integer_traits<T> \
{ \
static constexpr T kMin{static_cast<T>(MIN)}; \
static constexpr T kMax{static_cast<T>(MAX)}; \
}
ASL_INTEGER_TRAITS(uint8_t, 0, 0xff);
ASL_INTEGER_TRAITS(uint16_t, 0, 0xffff);
ASL_INTEGER_TRAITS(uint32_t, 0, 0xffff'ffff);
ASL_INTEGER_TRAITS(uint64_t, 0, 0xffff'ffff'ffff'ffff);
ASL_INTEGER_TRAITS(int8_t, -0x80, 0x7f);
ASL_INTEGER_TRAITS(int16_t, -0x8000, 0x7fff);
ASL_INTEGER_TRAITS(int32_t, -0x8000'0000, 0x7fff'ffff);
ASL_INTEGER_TRAITS(int64_t, -0x8000'0000'0000'0000, 0x7fff'ffff'ffff'ffff);
#undef ASL_INTEGER_TRAITS
} // namespace asl

View File

@ -25,6 +25,9 @@ struct empty {};
template<typename T> struct id { using type = T; };
struct in_place_t {};
static constexpr in_place_t in_place{};
template<typename... Args> static constexpr isize_t types_count = sizeof...(Args);
template<typename T, T kValue> struct integral_constant { static constexpr T value = kValue; };
@ -54,6 +57,11 @@ template<typename T> using as_rref_t = decltype(_as_rref_helper<T>(0))::type;
template<typename T> consteval as_rref_t<T> declval() {}
template<typename T> auto _as_ptr_helper(int) -> id<T*>;
template<typename T> auto _as_ptr_helper(...) -> id<T>;
template<typename T> using as_ptr_t = decltype(_as_ptr_helper<T>(0))::type;
template<typename T> struct _un_ref_t { using type = T; };
template<typename T> struct _un_ref_t<T&> { using type = T; };
template<typename T> struct _un_ref_t<T&&> { using type = T; };
@ -87,12 +95,6 @@ template<typename T> concept trivially_destructible = __is_trivially_destructibl
template<typename T> concept copyable = copy_constructible<T> && copy_assignable<T>;
template<typename T> concept moveable = move_constructible<T> && move_assignable<T>;
template<typename To, typename From>
concept convertible_from = __is_convertible(From, To);
template<typename Derived, class Base>
concept derived_from = __is_class(Derived) && __is_class(Base) && convertible_from<const volatile Base*, const volatile Derived*>;
using nullptr_t = decltype(nullptr);
template<typename T> struct _un_const_helper { using type = T; };
@ -144,16 +146,27 @@ struct _copy_const_helper<From, To, true> { using type = const To; };
template<typename From, typename To> using copy_cref_t =
_copy_ref_helper<From, typename _copy_const_helper<From, un_cvref_t<To>>::type>::type;
template<typename From, typename To> using copy_const_t = _copy_const_helper<From, un_cvref_t<To>>::type;
template<typename T> struct _is_ptr_helper : false_type {};
template<typename T> struct _is_ptr_helper<T*> : true_type {};
template<typename T> concept is_ptr = _is_ptr_helper<un_cv_t<T>>::value;
template<typename From, typename To>
concept convertible_to = __is_convertible(From, To);
template<typename Derived, typename Base>
concept derived_from = __is_class(Derived) && __is_class(Base) && convertible_to<const volatile Derived*, const volatile Base*>;
template<typename Derived, typename Base>
concept same_or_derived_from = same_as<un_cvref_t<Derived>, Base> || derived_from<un_cvref_t<Derived>, Base>;
template<typename T> struct _tame_helper { using type = T; };
#define TAME_HELPER_IMPL(TRAILING) \
template<typename R, typename... Args> \
struct _tame_helper<R(Args...) TRAILING> { using type = R(Args...); }
struct _tame_helper<R(Args...) TRAILING> { using type = R(Args...); } // NOLINT(*-parentheses)
TAME_HELPER_IMPL();
TAME_HELPER_IMPL(&);
@ -189,13 +202,36 @@ template<typename R, typename... Args> struct _is_func_helper<R(Args...)> : true
template<typename T> concept is_func = _is_func_helper<tame_t<T>>::value;
template<typename T> struct _is_member_ptr_helper : false_type {};
template<typename C, typename T> struct _is_member_ptr_helper<T C::*> : true_type
{
static constexpr bool kIsFunc = is_func<T>;
};
template<typename T> concept is_member_ptr = _is_member_ptr_helper<un_cv_t<T>>::value;
template<typename T> concept is_member_func_ptr = is_member_ptr<T> && _is_member_ptr_helper<un_cv_t<T>>::kIsFunc;
template<typename T> concept is_member_data_ptr = is_member_ptr<T> && !_is_member_ptr_helper<un_cv_t<T>>::kIsFunc;
template<typename T> concept is_object = !is_void<T> && !is_ref<T> && !is_func<T>;
template<typename T> struct _is_array_helper : false_type {};
template<typename T> struct _is_array_helper<T[]> : true_type {};
template<typename T, int N> struct _is_array_helper<T[N]> : true_type {};
template<typename T> struct _array_helper : false_type { using type = T; };
template<typename T> struct _array_helper<T[]> : true_type { using type = T; };
template<typename T, int N> struct _array_helper<T[N]> : true_type { using type = T; };
template<typename T> concept is_array = _is_array_helper<T>::value;
template<typename T> concept is_array = _array_helper<T>::value;
template<typename T> using remove_extent_t = _array_helper<T>::type;
template<typename T>
using decay_t =
select_t<
is_array<un_ref_t<T>>,
as_ptr_t<remove_extent_t<un_ref_t<T>>>,
select_t<
is_func<un_ref_t<T>>,
as_ptr_t<un_ref_t<T>>,
un_cv_t<un_ref_t<T>>>>;
template<typename T> struct _is_floating_point_helper : false_type {};
template<> struct _is_floating_point_helper<float> : true_type {};
@ -203,25 +239,111 @@ template<> struct _is_floating_point_helper<double> : true_type {};
template<typename T> concept is_floating_point = _is_floating_point_helper<un_cv_t<T>>::value;
template<typename T> struct _is_integer_helper : false_type {};
template<> struct _is_integer_helper<int8_t> : true_type {};
template<> struct _is_integer_helper<int16_t> : true_type {};
template<> struct _is_integer_helper<int32_t> : true_type {};
template<> struct _is_integer_helper<int64_t> : true_type {};
template<> struct _is_integer_helper<uint8_t> : true_type {};
template<> struct _is_integer_helper<uint16_t> : true_type {};
template<> struct _is_integer_helper<uint32_t> : true_type {};
template<> struct _is_integer_helper<uint64_t> : true_type {};
template<typename T> struct _integer_traits
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = false;
};
template<typename T> concept is_integer = _is_integer_helper<un_cv_t<T>>::value;
template<> struct _integer_traits<uint8_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int8_t;
using as_unsigned = uint8_t;
};
template<> struct _integer_traits<uint16_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int16_t;
using as_unsigned = uint16_t;
};
template<> struct _integer_traits<uint32_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int32_t;
using as_unsigned = uint32_t;
};
template<> struct _integer_traits<uint64_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int64_t;
using as_unsigned = uint64_t;
};
template<> struct _integer_traits<int8_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint8_t;
using as_signed = int8_t;
};
template<> struct _integer_traits<int16_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint16_t;
using as_signed = int16_t;
};
template<> struct _integer_traits<int32_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint32_t;
using as_signed = int32_t;
};
template<> struct _integer_traits<int64_t>
{
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint64_t;
using as_signed = int64_t;
};
template<typename T> concept is_signed_integer = _integer_traits<T>::kSigned;
template<typename T> concept is_unsigned_integer = _integer_traits<T>::kUnsigned;
template<typename T> concept is_integer = is_signed_integer<T> || is_unsigned_integer<T>;
template<is_integer T> using as_unsigned_integer = _integer_traits<T>::as_unsigned;
template<is_integer T> using as_signed_integer = _integer_traits<T>::as_signed;
template<int N>
struct smallest_unsigned_integer_type_for_width_helper { using type = void; };
template<int N> requires (N >= 1 and N <= 8)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint8_t; };
template<int N> requires (N >= 9 and N <= 16)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint16_t; };
template<int N> requires (N >= 17 and N <= 32)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint32_t; };
template<int N> requires (N >= 33 and N <= 64)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint64_t; };
template<int N> using smallest_unsigned_integer_type_for_width
= smallest_unsigned_integer_type_for_width_helper<N>::type;
template<typename T> concept is_enum = __is_enum(T);
template<typename T> struct is_uniquely_represented : false_type {};
template<is_integer T> struct is_uniquely_represented<T> : true_type {};
template<is_enum T> struct is_uniquely_represented<T> : true_type {};
template<is_enum T> using underlying_t = __underlying_type(T);
template<typename T> struct is_uniquely_represented : false_type {};
template<is_integer T> struct is_uniquely_represented<T> : true_type {};
template<is_enum T> struct is_uniquely_represented<T> : true_type {};
template<> struct is_uniquely_represented<uint128_t> : true_type {};
template<> struct is_uniquely_represented<byte> : true_type {};
template<> struct is_uniquely_represented<byte> : true_type {};
template<typename T> concept uniquely_represented = is_uniquely_represented<un_cv_t<T>>::value;
@ -244,29 +366,28 @@ concept has_niche = constructible_from<T, niche_t> && equality_comparable_with<T
template<typename T>
concept is_niche = same_as<un_cvref_t<T>, niche_t>;
template<typename T, typename U>
concept _derefs_with_indirection_as = requires(T& t)
template<typename From, typename To>
concept _dereferenceable_as_convertible = requires(From& t)
{
*t;
requires convertible_from<U&, decltype(*t)>;
{ *t } -> convertible_to<To&>;
};
template<typename T, typename U>
concept _derefs_reference_as = is_ref<T> && convertible_from<U&, T>;
template<typename From, typename To>
concept derefs_as = is_object<To> &&
(convertible_to<un_ref_t<From>&, To&> || _dereferenceable_as_convertible<un_ref_t<From>, To>);
template<typename T, typename U>
concept _derefs_value_as = !is_ref<T> && convertible_from<U&, T&>;
template<typename U, _derefs_with_indirection_as<U> T>
constexpr U& deref(T&& t) { return static_cast<U&>(*t); }
template<typename U, _derefs_reference_as<U> T>
constexpr U& deref(T&& t) { return static_cast<U&>(t); }
template<typename U, _derefs_value_as<U> T>
constexpr U& deref(T&& t) { return static_cast<U&>(t); }
template<typename T, typename U>
concept derefs_as = _derefs_with_indirection_as<T, U> || _derefs_reference_as<T, U> || _derefs_value_as<T, U>;
template<typename To, derefs_as<To> From>
constexpr auto&& deref(From&& from) // NOLINT(*-missing-std-forward)
{
if constexpr (_dereferenceable_as_convertible<From, To>)
{
using deref_type = decltype(*declval<From&&>());
return static_cast<copy_cref_t<deref_type, To>>(*static_cast<From&&>(from));
}
else
{
return static_cast<copy_cref_t<From&&, To>>(from);
}
}
} // namespace asl

View File

@ -135,6 +135,12 @@ static_assert(!asl::is_ref<void>);
static_assert(!asl::is_ref<void()>);
static_assert(!asl::is_ref<void() const &&>);
struct MyClass
{
int data;
int fn(int x) { return x; } // NOLINT
};
static_assert(asl::is_ptr<int*>);
static_assert(asl::is_ptr<const int* const>);
static_assert(asl::is_ptr<const volatile int*>);
@ -142,6 +148,28 @@ static_assert(!asl::is_ptr<int>);
static_assert(!asl::is_ptr<void>);
static_assert(!asl::is_ptr<void()>);
static_assert(!asl::is_ptr<void() const &&>);
static_assert(!asl::is_ptr<int MyClass::*>);
static_assert(!asl::is_ptr<int (MyClass::*)(int)>);
static_assert(!asl::is_member_ptr<int*>);
static_assert(!asl::is_member_ptr<void()>);
static_assert(!asl::is_member_ptr<void() const &&>);
static_assert(asl::is_member_ptr<int MyClass::*>);
static_assert(asl::is_member_ptr<int (MyClass::*)(int)>);
static_assert(!asl::is_member_data_ptr<int*>);
static_assert(!asl::is_member_data_ptr<void()>);
static_assert(!asl::is_member_data_ptr<void() const &&>);
static_assert(asl::is_member_data_ptr<int MyClass::*>);
static_assert(!asl::is_member_data_ptr<int (MyClass::*)(int)>);
static_assert(!asl::is_member_func_ptr<int*>);
static_assert(!asl::is_member_func_ptr<void()>);
static_assert(!asl::is_member_func_ptr<void() const &&>);
static_assert(!asl::is_member_func_ptr<int MyClass::*>);
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int)>);
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int) const>);
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int) volatile &&>);
static_assert(asl::same_as<int, asl::tame_t<int>>);
static_assert(asl::same_as<int(), asl::tame_t<int()>>);
@ -189,6 +217,10 @@ static_assert(!asl::is_array<void>);
static_assert(!asl::is_array<void(int)>);
static_assert(!asl::is_array<int(float) const && noexcept>);
static_assert(asl::same_as<int, asl::remove_extent_t<int>>);
static_assert(asl::same_as<int, asl::remove_extent_t<int[]>>);
static_assert(asl::same_as<int, asl::remove_extent_t<int[67]>>);
static_assert(asl::same_as<int, asl::un_ref_t<int>>);
static_assert(asl::same_as<int, asl::un_ref_t<int&>>);
static_assert(asl::same_as<int, asl::un_ref_t<int&&>>);
@ -205,21 +237,23 @@ class C {};
class D { public: operator C() { return c; } C c; }; // NOLINT
class E { public: template<class T> E(T&&) {} }; // NOLINT
static_assert(asl::convertible_from<Base*, Derived*>);
static_assert(!asl::convertible_from<Derived*, Base*>);
static_assert(asl::convertible_from<C, D>);
static_assert(!asl::convertible_from<C*, Derived*>);
static_assert(asl::convertible_from<E, Base>);
static_assert(asl::convertible_to<Derived*, Base*>);
static_assert(!asl::convertible_to<Base*, Derived*>);
static_assert(asl::convertible_to<D, C>);
static_assert(!asl::convertible_to<Derived*, C*>);
static_assert(asl::convertible_to<Base, E>);
static_assert(!asl::convertible_from<int16_t(&)[], int32_t(&)[]>);
static_assert(asl::convertible_from<const int16_t(&)[], int16_t(&)[]>);
static_assert(asl::convertible_from<const int16_t(&)[], const int16_t(&)[]>);
static_assert(asl::convertible_from<int16_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_from<int32_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_from<int16_t(&)[], const int16_t(&)[]>);
static_assert(!asl::convertible_from<C(&)[], D(&)[]>);
static_assert(!asl::convertible_to<int32_t(&)[], int16_t(&)[]>);
static_assert(asl::convertible_to<int16_t(&)[], const int16_t(&)[]>);
static_assert(asl::convertible_to<const int16_t(&)[], const int16_t(&)[]>);
static_assert(asl::convertible_to<int16_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_to<int16_t(&)[], int32_t(&)[]>);
static_assert(!asl::convertible_to<const int16_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_to<D(&)[], C(&)[]>);
static_assert(asl::derived_from<Derived, Base>);
static_assert(asl::derived_from<Derived, Derived>);
static_assert(asl::derived_from<Base, Base>);
static_assert(!asl::derived_from<Base, Derived>);
static_assert(!asl::derived_from<D, C>);
static_assert(!asl::derived_from<C, D>);
@ -227,9 +261,20 @@ static_assert(!asl::derived_from<uint8_t, uint16_t>);
static_assert(!asl::derived_from<uint16_t, uint8_t>);
static_assert(!asl::derived_from<int, int>);
static_assert(asl::same_or_derived_from<Derived, Base>);
static_assert(asl::same_or_derived_from<Derived, Derived>);
static_assert(asl::same_or_derived_from<Base, Base>);
static_assert(!asl::same_or_derived_from<Base, Derived>);
static_assert(!asl::same_or_derived_from<D, C>);
static_assert(!asl::same_or_derived_from<C, D>);
static_assert(!asl::same_or_derived_from<uint8_t, uint16_t>);
static_assert(!asl::same_or_derived_from<uint16_t, uint8_t>);
static_assert(asl::same_or_derived_from<int, int>);
static_assert(!asl::is_const<int>);
static_assert(asl::is_const<const int>);
static_assert(!asl::is_const<const int*>);
static_assert(!asl::is_const<const int&>);
static_assert(asl::is_const<int* const>);
static_assert(asl::is_floating_point<float>);
@ -255,16 +300,22 @@ static_assert(asl::is_enum<Enum2>);
static_assert(asl::derefs_as<int, int>);
static_assert(asl::derefs_as<int*, int>);
static_assert(!asl::derefs_as<const int*, int>);
static_assert(asl::derefs_as<int&, int>);
static_assert(!asl::derefs_as<const int&, int>);
static_assert(asl::derefs_as<asl::box<int>, int>);
static_assert(asl::derefs_as<Derived, Base>);
static_assert(!asl::derefs_as<Base, Derived>);
static_assert(asl::derefs_as<Derived*, Base>);
static_assert(asl::derefs_as<Derived&, Base>);
static_assert(!asl::derefs_as<Base&, Derived>);
static_assert(asl::derefs_as<asl::box<Derived>, Base>);
static_assert(asl::derefs_as<asl::box<Derived>, Derived>);
static_assert(asl::derefs_as<asl::box<Base>, Base>);
static void wants_int(int) {}
static void wants_base(Base&) {}
static void wants_base(const Base&) {}
static void wants_base_ptr(Base*) {}
ASL_TEST(deref)
@ -285,10 +336,11 @@ ASL_TEST(deref)
wants_base(asl::deref<Base>(&c));
wants_base(asl::deref<Base>(d));
wants_base_ptr(&asl::deref<Base>(Derived{}));
wants_base_ptr(&asl::deref<Base>(c));
wants_base_ptr(&asl::deref<Base>(&c));
wants_base_ptr(&asl::deref<Base>(d));
wants_base(asl::deref<Base>(std::move(d)));
}
static_assert(asl::same_as<asl::copy_cref_t<int, float>, float>);
@ -311,3 +363,65 @@ static_assert(asl::same_as<asl::copy_cref_t<const int&, const float>, const floa
static_assert(asl::same_as<asl::copy_cref_t<const int&, float&&>, const float&>);
static_assert(asl::same_as<asl::copy_cref_t<const int&, const float&>, const float&>);
static_assert(asl::same_as<asl::copy_const_t<int, float>, float>);
static_assert(asl::same_as<asl::copy_const_t<int, const float>, float>);
static_assert(asl::same_as<asl::copy_const_t<const int, float>, const float>);
static_assert(asl::same_as<asl::copy_const_t<const int, const float>, const float>);
static_assert(asl::same_as<asl::copy_const_t<const int*, float>, float>);
static_assert(asl::same_as<asl::copy_const_t<int* const, float>, const float>);
static_assert(asl::same_as<asl::decay_t<int>, int>);
static_assert(!asl::same_as<asl::decay_t<int>, float>);
static_assert(asl::same_as<asl::decay_t<int&>, int>);
static_assert(asl::same_as<asl::decay_t<int&&>, int>);
static_assert(asl::same_as<asl::decay_t<const int&>, int>);
static_assert(asl::same_as<asl::decay_t<int[2]>, int*>);
static_assert(!asl::same_as<asl::decay_t<int[4][2]>, int*>);
static_assert(!asl::same_as<asl::decay_t<int[4][2]>, int**>);
static_assert(asl::same_as<asl::decay_t<int[4][2]>, int(*)[2]>);
static_assert(asl::same_as<asl::decay_t<int(int)>, int(*)(int)>);
enum EnumU8 : uint8_t {};
enum EnumI64 : int64_t {};
static_assert(asl::same_as<asl::underlying_t<EnumU8>, uint8_t>);
static_assert(asl::same_as<asl::underlying_t<EnumI64>, int64_t>);
static_assert(!asl::is_integer<EnumU8>);
static_assert(!asl::is_integer<EnumI64>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint8_t>, uint8_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint16_t>, uint16_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint32_t>, uint32_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint64_t>, uint64_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int8_t>, uint8_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int16_t>, uint16_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int32_t>, uint32_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int64_t>, uint64_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint8_t>, int8_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint16_t>, int16_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint32_t>, int32_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint64_t>, int64_t>);
static_assert(asl::same_as<asl::as_signed_integer<int8_t>, int8_t>);
static_assert(asl::same_as<asl::as_signed_integer<int16_t>, int16_t>);
static_assert(asl::same_as<asl::as_signed_integer<int32_t>, int32_t>);
static_assert(asl::same_as<asl::as_signed_integer<int64_t>, int64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<1>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<2>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<4>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<8>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<12>, uint16_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<16>, uint16_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<20>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<30>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<31>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<32>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<50>, uint64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<63>, uint64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<64>, uint64_t>);

101
asl/base/numeric.hpp Normal file
View File

@ -0,0 +1,101 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/float.hpp"
#include "asl/base/bit.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/assert.hpp"
namespace asl
{
template<is_integer T>
constexpr bool is_pow2(T x)
{
using unsigned_type = as_unsigned_integer<T>;
return x > 0 && has_single_bit(static_cast<unsigned_type>(x));
}
template<is_integer T>
constexpr T round_down_pow2(T x, T div)
{
ASL_ASSERT(is_pow2(div));
return x & (-div);
}
template<is_integer T>
constexpr T round_up_pow2(T x, T div)
{
ASL_ASSERT(is_pow2(div));
return (x + (div - 1)) & (-div);
}
template<typename T>
concept is_numeric = is_integer<T> || is_floating_point<T>;
template<is_numeric T>
constexpr T min(T a, T b)
{
return (a <= b) ? a : b;
}
template<is_numeric T>
constexpr T max(T a, T b)
{
return (a >= b) ? a : b;
}
template<is_numeric T>
constexpr T clamp(T x, T a, T b)
{
return min(max(x, a), b);
}
constexpr float32_t abs(float32_t x) { return __builtin_fabsf(x); }
constexpr float64_t abs(float64_t x) { return __builtin_fabs(x); }
template<is_floating_point T>
bool are_nearly_equal(T a, T b)
{
// This is a fast path for identical values and correctly handles
// the case where +0.0 == -0.0.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
if (a == b) { return true; }
#pragma clang diagnostic pop
// NaNs are never equal to anything, including themselves.
if (is_nan(a) || is_nan(b)) { return false; }
// Infinities are only equal if they are identical (which is handled by the
// `a == b` check above). If one is infinity and the other is not, they
// are not equal. The relative comparison below would fail with infinities.
if (is_infinity(a) || is_infinity(b)) { return false; }
static constexpr T kMin = float_traits<T>::kSmallest;
static constexpr T kEps = float_traits<T>::kEpsilon;
const T abs_a = abs(a);
const T abs_b = abs(b);
const T abs_diff = abs(a - b);
// The relative error comparison (`|a-b| <= ε * max(|a|, |b|)`) breaks
// down when `a` and `b` are near zero. In this case, we switch to an
// absolute error comparison. `DBL_MIN` is the smallest positive
// normalized double, so it's a good threshold for this check.
if (abs_a < kMin || abs_b < kMin)
{
return abs_diff < kEps;
}
// For all other cases, we use the standard relative error formula.
// The error is scaled by the magnitude of the numbers.
return abs_diff <= kEps * max(abs_a, abs_b);
}
} // namespace asl

View File

@ -0,0 +1,68 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/base/numeric.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(is_pow2)
{
ASL_TEST_EXPECT(asl::is_pow2(4));
ASL_TEST_EXPECT(asl::is_pow2(65536));
ASL_TEST_EXPECT(!asl::is_pow2(6));
ASL_TEST_EXPECT(!asl::is_pow2(1978));
ASL_TEST_EXPECT(!asl::is_pow2(0));
ASL_TEST_EXPECT(asl::is_pow2(4U));
ASL_TEST_EXPECT(asl::is_pow2(uint64_t{65536}));
}
ASL_TEST(round_down_pow2) // NOLINT
{
ASL_TEST_EXPECT(asl::round_down_pow2(0, 1) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(1, 1) == 1);
ASL_TEST_EXPECT(asl::round_down_pow2(2, 1) == 2);
ASL_TEST_EXPECT(asl::round_down_pow2(3, 1) == 3);
ASL_TEST_EXPECT(asl::round_down_pow2(-1, 1) == -1);
ASL_TEST_EXPECT(asl::round_down_pow2(-2, 1) == -2);
ASL_TEST_EXPECT(asl::round_down_pow2(-3, 1) == -3);
ASL_TEST_EXPECT(asl::round_down_pow2(0U, 1U) == 0U);
ASL_TEST_EXPECT(asl::round_down_pow2(1U, 1U) == 1U);
ASL_TEST_EXPECT(asl::round_down_pow2(2U, 1U) == 2U);
ASL_TEST_EXPECT(asl::round_down_pow2(3U, 1U) == 3U);
ASL_TEST_EXPECT(asl::round_down_pow2(0, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(1, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(8, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(15, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(16, 16) == 16);
ASL_TEST_EXPECT(asl::round_down_pow2(17, 16) == 16);
ASL_TEST_EXPECT(asl::round_down_pow2(255, 16) == 240);
ASL_TEST_EXPECT(asl::round_down_pow2(-255, 16) == -256);
}
ASL_TEST(round_up_pow2) // NOLINT
{
ASL_TEST_EXPECT(asl::round_up_pow2(0, 1) == 0);
ASL_TEST_EXPECT(asl::round_up_pow2(1, 1) == 1);
ASL_TEST_EXPECT(asl::round_up_pow2(2, 1) == 2);
ASL_TEST_EXPECT(asl::round_up_pow2(3, 1) == 3);
ASL_TEST_EXPECT(asl::round_up_pow2(-1, 1) == -1);
ASL_TEST_EXPECT(asl::round_up_pow2(-2, 1) == -2);
ASL_TEST_EXPECT(asl::round_up_pow2(-3, 1) == -3);
ASL_TEST_EXPECT(asl::round_up_pow2(0U, 1U) == 0U);
ASL_TEST_EXPECT(asl::round_up_pow2(1U, 1U) == 1U);
ASL_TEST_EXPECT(asl::round_up_pow2(2U, 1U) == 2U);
ASL_TEST_EXPECT(asl::round_up_pow2(3U, 1U) == 3U);
ASL_TEST_EXPECT(asl::round_up_pow2(0, 16) == 0);
ASL_TEST_EXPECT(asl::round_up_pow2(1, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(8, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(15, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(16, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(17, 16) == 32);
ASL_TEST_EXPECT(asl::round_up_pow2(255, 16) == 256);
ASL_TEST_EXPECT(asl::round_up_pow2(-255, 16) == -240);
}

View File

@ -7,31 +7,59 @@
#include "asl/base/meta.hpp"
#include "asl/base/assert.hpp"
#define ASL_MOVE(...) (static_cast<::asl::un_ref_t<decltype(__VA_ARGS__)>&&>(__VA_ARGS__))
namespace std
{
#define ASL_FWD(expr_) (static_cast<decltype(expr_)&&>(expr_))
template<typename T>
[[nodiscard]] constexpr asl::un_ref_t<T>&& move(T&& t) noexcept // NOLINT
{
return static_cast<asl::un_ref_t<T>&&>(t);
}
#define ASL_FWD_LIKE(ref_, expr_) (static_cast<::asl::copy_cref_t<ref_, decltype(expr_)&&>>(expr_))
template<typename T>
[[nodiscard]] constexpr T&& forward(asl::un_ref_t<T>& t) noexcept // NOLINT
{
return static_cast<T&&>(t);
}
template< class T >
[[nodiscard]] constexpr T&& forward(asl::un_ref_t<T>&& t) noexcept // NOLINT
{
return static_cast<T&&>(t);
}
template<typename T, typename U>
[[nodiscard]] constexpr auto forward_like(U&& x) noexcept -> asl::copy_cref_t<T, U> // NOLINT
{
using return_type = asl::copy_cref_t<T, U&&>;
return static_cast<return_type>(x);
}
template<typename T>
[[nodiscard]] constexpr T* launder(T* ptr) noexcept // NOLINT
requires (!asl::is_func<T> && !asl::is_void<T>)
{
return __builtin_launder(ptr);
}
} // namespace std
namespace asl
{
struct in_place_t {};
static constexpr in_place_t in_place{};
template<moveable T>
constexpr void swap(T& a, T& b)
{
T tmp{ASL_MOVE(a)};
a = ASL_MOVE(b);
b = ASL_MOVE(tmp);
T tmp{std::move(a)};
a = std::move(b);
b = std::move(tmp);
}
template<typename T, typename U>
T exchange(T& obj, U&& new_value)
{
T old_value = ASL_MOVE(obj);
obj = ASL_FWD(new_value);
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
@ -41,62 +69,37 @@ constexpr U bit_cast(T value) requires (sizeof(T) == sizeof(U))
return __builtin_bit_cast(U, value);
}
template<typename T>
constexpr T min(T a, T b)
template<is_enum T>
constexpr auto to_underlying(T value)
{
return (a <= b) ? a : b;
}
template<typename T>
constexpr T max(T a, T b)
{
return (a >= b) ? a : b;
}
constexpr uint64_t round_up_pow2(uint64_t v)
{
ASL_ASSERT(v <= 0x8000'0000'0000'0000);
v -= 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v |= v >> 32;
return v + 1;
}
constexpr bool is_pow2(isize_t v)
{
return v > 0 && ((v - 1) & v) == 0;
return static_cast<underlying_t<T>>(value);
}
// NOLINTBEGIN(*-macro-parentheses)
#define ASL_DELETE_COPY(T) \
T(const T&) = delete; \
T& operator=(const T&) = delete;
T& operator=(const T&) = delete
#define ASL_DELETE_MOVE(T) \
T(T&&) = delete; \
T& operator=(T&&) = delete;
T& operator=(T&&) = delete
#define ASL_DELETE_COPY_MOVE(T) \
ASL_DELETE_COPY(T) \
ASL_DELETE_COPY(T); \
ASL_DELETE_MOVE(T)
#define ASL_DEFAULT_COPY(T) \
T(const T&) = default; \
T& operator=(const T&) = default;
T& operator=(const T&) = default
#define ASL_DEFAULT_MOVE(T) \
T(T&&) = default; \
T& operator=(T&&) = default;
T& operator=(T&&) = default
#define ASL_DEFAULT_COPY_MOVE(T) \
ASL_DEFAULT_COPY(T) \
ASL_DEFAULT_COPY(T); \
ASL_DEFAULT_MOVE(T)
// NOLINTEND(*-macro-parentheses)
#define ASL_CONCAT2(A, B) A##B
#define ASL_CONCAT(A, B) ASL_CONCAT2(A, B)

View File

@ -12,28 +12,28 @@ template<typename T> static constexpr int identify(T&&) { return 4; }
struct IdentifySelf
{
constexpr int get(this auto&& self) { return identify(ASL_FWD(self)); }
constexpr int get(this auto&& self) { return identify(std::forward<decltype(self)>(self)); }
};
static int get_const_lref(const IdentifySelf& i) { return ASL_FWD(i).get(); }
static int get_const_rref(const IdentifySelf&& i) { return ASL_FWD(i).get(); }
static int get_lref(IdentifySelf& i) { return ASL_FWD(i).get(); }
static int get_rref(IdentifySelf&& i) { return ASL_FWD(i).get(); }
static int get_const_lref(const IdentifySelf& i) { return i.get(); }
static int get_const_rref(const IdentifySelf&& i) { return std::move(i).get(); } // NOLINT
static int get_lref(IdentifySelf& i) { return i.get(); }
static int get_rref(IdentifySelf&& i) { return std::move(i).get(); } // NOLINT
ASL_TEST(forward)
{
IdentifySelf id;
ASL_TEST_EXPECT(get_const_lref(id) == 1);
IdentifySelf id{};
ASL_TEST_EXPECT(get_const_lref(IdentifySelf{}) == 1);
ASL_TEST_EXPECT(get_lref(id) == 3);
ASL_TEST_EXPECT(get_const_rref(ASL_MOVE(id)) == 2);
ASL_TEST_EXPECT(get_rref(ASL_MOVE(id)) == 4);
ASL_TEST_EXPECT(get_const_rref(IdentifySelf{}) == 2);
ASL_TEST_EXPECT(get_rref(IdentifySelf{}) == 4);
}
ASL_TEST(move)
{
IdentifySelf id;
ASL_TEST_EXPECT(id.get() == 3);
ASL_TEST_EXPECT(ASL_MOVE(id).get() == 4);
ASL_TEST_EXPECT(IdentifySelf{}.get() == 4);
}
struct Level1
@ -51,33 +51,50 @@ struct Level3
Level2 deeper;
};
static int get_const_lref(const Level3& i) { return ASL_FWD(i).deeper.deeper.id.get(); }
static int get_const_rref(const Level3&& i) { return ASL_FWD(i).deeper.deeper.id.get(); }
static int get_lref(Level3& i) { return ASL_FWD(i).deeper.deeper.id.get(); }
static int get_rref(Level3&& i) { return ASL_FWD(i).deeper.deeper.id.get(); }
static int get_const_lref(const Level3& i) { return i.deeper.deeper.id.get(); }
static int get_const_rref(const Level3&& i) { return std::move(i).deeper.deeper.id.get(); }
static int get_lref(Level3& i) { return i.deeper.deeper.id.get(); }
static int get_rref(Level3&& i) { return std::move(i).deeper.deeper.id.get(); }
ASL_TEST(forward2)
{
Level3 id{};
ASL_TEST_EXPECT(get_const_lref(id) == 1);
ASL_TEST_EXPECT(get_lref(id) == 3);
ASL_TEST_EXPECT(get_const_rref(ASL_MOVE(id)) == 2);
ASL_TEST_EXPECT(get_rref(ASL_MOVE(id)) == 4);
ASL_TEST_EXPECT(get_const_rref(Level3{}) == 2);
ASL_TEST_EXPECT(get_rref(Level3{}) == 4);
}
template<typename T>
static int test_fwd_like(T&& t)
static int test_fwd_like(T) // NOLINT
{
IdentifySelf id;
return ASL_FWD_LIKE(decltype(t), id).get();
const IdentifySelf id;
return std::forward_like<T>(id).get();
}
ASL_TEST(forward_like)
{
int x{};
ASL_TEST_EXPECT(test_fwd_like<const int&>(x) == 1);
ASL_TEST_EXPECT(test_fwd_like<const int&>(7) == 1);
ASL_TEST_EXPECT(test_fwd_like<int&>(x) == 3);
ASL_TEST_EXPECT(test_fwd_like<const int&&>(ASL_MOVE(x)) == 2);
ASL_TEST_EXPECT(test_fwd_like<int&&>(ASL_MOVE(x)) == 4);
ASL_TEST_EXPECT(test_fwd_like<const int&&>(8) == 2);
ASL_TEST_EXPECT(test_fwd_like<int&&>(9) == 4);
}
enum class Enum : int // NOLINT
{
kOne = 1,
kTwo = 2,
};
enum Enum2 : int // NOLINT
{
kOne = 1,
kTwo = 2,
};
static_assert(asl::to_underlying(Enum::kOne) == 1);
static_assert(asl::to_underlying(Enum::kTwo) == 2);
static_assert(asl::to_underlying(kOne) == 1);
static_assert(asl::to_underlying(kTwo) == 2);

View File

@ -13,6 +13,7 @@ cc_library(
],
deps = [
"//asl/memory",
"//asl/memory:allocator",
"//asl/base",
"//asl/types:span",
"//asl/hashing",
@ -20,6 +21,22 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "chunked_buffer",
hdrs = [
"chunked_buffer.hpp",
],
deps = [
"//asl/memory",
"//asl/memory:allocator",
"//asl/base",
"//asl/containers:buffer",
"//asl/types:array",
"//asl/types:maybe_uninit",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "hash_set",
hdrs = [
@ -28,6 +45,7 @@ cc_library(
deps = [
"//asl/base",
"//asl/memory",
"//asl/memory:allocator",
"//asl/types:maybe_uninit",
"//asl/hashing",
],
@ -42,6 +60,7 @@ cc_library(
deps = [
"//asl/base",
"//asl/memory",
"//asl/memory:allocator",
"//asl/hashing",
":hash_set",
],
@ -72,6 +91,7 @@ cc_library(
],
) for name in [
"buffer",
"chunked_buffer",
"hash_map",
"hash_set",
"intrusive_list",

View File

@ -9,13 +9,15 @@
#include "asl/memory/memory.hpp"
#include "asl/base/annotations.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/bit.hpp"
#include "asl/types/span.hpp"
#include "asl/hashing/hash.hpp"
namespace asl
{
template<is_object T, allocator Allocator = DefaultAllocator>
template<typename T, allocator Allocator = DefaultAllocator>
requires is_object<T> && moveable<T>
class buffer
{
T* m_data{};
@ -46,7 +48,7 @@ private:
static_assert(align_of<T*> == align_of<isize_t>);
static_assert(align_of<T*> == align_of<size_t>);
constexpr size_t load_size_encoded() const
[[nodiscard]] constexpr size_t load_size_encoded() const
{
size_t s{};
asl::memcpy(&s, &m_size_encoded_, sizeof(size_t));
@ -80,28 +82,31 @@ private:
{
return is_on_heap(size_encoded)
? static_cast<isize_t>(size_encoded & (~kOnHeapMask))
: static_cast<isize_t>(size_encoded >> 56);
: static_cast<isize_t>(size_encoded >> 56U);
}
}
constexpr bool is_on_heap() const
[[nodiscard]] constexpr bool is_on_heap() const
{
return is_on_heap(load_size_encoded());
}
constexpr T* push_uninit()
constexpr void* push_uninit()
{
isize_t sz = size();
const isize_t sz = size();
resize_uninit_inner(sz + 1);
return data() + sz;
}
constexpr void resize_uninit_inner(isize_t new_size)
{
isize_t old_size = size();
if (!trivially_destructible<T> && new_size < old_size)
if constexpr (!trivially_destructible<T>)
{
destroy_n(data() + new_size, old_size - new_size);
const isize_t old_size = size();
if (new_size < old_size)
{
destroy_n(data() + new_size, old_size - new_size);
}
}
reserve_capacity(new_size);
set_size(new_size);
@ -110,7 +115,9 @@ private:
constexpr void set_size_inline(isize_t new_size)
{
ASL_ASSERT(new_size >= 0 && new_size <= kInlineCapacity);
size_t size_encoded = (load_size_encoded() & size_t{0x00ff'ffff'ffff'ffff}) | (bit_cast<size_t>(new_size) << 56);
const size_t size_encoded =
(load_size_encoded() & size_t{0x00ff'ffff'ffff'ffff})
| (bit_cast<size_t>(new_size) << 56U);
store_size_encoded(size_encoded);
}
@ -132,20 +139,28 @@ private:
{
if (other.is_on_heap())
{
// If the other in on heap, destroy here and adopt their
// data. We'll soon adopt the allocator as well.
destroy();
m_data = other.m_data;
m_capacity = other.m_capacity;
store_size_encoded(other.load_size_encoded());
}
else if (trivially_move_constructible<T>)
{
destroy();
asl::memcpy(this, &other, kInlineRegionSize);
}
else if (!assign || m_allocator == other.m_allocator)
{
isize_t other_n = other.size();
isize_t this_n = size();
// If allocators are compatible, we can move other's inline
// data here, even if it's on heap here, because that
// memory can be freed by other's allocator, which we will
// soon adopt.
//
// @Note There is an argument to be made for not doing this and
// instead destroying our data here and moving into inline
// storage, which frees one allocation. But also this avoids
// freeing. So I don't know.
// Maybe If this storage is much much larger than the inline
// data, it's worth freeing.
const isize_t other_n = other.size();
const isize_t this_n = size();
resize_uninit_inner(other_n);
if (other_n <= this_n)
{
@ -159,25 +174,42 @@ private:
}
else
{
// Otherwise, if we have to free, because the allocators are
// not compatible, well we free and move into our inline
// storage region.
// There is an optimization here when the data is trivially
// move constructible (which implies trivially destructible),
// we copy the whole inline region, which includes the size.
// Very magic.
destroy();
isize_t n = other.size();
ASL_ASSERT(n <= kInlineCapacity);
relocate_uninit_n(data(), other.data(), n);
set_size_inline(n);
if constexpr (trivially_move_constructible<T>)
{
ASL_ASSERT(!is_on_heap());
asl::memcpy(this, &other, kInlineRegionSize);
}
else
{
const isize_t n = other.size();
ASL_ASSERT(n <= kInlineCapacity);
resize_uninit_inner(n);
ASL_ASSERT(!is_on_heap());
relocate_uninit_n(data(), other.data(), n);
}
}
other.set_size_inline(0);
if (assign)
{
m_allocator = ASL_MOVE(other.m_allocator);
m_allocator = std::move(other.m_allocator);
}
}
void copy_range(span<const T> to_copy)
{
isize_t this_size = size();
isize_t new_size = to_copy.size();
const isize_t this_size = size();
const isize_t new_size = to_copy.size();
resize_uninit_inner(to_copy.size());
ASL_ASSERT(capacity() >= new_size);
@ -209,7 +241,7 @@ private:
// NOLINTNEXTLINE(*-pointer-arithmetic)
for (T* it = data_ptr + old_size; it < end; ++it)
{
construct_at<T>(it, ASL_FWD(args)...);
construct_at<T>(it, std::forward<Args>(args)...);
}
}
@ -224,11 +256,11 @@ public:
}
explicit constexpr buffer(Allocator allocator)
: m_allocator{ASL_MOVE(allocator)}
: m_allocator{std::move(allocator)}
{}
explicit constexpr buffer(span<const T> s, Allocator allocator)
: m_allocator{ASL_MOVE(allocator)}
: m_allocator{std::move(allocator)}
{
copy_range(s);
}
@ -241,10 +273,9 @@ public:
}
constexpr buffer(buffer&& other)
requires moveable<T>
: buffer(ASL_MOVE(other.m_allocator))
: buffer(std::move(other.m_allocator))
{
move_from_other(ASL_MOVE(other), false);
move_from_other(std::move(other), false);
}
constexpr buffer& operator=(const buffer& other)
@ -256,10 +287,9 @@ public:
}
constexpr buffer& operator=(buffer&& other)
requires moveable<T>
{
if (&other == this) { return *this; }
move_from_other(ASL_MOVE(other), true);
move_from_other(std::move(other), true);
return *this;
}
@ -268,12 +298,22 @@ public:
destroy();
}
constexpr isize_t size() const
constexpr Allocator allocator_copy() const
requires copy_constructible<Allocator>
{
return m_allocator;
}
constexpr Allocator& allocator() { return m_allocator; }
[[nodiscard]] constexpr isize_t size() const
{
return decode_size(load_size_encoded());
}
constexpr isize_t capacity() const
[[nodiscard]] constexpr bool is_empty() const { return size() == 0; }
[[nodiscard]] constexpr isize_t capacity() const
{
if constexpr (kInlineCapacity == 0)
{
@ -287,7 +327,7 @@ public:
void clear()
{
isize_t current_size = size();
const isize_t current_size = size();
if (current_size == 0) { return; }
destroy_n(data(), current_size);
@ -311,12 +351,11 @@ public:
void reserve_capacity(isize_t new_capacity)
{
ASL_ASSERT(new_capacity >= 0);
ASL_ASSERT_RELEASE(new_capacity <= 0x4000'0000'0000'0000);
if (new_capacity <= capacity()) { return; }
ASL_ASSERT(new_capacity > kInlineCapacity);
new_capacity = static_cast<isize_t>(round_up_pow2(static_cast<uint64_t>(new_capacity)));
new_capacity = static_cast<isize_t>(bit_ceil(static_cast<uint64_t>(new_capacity)));
T* old_data = data();
const isize_t old_capacity = capacity();
@ -328,12 +367,12 @@ public:
if (currently_on_heap && trivially_move_constructible<T>)
{
m_data = reinterpret_cast<T*>(m_allocator.realloc(m_data, old_layout, new_layout));
m_data = static_cast<T*>(m_allocator.realloc(m_data, old_layout, new_layout));
m_capacity = new_capacity;
return;
}
T* new_data = reinterpret_cast<T*>(m_allocator.alloc(new_layout));
T* new_data = static_cast<T*>(m_allocator.alloc(new_layout));
relocate_uninit_n(new_data, old_data, current_size);
@ -348,17 +387,16 @@ public:
}
constexpr void resize_uninit(isize_t new_size)
requires trivially_default_constructible<T> && trivially_destructible<T>
requires trivially_default_constructible<T>
{
reserve_capacity(new_size);
set_size(new_size);
resize_uninit_inner(new_size);
}
constexpr void resize_zero(isize_t new_size)
requires trivially_default_constructible<T> && trivially_destructible<T>
requires trivially_default_constructible<T>
{
isize_t old_size = size();
resize_uninit(new_size);
const isize_t old_size = size();
resize_uninit_inner(new_size);
if (new_size > old_size)
{
@ -369,7 +407,14 @@ public:
void resize(isize_t new_size)
requires default_constructible<T>
{
resize_inner(new_size);
if constexpr (trivially_default_constructible<T>)
{
resize_zero(new_size);
}
else
{
resize_inner(new_size);
}
}
void resize(isize_t new_size, const T& value)
@ -380,63 +425,68 @@ public:
constexpr T& push(auto&&... args)
requires constructible_from<T, decltype(args)&&...>
{
T* uninit = push_uninit();
T* init = construct_at<T>(uninit, ASL_FWD(args)...);
void* uninit = push_uninit();
T* init = construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
return *init;
}
auto data(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = as_ptr_t<copy_const_t<un_ref_t<decltype(self)>, T>>;
// NOLINTNEXTLINE(*-reinterpret-cast)
auto&& buffer = reinterpret_cast<copy_cref_t<decltype(self), class buffer>>(self);
if constexpr (kInlineCapacity == 0)
{
return return_type{ self.m_data };
return return_type{ buffer.m_data };
}
else
{
return self.is_on_heap() ? return_type{ self.m_data } : reinterpret_cast<return_type>(&self);
return buffer.is_on_heap()
? return_type{ buffer.m_data }
// NOLINTNEXTLINE(*-reinterpret-cast)
: std::launder(reinterpret_cast<return_type>(&buffer));
}
}
constexpr auto begin(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return contiguous_iterator<type>{self.data()};
}
constexpr auto end(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return contiguous_iterator<type>{self.data() + self.size()};
}
constexpr operator span<const T>() const // NOLINT(*-explicit-conversions)
constexpr operator span<const T>() const // NOLINT(*explicit*)
{
return as_span();
}
constexpr operator span<T>() // NOLINT(*-explicit-conversions)
constexpr operator span<T>() // NOLINT(*explicit*)
{
return as_span();
}
constexpr auto as_span(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return span<type>{self.data(), self.size()};
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i <= self.size());
return ASL_FWD_LIKE(decltype(self), ASL_FWD(self).data()[i]);
ASL_ASSERT(i >= 0 && i < self.size());
return std::forward_like<decltype(self)>(std::forward<decltype(self)>(self).data()[i]);
}
template<typename H>
requires hashable<T>
friend H AslHashValue(H h, const buffer& b)
{
return H::combine_contiguous(ASL_MOVE(h), b.as_span());
return H::combine_contiguous(std::move(h), b.as_span());
}
};

View File

@ -6,6 +6,7 @@
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/tests/counting_allocator.hpp"
struct Big
{
@ -14,6 +15,7 @@ struct Big
static_assert(asl::buffer<int32_t>::kInlineCapacity == 5);
static_assert(asl::buffer<int64_t>::kInlineCapacity == 2);
static_assert(asl::buffer<void*>::kInlineCapacity == 2);
static_assert(asl::buffer<char>::kInlineCapacity == 23);
static_assert(asl::buffer<Big>::kInlineCapacity == 0);
@ -24,37 +26,12 @@ ASL_TEST(default_size)
ASL_TEST_EXPECT(b1.capacity() == 5);
ASL_TEST_EXPECT(static_cast<const void*>(b1.data()) == &b1);
asl::buffer<Big> b2;
const asl::buffer<Big> b2;
ASL_TEST_EXPECT(b2.size() == 0);
ASL_TEST_EXPECT(b2.capacity() == 0);
ASL_TEST_EXPECT(b2.data() == nullptr);
}
struct CounterAllocator
{
isize_t* count;
void* alloc(const asl::layout& layout) const
{
*count += 1;
return asl::GlobalHeap::alloc(layout);
}
void* realloc(void* ptr, const asl::layout& old, const asl::layout& new_layout) const
{
*count += 1;
return asl::GlobalHeap::realloc(ptr, old, new_layout);
}
static void dealloc(void* ptr, const asl::layout& layout)
{
asl::GlobalHeap::dealloc(ptr, layout);
}
constexpr bool operator==(const CounterAllocator&) const { return true; }
};
static_assert(asl::allocator<CounterAllocator>);
struct IncompatibleAllocator
{
static void* alloc(const asl::layout& layout)
@ -76,35 +53,37 @@ struct IncompatibleAllocator
};
static_assert(asl::allocator<IncompatibleAllocator>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(reserve_capacity)
{
isize_t count = 0;
asl::buffer<int32_t, CounterAllocator> b(CounterAllocator{&count});
CountingAllocator::Stats stats;
asl::buffer<int32_t, CountingAllocator> b(CountingAllocator{&stats});
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(4);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(12);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 12);
ASL_TEST_EXPECT(count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(13);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 13);
ASL_TEST_EXPECT(count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(130);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 130);
ASL_TEST_EXPECT(count == 2);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push)
{
asl::buffer<int32_t> b;
@ -137,7 +116,7 @@ ASL_TEST(push)
ASL_TEST(from_span)
{
int data[] = {1, 2, 4, 8};
const int data[] = {1, 2, 4, 8};
asl::buffer<int> b{data};
ASL_TEST_EXPECT(b.size() == 4);
@ -156,13 +135,21 @@ struct MoveableType
MoveableType(const MoveableType&) = delete;
MoveableType(MoveableType&& other) : moved{other.moved + 1}, value{other.value} {}
MoveableType& operator=(const MoveableType&) = delete;
MoveableType& operator=(MoveableType&&) = delete;
MoveableType& operator=(MoveableType&& other)
{
if (this == &other) { return *this; }
moved = other.moved + 1;
value = other.value;
return *this;
}
~MoveableType() = default;
};
static_assert(!asl::trivially_copy_constructible<MoveableType>);
static_assert(!asl::trivially_move_constructible<MoveableType>);
static_assert(!asl::copyable<MoveableType>);
static_assert(asl::move_constructible<MoveableType>);
static_assert(asl::moveable<MoveableType>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_move)
{
asl::buffer<MoveableType> b;
@ -208,6 +195,30 @@ ASL_TEST(push_move)
ASL_TEST_EXPECT(b[3].moved == 1);
ASL_TEST_EXPECT(b[4].value == 4);
ASL_TEST_EXPECT(b[4].moved == 0);
asl::buffer<MoveableType> b2 = std::move(b);
ASL_TEST_EXPECT(b2[0].value == 0);
ASL_TEST_EXPECT(b2[0].moved == 2);
ASL_TEST_EXPECT(b2[1].value == 1);
ASL_TEST_EXPECT(b2[1].moved == 2);
ASL_TEST_EXPECT(b2[2].value == 2);
ASL_TEST_EXPECT(b2[2].moved == 1);
ASL_TEST_EXPECT(b2[3].value == 3);
ASL_TEST_EXPECT(b2[3].moved == 1);
ASL_TEST_EXPECT(b2[4].value == 4);
ASL_TEST_EXPECT(b2[4].moved == 0);
b = std::move(b2);
ASL_TEST_EXPECT(b[0].value == 0);
ASL_TEST_EXPECT(b[0].moved == 2);
ASL_TEST_EXPECT(b[1].value == 1);
ASL_TEST_EXPECT(b[1].moved == 2);
ASL_TEST_EXPECT(b[2].value == 2);
ASL_TEST_EXPECT(b[2].moved == 1);
ASL_TEST_EXPECT(b[3].value == 3);
ASL_TEST_EXPECT(b[3].moved == 1);
ASL_TEST_EXPECT(b[4].value == 4);
ASL_TEST_EXPECT(b[4].moved == 0);
}
ASL_TEST(clear)
@ -275,14 +286,13 @@ ASL_TEST(move_construct_from_heap)
buf.push(&d[2]);
{
asl::buffer<DestructorObserver> buf2(ASL_MOVE(buf));
const asl::buffer<DestructorObserver> buf2(std::move(buf));
ASL_TEST_EXPECT(buf2.size() == 3);
ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false);
ASL_TEST_EXPECT(d[2] == false);
}
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true);
ASL_TEST_EXPECT(d[2] == true);
@ -294,12 +304,11 @@ ASL_TEST(move_construct_inline_trivial)
buf.push(1U);
buf.push(2U);
asl::buffer<uint64_t> buf2(ASL_MOVE(buf));
asl::buffer<uint64_t> buf2(std::move(buf));
ASL_TEST_EXPECT(buf2[0] == 1U);
ASL_TEST_EXPECT(buf2[1] == 2U);
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(buf.size() == 0);
}
ASL_TEST(move_construct_from_inline_non_trivial)
@ -310,17 +319,17 @@ ASL_TEST(move_construct_from_inline_non_trivial)
buf.push(&d[1]);
{
asl::buffer<DestructorObserver> buf2(ASL_MOVE(buf));
const asl::buffer<DestructorObserver> buf2(std::move(buf));
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false);
}
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_from_heap)
{
bool d[6]{};
@ -344,9 +353,8 @@ ASL_TEST(move_assign_from_heap)
ASL_TEST_EXPECT(d[4] == false);
ASL_TEST_EXPECT(d[5] == false);
buf2 = ASL_MOVE(buf);
buf2 = std::move(buf);
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(buf2.size() == 3);
ASL_TEST_EXPECT(d[0] == false);
@ -367,24 +375,23 @@ ASL_TEST(move_assign_from_heap)
ASL_TEST(move_assign_trivial_heap_to_inline)
{
isize_t alloc_count = 0;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}};
CountingAllocator::Stats stats;
asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1);
buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3);
buf2.push(4);
buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf = ASL_MOVE(buf2);
ASL_TEST_EXPECT(alloc_count == 1);
buf = std::move(buf2);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf.size() == 3);
ASL_TEST_EXPECT(buf2.size() == 0);
ASL_TEST_EXPECT(buf[0] == 3);
ASL_TEST_EXPECT(buf[1] == 4);
ASL_TEST_EXPECT(buf[2] == 5);
@ -392,28 +399,28 @@ ASL_TEST(move_assign_trivial_heap_to_inline)
ASL_TEST(move_assign_trivial_inline_to_heap)
{
isize_t alloc_count = 0;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}};
CountingAllocator::Stats stats;
asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1);
buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3);
buf2.push(4);
buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf2 = ASL_MOVE(buf);
ASL_TEST_EXPECT(alloc_count == 1);
buf2 = std::move(buf);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(buf2[0] == 1);
ASL_TEST_EXPECT(buf2[1] == 2);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_inline_to_heap)
{
bool d[6]{};
@ -430,26 +437,26 @@ ASL_TEST(move_assign_inline_to_heap)
buf2.push(&d[4]);
buf2.push(&d[5]);
buf2 = ASL_MOVE(buf);
buf2 = std::move(buf);
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true);
}
ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_from_inline_incompatible_allocator)
{
bool d[6]{};
@ -466,9 +473,8 @@ ASL_TEST(move_assign_from_inline_incompatible_allocator)
buf2.push(&d[4]);
buf2.push(&d[5]);
buf2 = ASL_MOVE(buf);
buf2 = std::move(buf);
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false);
@ -605,3 +611,6 @@ ASL_TEST(resize_zero)
}
}
static_assert(asl::same_as<decltype(asl::declval<asl::buffer<int>>().data()), int*>);
static_assert(asl::same_as<decltype(asl::declval<const asl::buffer<int>>().data()), const int*>);

View File

@ -0,0 +1,405 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/utility.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/numeric.hpp"
#include "asl/containers/buffer.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/types/array.hpp"
#include "asl/types/maybe_uninit.hpp"
namespace asl
{
template<
is_object T,
isize_t kChunkSize,
allocator Allocator = DefaultAllocator>
class chunked_buffer
{
static_assert(kChunkSize > 0 && is_pow2(kChunkSize));
using Chunk = array<maybe_uninit<T>, kChunkSize>;
static constexpr isize_t chunk_index(isize_t i)
{
static constexpr int kChunkSizeLog2 = countr_zero(uint64_t{kChunkSize});
return i >> kChunkSizeLog2;
}
static constexpr isize_t index_in_chunk(isize_t i)
{
static constexpr isize_t kMask = kChunkSize - 1;
return i & kMask;
}
struct PerChunkIterator
{
isize_t from_chunk;
isize_t to_chunk;
isize_t from_index_in_chunk;
isize_t to_index_in_chunk;
[[nodiscard]] constexpr bool has_more() const
{
return from_chunk <= to_chunk;
}
void advance()
{
from_chunk += 1;
from_index_in_chunk = 0;
}
[[nodiscard]] constexpr isize_t chunk() const { return from_chunk; }
span<maybe_uninit<T>> make_span(Chunk& chunk) const
{
isize_t from = from_index_in_chunk;
isize_t to = (from_chunk == to_chunk) ? to_index_in_chunk : kChunkSize - 1;
return chunk.as_span().subspan(from, to - from + 1);
}
};
PerChunkIterator make_index_iterator(isize_t from, isize_t to)
{
return PerChunkIterator {
chunk_index(from), chunk_index(to),
index_in_chunk(from), index_in_chunk(to)
};
}
buffer<Chunk*, Allocator> m_chunks;
isize_t m_size{};
void resize_uninit_inner(isize_t new_size)
{
ASL_ASSERT(new_size >= 0);
if constexpr (!trivially_destructible<T>)
{
const isize_t old_size = size();
if (new_size < old_size)
{
for (PerChunkIterator it = make_index_iterator(new_size, old_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
for (auto& el: span)
{
el.destroy_unsafe();
}
}
}
}
reserve_capacity(new_size);
m_size = new_size;
}
template<typename... Args>
void resize_construct(isize_t new_size, Args&&... args)
requires constructible_from<T, Args&&...>
{
const isize_t old_size = m_size;
resize_uninit_inner(new_size);
if (new_size > old_size)
{
for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
for (auto& uninit: span)
{
uninit.construct_unsafe(std::forward<Args>(args)...);
}
}
}
}
void copy_from(const chunked_buffer& other)
requires copyable<T>
{
const isize_t this_size = size();
isize_t to_copy_assign = asl::min(other.size(), this_size);
resize_uninit_inner(other.size());
for (PerChunkIterator it = make_index_iterator(0, to_copy_assign - 1);
it.has_more();
it.advance())
{
auto to_span = it.make_span(*m_chunks[it.chunk()]);
auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
copy_assign_n(
reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
to_span.size());
}
if (other.size() > this_size)
{
for (PerChunkIterator it = make_index_iterator(to_copy_assign, other.size() - 1);
it.has_more();
it.advance())
{
auto to_span = it.make_span(*m_chunks[it.chunk()]);
auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
copy_uninit_n(
reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
to_span.size());
}
}
ASL_ASSERT(size() == other.size());
}
public:
constexpr chunked_buffer()
requires default_constructible<Allocator>
= default;
explicit constexpr chunked_buffer(Allocator allocator)
: m_chunks{std::move(allocator)}
{}
constexpr chunked_buffer(const chunked_buffer& other)
requires copyable<T> && copy_constructible<Allocator>
: m_chunks{other.m_chunks.allocator_copy()}
{
copy_from(other);
}
constexpr chunked_buffer(chunked_buffer&& other)
: m_chunks{std::move(other.m_chunks)}
, m_size{asl::exchange(other.m_size, 0)}
{
ASL_ASSERT(other.m_chunks.size() == 0);
}
constexpr chunked_buffer& operator=(const chunked_buffer& other)
requires copyable<T>
{
if (&other == this) { return *this; }
copy_from(other);
return *this;
}
constexpr chunked_buffer& operator=(chunked_buffer&& other)
{
if (&other == this) { return *this; }
destroy();
m_chunks = std::move(other.m_chunks);
m_size = asl::exchange(other.m_size, 0);
ASL_ASSERT(other.m_chunks.size() == 0);
return *this;
}
~chunked_buffer()
{
destroy();
}
void clear()
{
if constexpr (trivially_destructible<T>)
{
m_size = 0;
}
else if (m_size > 0)
{
resize_uninit_inner(0);
ASL_ASSERT(m_size == 0);
}
}
void destroy()
{
clear();
ASL_ASSERT(size() == 0);
for (Chunk* chunk: m_chunks)
{
alloc_delete(m_chunks.allocator(), chunk);
}
m_chunks.destroy();
}
[[nodiscard]] constexpr isize_t size() const { return m_size; }
[[nodiscard]] constexpr bool is_empty() const { return size() == 0; }
[[nodiscard]] constexpr isize_t capacity() const
{
return m_chunks.size() * kChunkSize;
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i < self.m_size);
return std::forward_like<decltype(self)>(
(*std::forward<decltype(self)>(self).m_chunks[chunk_index(i)])
[index_in_chunk(i)].as_init_unsafe()
);
}
constexpr T& push(auto&&... args)
requires constructible_from<T, decltype(args)&&...>
{
const isize_t chunk = chunk_index(m_size);
const isize_t in_chunk = index_in_chunk(m_size);
if (m_size == capacity())
{
resize_uninit_inner(m_size + 1);
}
else
{
m_size += 1;
}
void* uninit = &(*m_chunks[chunk])[in_chunk];
return *construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
}
void reserve_capacity(isize_t new_capacity)
{
new_capacity = round_up_pow2(new_capacity, kChunkSize);
if (new_capacity <= capacity()) { return; }
const isize_t required_chunks = new_capacity / kChunkSize;
const isize_t additional_chunks = required_chunks - m_chunks.size();
ASL_ASSERT(additional_chunks > 0);
m_chunks.reserve_capacity(required_chunks);
for (isize_t i = 0; i < additional_chunks; ++i)
{
// @Todo(C++26) _unsafe shouldn't be needed with trivial unions
auto* chunk = alloc_uninit_unsafe<Chunk>(m_chunks.allocator());
m_chunks.push(chunk);
}
}
void resize(isize_t new_size)
requires default_constructible<T>
{
if constexpr (trivially_default_constructible<T>)
{
resize_zero(new_size);
}
else
{
resize_construct(new_size);
}
}
void resize(isize_t new_size, const T& value)
requires copy_constructible<T>
{
resize_construct(new_size, value);
}
void resize_zero(isize_t new_size)
requires trivially_default_constructible<T>
{
const isize_t old_size = m_size;
resize_uninit_inner(new_size);
if (new_size > old_size)
{
for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
asl::memzero(span.data(), span.size_bytes());
}
}
}
void resize_uninit(isize_t new_size)
requires trivially_default_constructible<T>
{
resize_uninit_inner(new_size);
}
void pop()
{
ASL_ASSERT(size() > 0);
resize_uninit_inner(size() - 1);
}
template<typename Chunk>
class generic_iterator
{
isize_t m_index;
span<Chunk> m_chunks;
public:
constexpr generic_iterator(isize_t index, span<Chunk> chunks)
: m_index{index}
, m_chunks{chunks}
{}
constexpr generic_iterator& operator++()
{
m_index += 1;
return *this;
}
constexpr generic_iterator operator++(int)
{
auto tmp = *this;
m_index += 1;
return tmp;
}
constexpr bool operator==(this generic_iterator self, generic_iterator other)
{
ASL_ASSERT(self.m_chunks.data() == other.m_chunks.data());
return self.m_index == other.m_index;
}
constexpr auto& operator*(this generic_iterator self)
{
ASL_ASSERT(self.m_index >= 0);
return (*self.m_chunks[chunk_index(self.m_index)])[index_in_chunk(self.m_index)].as_init_unsafe();
}
constexpr auto* operator->(this generic_iterator self)
{
return &*self;
}
};
using iterator = generic_iterator<Chunk*>;
using const_iterator = generic_iterator<const Chunk* const>;
constexpr iterator begin() { return iterator{0, m_chunks}; }
constexpr iterator end() { return iterator{m_size, m_chunks}; }
constexpr const_iterator begin() const
{
return const_iterator{0, {m_chunks.data(), m_chunks.size()}};
}
constexpr const_iterator end() const
{
return const_iterator{m_size, {m_chunks.data(), m_chunks.size()}};
}
};
} // namespace asl

View File

@ -0,0 +1,377 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/tests/counting_allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
static_assert(asl::moveable<asl::chunked_buffer<int, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<Copyable, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<MoveableOnly, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<Pinned, 8>>);
static_assert(asl::copyable<asl::chunked_buffer<int, 8>>);
static_assert(asl::copyable<asl::chunked_buffer<Copyable, 8>>);
static_assert(!asl::copyable<asl::chunked_buffer<MoveableOnly, 8>>);
static_assert(!asl::copyable<asl::chunked_buffer<Pinned, 8>>);
ASL_TEST(reserve)
{
asl::chunked_buffer<int, 16> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(1);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(5);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(16);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(35);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(12);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 0);
}
ASL_TEST(resize_uninit)
{
asl::chunked_buffer<int, 16> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize_uninit(1);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 1);
b.resize_uninit(5);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 5);
b.resize_uninit(16);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 16);
b.resize_uninit(35);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 35);
b.resize_uninit(12);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 12);
}
ASL_TEST(resize_zero)
{
asl::chunked_buffer<int, 4> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize_zero(2);
for (isize_t i = 0; i < 2; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
b.resize_zero(18);
for (isize_t i = 0; i < 18; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
}
ASL_TEST(resize)
{
asl::chunked_buffer<int, 4> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize(10);
for (isize_t i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
b.resize(20, 8);
for (isize_t i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
for (isize_t i = 10; i < 20; ++i)
{
ASL_TEST_EXPECT(b[i] == 8);
}
}
ASL_TEST(push)
{
asl::chunked_buffer<int, 4> b;
for (int i = 0; i < 100; ++i)
{
b.push(i);
}
for (int i = 0; i < 100; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
b.resize(1000);
for (int i = 0; i < 100; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
}
ASL_TEST(pop)
{
asl::chunked_buffer<int, 2> b;
for (int i = 0; i < 8; ++i)
{
b.push(i);
}
ASL_TEST_EXPECT(b.size() == 8);
b.pop();
ASL_TEST_EXPECT(b.size() == 7);
for (int i = 0; i < 7; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
}
ASL_TEST(pop_destruct)
{
bool d[3]{};
asl::chunked_buffer<DestructorObserver, 16> b;
b.push(&d[0]);
b.push(&d[1]);
b.push(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
b.pop();
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(d[2]);
b.pop();
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(clear_destroy)
{
bool destroyed[5]{};
asl::chunked_buffer<DestructorObserver, 2> buf;
for (bool& d: destroyed)
{
buf.push(&d); // NOLINT
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
buf.clear();
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(d);
}
}
ASL_TEST(alloc_count) // NOLINT
{
CountingAllocator::Stats stats;
asl::chunked_buffer<int, 4, CountingAllocator> buf{CountingAllocator{&stats}};
ASL_TEST_EXPECT(stats.alive_bytes == 0);
ASL_TEST_EXPECT(stats.alloc_count == 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf.push(1);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf.push(2);
buf.push(3);
buf.push(4);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf.push(5);
buf.push(6);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
buf.resize(8, 8);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
buf.resize(32, 8);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.resize(16, 0);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.clear();
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.destroy();
ASL_TEST_EXPECT(stats.alive_bytes == 0);
ASL_TEST_EXPECT(stats.dealloc_count == 9);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
}
ASL_TEST(move)
{
bool destroyed[5]{};
{
asl::chunked_buffer<DestructorObserver, 2> buf;
for (bool& d: destroyed)
{
buf.push(&d); // NOLINT
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
asl::chunked_buffer<DestructorObserver, 2> buf2 = std::move(buf);
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
buf = std::move(buf2);
buf2.destroy();
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(d);
}
}
ASL_TEST(copy) // NOLINT
{
asl::chunked_buffer<int, 4> buf;
for (int i = 0; i < 10; ++i) { buf.push(i); }
asl::chunked_buffer<int, 4> buf2 = buf;
ASL_TEST_EXPECT(buf.size() == 10);
ASL_TEST_EXPECT(buf2.size() == 10);
for (int i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(buf[i] == i);
ASL_TEST_EXPECT(buf2[i] == i);
}
buf2.resize(5);
buf = buf2;
ASL_TEST_EXPECT(buf.size() == 5);
ASL_TEST_EXPECT(buf2.size() == 5);
for (int i = 0; i < 5; ++i)
{
ASL_TEST_EXPECT(buf[i] == i);
ASL_TEST_EXPECT(buf2[i] == i);
}
buf.clear();
buf.resize(80, 12);
buf2 = buf;
ASL_TEST_EXPECT(buf.size() == 80);
ASL_TEST_EXPECT(buf2.size() == 80);
for (int i = 0; i < 80; ++i)
{
ASL_TEST_EXPECT(buf[i] == 12);
ASL_TEST_EXPECT(buf2[i] == 12);
}
}
ASL_TEST(iterator)
{
asl::chunked_buffer<int, 4> buf;
for (int i = 0; i < 30; ++i) { buf.push(100 + i); }
auto it = buf.begin();
auto end = buf.end();
for (int i = 0; i < 30; ++i)
{
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 100 + i);
it++;
}
ASL_TEST_EXPECT(it == end);
static_assert(asl::same_as<decltype(*it), int&>);
asl::chunked_buffer<int, 8> buf2;
ASL_TEST_EXPECT(buf2.begin() == buf2.end());
}
ASL_TEST(const_iterator)
{
asl::chunked_buffer<int, 4> buf_value;
for (int i = 0; i < 30; ++i) { buf_value.push(100 + i); }
const auto& buf = buf_value;
auto it = buf.begin();
auto end = buf.end();
for (int i = 0; i < 30; ++i)
{
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 100 + i);
++it;
}
ASL_TEST_EXPECT(it == end);
static_assert(asl::same_as<decltype(*it), const int&>);
asl::chunked_buffer<int, 8> buf2;
ASL_TEST_EXPECT(buf2.begin() == buf2.end());
}

View File

@ -77,25 +77,17 @@ public:
constexpr hash_map() requires default_constructible<Allocator> = default;
explicit constexpr hash_map(Allocator allocator)
: Base{ASL_MOVE(allocator)}
: Base{std::move(allocator)}
{}
hash_map(const hash_map&) requires copyable<K> && copyable<V> = default;
hash_map& operator=(const hash_map&) requires copyable<K> && copyable<V> = default;
hash_map(hash_map&&) = default;
hash_map& operator=(hash_map&&) = default;
~hash_map() = default;
using Base::destroy;
using Base::clear;
using Base::size;
using Base::is_empty;
using Base::remove;
using Base::contains;
@ -122,7 +114,7 @@ public:
{
ASL_ASSERT((Base::m_tags[result.first_available_index] & Base::kHasValue) == 0);
Base::m_values[result.first_available_index].construct_unsafe(ASL_MOVE(Base::m_values[result.already_present_index].as_init_unsafe()));
Base::m_values[result.first_available_index].construct_unsafe(std::move(Base::m_values[result.already_present_index].as_init_unsafe()));
Base::m_values[result.already_present_index].destroy_unsafe();
Base::m_tags[result.first_available_index] = result.tag;
@ -133,17 +125,17 @@ public:
if constexpr (sizeof...(Args1) == 0 && assignable_from<V&, Arg0&&>)
{
Base::m_values[result.first_available_index].as_init_unsafe().value = ASL_FWD(arg0);
Base::m_values[result.first_available_index].as_init_unsafe().value = std::forward<Arg0>(arg0);
}
else
{
Base::m_values[result.first_available_index].as_init_unsafe().value = ASL_MOVE(V{ASL_FWD(arg0), ASL_FWD(args1)...});
Base::m_values[result.first_available_index].as_init_unsafe().value = std::move(V{std::forward<Arg0>(arg0), std::forward<Args1>(args1)...});
}
}
else
{
ASL_ASSERT((Base::m_tags[result.first_available_index] & Base::kHasValue) == 0);
Base::m_values[result.first_available_index].construct_unsafe(ASL_FWD(key), V{ASL_FWD(arg0), ASL_FWD(args1)...});
Base::m_values[result.first_available_index].construct_unsafe(std::forward<U>(key), V{std::forward<Arg0>(arg0), std::forward<Args1>(args1)...});
Base::m_tags[result.first_available_index] = result.tag;
Base::m_size += 1;
}
@ -155,7 +147,7 @@ public:
auto get(this auto&& self, const U& value)
requires key_hasher<KeyHasher, U> && key_comparator<KeyComparator, K, U>
{
using return_type = un_ref_t<copy_cref_t<decltype(self), V>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, V>*;
isize_t index = self.find_slot_lookup(value);
if (index >= 0)
{

View File

@ -5,6 +5,7 @@
#include "asl/testing/testing.hpp"
#include "asl/containers/hash_map.hpp"
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(default)
{
asl::hash_map<int, int> map;

View File

@ -7,6 +7,8 @@
#include "asl/base/annotations.hpp"
#include "asl/base/utility.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/bit.hpp"
#include "asl/base/numeric.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/memory/memory.hpp"
#include "asl/types/maybe_uninit.hpp"
@ -72,10 +74,10 @@ protected:
ASL_NO_UNIQUE_ADDRESS Allocator m_allocator;
constexpr isize_t max_size() const
[[nodiscard]] constexpr isize_t max_size() const
{
// Max load factor is 75%
return (m_capacity >> 1) + (m_capacity >> 2);
return (m_capacity >> 1) + (m_capacity >> 2); // NOLINT(*-signed-bitwise)
}
static isize_t size_to_capacity(isize_t size)
@ -83,7 +85,7 @@ protected:
ASL_ASSERT(size > 0);
return max<isize_t>(
kMinCapacity,
static_cast<isize_t>(round_up_pow2((static_cast<uint64_t>(size) * 4 + 2) / 3)));
static_cast<isize_t>(bit_ceil((static_cast<uint64_t>(size) * 4 + 2) / 3)));
}
static void insert_inner(
@ -115,7 +117,7 @@ protected:
*size += 1;
}
values[result.first_available_index].construct_unsafe(ASL_MOVE(value));
values[result.first_available_index].construct_unsafe(std::move(value));
tags[result.first_available_index] = result.tag;
}
@ -131,8 +133,8 @@ protected:
{
ASL_ASSERT(new_capacity >= kMinCapacity && is_pow2(new_capacity) && new_capacity > m_capacity);
auto* new_tags = reinterpret_cast<uint8_t*>(m_allocator.alloc(layout::array<uint8_t>(new_capacity)));
auto* new_values = reinterpret_cast<maybe_uninit<T>*>(m_allocator.alloc(layout::array<maybe_uninit<T>>(new_capacity)));
auto* new_tags = static_cast<uint8_t*>(m_allocator.alloc(layout::array<uint8_t>(new_capacity)));
auto* new_values = static_cast<maybe_uninit<T>*>(m_allocator.alloc(layout::array<maybe_uninit<T>>(new_capacity)));
asl::memzero(new_tags, new_capacity);
isize_t new_size = 0;
@ -144,7 +146,7 @@ protected:
{
if ((m_tags[i] & kHasValue) == 0) { continue; }
insert_inner(ASL_MOVE(m_values[i].as_init_unsafe()), new_tags, new_values, new_capacity, &new_size);
insert_inner(std::move(m_values[i].as_init_unsafe()), new_tags, new_values, new_capacity, &new_size);
// Destroy now so that destroy() has less things to do
m_values[i].destroy_unsafe();
@ -228,7 +230,6 @@ protected:
result.tag = static_cast<uint8_t>(hash & kHashMask) | kHasValue;
// NOLINTBEGIN(*-pointer-arithmetic)
for (
isize_t i = starting_index;
i != starting_index || result.first_available_index < 0;
@ -312,7 +313,7 @@ public:
{}
explicit constexpr hash_set(Allocator allocator)
: m_allocator{ASL_MOVE(allocator)}
: m_allocator{std::move(allocator)}
{}
hash_set(const hash_set& other)
@ -339,7 +340,7 @@ public:
, m_values{exchange(other.m_values, nullptr)}
, m_capacity{exchange(other.m_capacity, 0)}
, m_size{exchange(other.m_size, 0)}
, m_allocator{ASL_MOVE(other.m_allocator)}
, m_allocator{std::move(other.m_allocator)}
{}
hash_set& operator=(hash_set&& other)
@ -351,7 +352,7 @@ public:
m_values = exchange(other.m_values, nullptr);
m_capacity = exchange(other.m_capacity, 0);
m_size = exchange(other.m_size, 0);
m_allocator = ASL_MOVE(other.m_allocator);
m_allocator = std::move(other.m_allocator);
}
return *this;
}
@ -385,7 +386,9 @@ public:
}
}
constexpr isize_t size() const { return m_size; }
[[nodiscard]] constexpr isize_t size() const { return m_size; }
[[nodiscard]] constexpr bool is_empty() const { return m_size == 0; }
template<typename... Args>
void insert(Args&&... args)
@ -393,7 +396,7 @@ public:
{
maybe_grow_to_fit_one_more();
ASL_ASSERT(m_size < max_size());
insert_inner(ASL_MOVE(T{ASL_FWD(args)...}), m_tags, m_values, m_capacity, &m_size);
insert_inner(T{std::forward<Args>(args)...}, m_tags, m_values, m_capacity, &m_size);
}
template<typename U>

View File

@ -10,7 +10,7 @@
ASL_TEST(empty)
{
asl::hash_set<int> set;
const asl::hash_set<int> set;
ASL_TEST_EXPECT(set.size() == 0);
@ -42,7 +42,7 @@ ASL_TEST(a_bunch_of_ints)
{
asl::hash_set<int> set;
int count = 3000;
const int count = 3000;
for (int i = 0; i < count; ++i)
{
@ -98,6 +98,7 @@ struct CustomHasher
}
};
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(destructor_and_remove)
{
static constexpr int kCount = 200;
@ -113,9 +114,9 @@ ASL_TEST(destructor_and_remove)
ASL_TEST_EXPECT(set.size() == kCount);
for (int i = 0; i < kCount; ++i)
for (const bool i : destroyed)
{
ASL_TEST_EXPECT(!destroyed[i]); // NOLINT
ASL_TEST_EXPECT(!i); // NOLINT
}
for (int i = 0; i < kCount; i += 2)
@ -132,9 +133,9 @@ ASL_TEST(destructor_and_remove)
}
}
for (int i = 0; i < kCount; ++i)
for (const bool i : destroyed)
{
ASL_TEST_EXPECT(destroyed[i]); // NOLINT
ASL_TEST_EXPECT(i); // NOLINT
}
}
@ -147,7 +148,7 @@ ASL_TEST(copy)
set1.insert(i);
}
asl::hash_set<int> set2 = set1;
const asl::hash_set<int> set2 = set1;
asl::hash_set<int> set3;
set3 = set1;
@ -170,7 +171,7 @@ ASL_TEST(move)
set1.insert(i);
}
asl::hash_set<int> set2 = ASL_MOVE(set1);
asl::hash_set<int> set2 = std::move(set1);
ASL_TEST_EXPECT(set2.size() == 100);
for (int i = 0; i < 100; ++i)
@ -178,7 +179,7 @@ ASL_TEST(move)
ASL_TEST_EXPECT(set2.contains(i));
}
set1 = ASL_MOVE(set2);
set1 = std::move(set2);
ASL_TEST_EXPECT(set1.size() == 100);
for (int i = 0; i < 100; ++i)

View File

@ -19,7 +19,7 @@ struct intrusive_list_node
};
template<typename T>
concept is_intrusive_list_node = convertible_from<intrusive_list_node<T>*, T*>;
concept is_intrusive_list_node = derived_from<T, intrusive_list_node<T>>;
template<is_intrusive_list_node T>
class IntrusiveList
@ -43,11 +43,11 @@ public:
push_front(head);
}
ASL_DELETE_COPY(IntrusiveList)
ASL_DEFAULT_MOVE(IntrusiveList)
ASL_DELETE_COPY(IntrusiveList);
ASL_DEFAULT_MOVE(IntrusiveList);
~IntrusiveList() = default;
constexpr bool is_empty() const { return m_head == nullptr; }
[[nodiscard]] constexpr bool is_empty() const { return m_head == nullptr; }
void push_front(T* node)
{
@ -82,13 +82,13 @@ public:
constexpr auto front(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return return_type{ self.m_head };
}
constexpr auto back(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return return_type{ self.m_head != nullptr ? self.m_head->m_prev : nullptr };
}
@ -151,7 +151,7 @@ public:
, m_advanced{end}
{}
constexpr bool operator==(const generic_iterator& other) const = default;
constexpr bool operator==(this generic_iterator, generic_iterator other) = default;
constexpr generic_iterator& operator++()
{
@ -167,9 +167,9 @@ public:
};
}
constexpr TT& operator*() const { return *m_node; }
constexpr TT& operator*(this generic_iterator self) { return *self.m_node; }
constexpr TT* operator->() const { return m_node; }
constexpr TT* operator->(this generic_iterator self) { return self.m_node; }
};
using iterator = generic_iterator<T>;
@ -188,5 +188,5 @@ public:
}
};
}
} // namespace asl

View File

@ -23,6 +23,7 @@ ASL_TEST(empty_list)
ASL_TEST_EXPECT(list.back() == nullptr);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_front)
{
IntNode one{1};
@ -75,6 +76,7 @@ ASL_TEST(push_front)
ASL_TEST_ASSERT(it == end);
}
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_back)
{
IntNode one{1};

View File

@ -151,7 +151,7 @@ asl::string_view asl::format_uint64(uint64_t v, asl::span<char, kMaxUint64Digits
while (v >= 100)
{
uint64_t x = v % 100;
const uint64_t x = v % 100;
v /= 100;
write_two(s_pairs.subspan(static_cast<isize_t>(x * 2)).first<2>());
}
@ -195,7 +195,7 @@ void asl::AslFormat(Formatter& f, int64_t v)
if (v < 0)
{
f.write("-");
uint64_t absolute_value = ~(bit_cast<uint64_t>(v) - 1);
const uint64_t absolute_value = ~(bit_cast<uint64_t>(v) - 1);
AslFormat(f, absolute_value);
}
else

View File

@ -5,6 +5,7 @@
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/float.hpp"
#include "asl/base/meta.hpp"
#include "asl/io/writer.hpp"
#include "asl/types/span.hpp"
@ -32,7 +33,7 @@ struct type_erased_arg
template<formattable T>
static constexpr void erased_fn(Formatter& f, const void* data)
{
AslFormat(f, *reinterpret_cast<const T*>(data));
AslFormat(f, *static_cast<const T*>(data));
}
template<formattable T>
@ -44,7 +45,7 @@ struct type_erased_arg
void format(Writer*, string_view fmt, span<const type_erased_arg> args);
} // namespace internals
} // namespace format_internals
class Formatter
{
@ -60,7 +61,7 @@ public:
m_writer->write(as_bytes(s.as_span()));
}
constexpr Writer* writer() const { return m_writer; }
[[nodiscard]] constexpr Writer* writer() const { return m_writer; }
};
template<formattable... Args>
@ -68,7 +69,7 @@ void format(Writer* w, string_view fmt, const Args&... args)
{
if constexpr (types_count<Args...> > 0)
{
format_internals::type_erased_arg type_erased_args[] = {
const format_internals::type_erased_arg type_erased_args[] = {
format_internals::type_erased_arg(args)...
};
@ -93,8 +94,8 @@ inline void AslFormat(Formatter& f, string_view sv)
f.write(sv);
}
void AslFormat(Formatter& f, float);
void AslFormat(Formatter& f, double);
void AslFormat(Formatter& f, float32_t);
void AslFormat(Formatter& f, float64_t);
void AslFormat(Formatter& f, bool);

View File

@ -4,10 +4,14 @@
#include "asl/formatting/format.hpp"
#include "asl/base/float.hpp"
#include "asl/base/numeric.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 0
#define JKJ_STATIC_DATA_SECTION_DEFINED 0
#include <dragonbox.h>
#pragma clang diagnostic pop
static constexpr isize_t kZeroCount = 100;
static constexpr char kZeros[kZeroCount] = {
@ -23,14 +27,14 @@ static constexpr char kZeros[kZeroCount] = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
};
static constexpr bool is_zero(float x)
static constexpr bool is_zero(float32_t x)
{
return (asl::bit_cast<uint32_t>(x) & 0x7fff'ffff) == 0;
return (asl::bit_cast<uint32_t>(x) & 0x7fff'ffffU) == 0;
}
static constexpr bool is_zero(double x)
static constexpr bool is_zero(float64_t x)
{
return (asl::bit_cast<uint64_t>(x) & 0x7fff'ffff'ffff'ffff) == 0;
return (asl::bit_cast<uint64_t>(x) & 0x7fff'ffff'ffff'ffffULL) == 0;
}
template<asl::is_floating_point T>
@ -66,15 +70,15 @@ static void format_float(asl::Formatter& f, T value)
if (decimal.is_negative) { f.write("-"); }
char buffer[20];
asl::string_view digits = asl::format_uint64(decimal.significand, buffer);
const asl::string_view digits = asl::format_uint64(decimal.significand, buffer);
if (decimal.exponent >= 0)
{
f.write(digits);
while (decimal.exponent > 0)
{
isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(kZeros, to_write));
const isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(static_cast<const char*>(kZeros), to_write));
decimal.exponent -= to_write;
}
}
@ -86,8 +90,8 @@ static void format_float(asl::Formatter& f, T value)
decimal.exponent = -decimal.exponent - static_cast<int>(digits.size());
while (decimal.exponent > 0)
{
isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(kZeros, to_write));
const isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(static_cast<const char*>(kZeros), to_write));
decimal.exponent -= to_write;
}
f.write(digits);
@ -101,12 +105,12 @@ static void format_float(asl::Formatter& f, T value)
}
}
void asl::AslFormat(Formatter& f, float value)
void asl::AslFormat(Formatter& f, float32_t value)
{
format_float(f, value);
}
void asl::AslFormat(Formatter& f, double value)
void asl::AslFormat(Formatter& f, float64_t value)
{
format_float(f, value);
}

View File

@ -79,7 +79,7 @@ ASL_TEST(format_floats)
s = asl::format_to_string("{}", 123e-8);
ASL_TEST_EXPECT(s == "0.00000123"_sv);
s = asl::format_to_string("{} {}", asl::infinity<float>(), -asl::infinity<double>());
s = asl::format_to_string("{} {}", asl::infinity<float32_t>(), -asl::infinity<float64_t>());
ASL_TEST_EXPECT(s == "Infinity -Infinity"_sv);
s = asl::format_to_string("{}", asl::nan<float>());

View File

@ -0,0 +1,81 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
package(
default_applicable_licenses = ["//:license"],
)
cc_library(
name = "index_pool",
hdrs = [
"index_pool.hpp",
],
deps = [
"//asl/memory",
"//asl/memory:allocator",
"//asl/base",
"//asl/containers:chunked_buffer",
"//asl/types:option",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "dense_handle_pool",
hdrs = [
"dense_handle_pool.hpp",
],
deps = [
":index_pool",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "sparse_handle_pool",
hdrs = [
"sparse_handle_pool.hpp",
],
deps = [
":index_pool",
],
visibility = ["//visibility:public"],
)
cc_test(
name = "index_pool_tests",
srcs = [
"index_pool_tests.cpp",
],
deps = [
":index_pool",
"//asl/hashing",
"//asl/tests:utils",
"//asl/testing",
],
)
cc_test(
name = "dense_handle_pool_tests",
srcs = [
"dense_handle_pool_tests.cpp",
],
deps = [
":dense_handle_pool",
"//asl/tests:utils",
"//asl/testing",
],
)
cc_test(
name = "sparse_handle_pool_tests",
srcs = [
"sparse_handle_pool_tests.cpp",
],
deps = [
":sparse_handle_pool",
"//asl/tests:utils",
"//asl/testing",
],
)

View File

@ -0,0 +1,162 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/handle_pool/index_pool.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
namespace asl
{
// @Todo If we want the allocator to be non-copyable, we could
// introduce a reference allocator type that is copyable, and store
// the "main" allocator in the pool.
template<
is_object T,
int kIndexBits,
int kGenBits,
typename UserType = empty,
int kUserBits = 0,
isize_t kChunkSize = 32,
allocator Allocator = DefaultAllocator>
requires moveable<T> && copyable<Allocator>
class DenseHandlePool
{
using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, isize_t, Allocator>;
struct Slot
{
ThisIndexPool::handle h;
T obj;
template<typename... Args>
explicit Slot(ThisIndexPool::handle h_, Args&&... args)
: h{h_}
, obj(std::forward<Args>(args)...)
{}
};
using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
ThisIndexPool m_index_pool{};
Buffer m_buffer{};
using config = ThisIndexPool::handle::config;
template<typename... Args>
isize_t push(Args&&... args)
requires constructible_from<T, Args&&...>
{
m_buffer.push(typename ThisIndexPool::handle{}, std::forward<Args>(args)...);
return m_buffer.size() - 1;
}
public:
using handle = ThisIndexPool::handle;
DenseHandlePool() requires default_constructible<Allocator> = default;
explicit DenseHandlePool(const Allocator& allocator)
: m_index_pool(allocator)
, m_buffer(allocator)
{}
ASL_DELETE_COPY(DenseHandlePool);
ASL_DEFAULT_MOVE(DenseHandlePool);
~DenseHandlePool() = default;
[[nodiscard]] bool is_full() const
{
return m_index_pool.is_full();
}
bool is_valid(handle h) const
{
return m_index_pool.is_valid(h);
}
template<typename... Args>
option<handle> acquire(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(user, obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
option<handle> acquire(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
handle acquire_ensure(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(user, obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
handle acquire_ensure(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
void release(handle to_release_handle)
{
if (!is_valid(to_release_handle)) { return; }
const auto to_release_index = *m_index_pool.get_payload(to_release_handle);
if (to_release_index < m_buffer.size() - 1)
{
const auto to_swap_index = m_buffer.size() - 1;
const auto to_swap_handle = m_buffer[to_swap_index].h;
m_buffer[to_release_index] = std::move(m_buffer[to_swap_index]);
m_index_pool.exchange_payload(to_swap_handle, to_release_index);
}
m_buffer.pop();
m_index_pool.release(to_release_handle);
}
auto get(this auto&& self, handle h)
-> copy_const_t<un_ref_t<decltype(self)>, T>*
{
if (!self.is_valid(h)) { return nullptr; }
const auto index = *self.m_index_pool.get_payload(h);
return &self.m_buffer[index].obj;
}
auto get_ensure(this auto&& self, handle h)
-> copy_cref_t<decltype(self), T>
{
ASL_ASSERT_RELEASE(self.is_valid(h));
const auto index = *self.m_index_pool.get_payload(h);
return std::forward<decltype(self)>(self).m_buffer[index].obj;
}
};
} // namespace asl

View File

@ -0,0 +1,117 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/handle_pool/dense_handle_pool.hpp"
ASL_TEST(acquire_release) // NOLINT
{
asl::DenseHandlePool<int, 1, 1> pool;
ASL_TEST_EXPECT(!pool.is_full());
const auto a = pool.acquire_ensure(6);
const auto b = pool.acquire_ensure(7);
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(pool.get_ensure(a) == 6);
ASL_TEST_EXPECT(pool.get_ensure(b) == 7);
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.get(a) == nullptr);
ASL_TEST_EXPECT(*pool.get(b) == 7);
const auto c = pool.acquire_ensure(8);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(*pool.get(b) == 7);
ASL_TEST_EXPECT(*pool.get(c) == 8);
ASL_TEST_EXPECT(pool.get(a) == nullptr);
}
ASL_TEST(element_destructor)
{
asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
bool d[3]{};
const auto d0 = pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
const auto d2 = pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d0);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d2);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(destructor)
{
bool d[3]{};
{
asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
}
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
enum Flags : uint8_t
{
kFlag1 = 1,
kFlag2 = 2,
};
ASL_TEST(user_type)
{
asl::DenseHandlePool<int, 8, 8, Flags> pool;
auto a = pool.acquire_ensure(kFlag2, 22);
auto b = pool.acquire_ensure(kFlag1, 11);
ASL_TEST_EXPECT(pool.get_ensure(a) == 22);
ASL_TEST_EXPECT(pool.get_ensure(b) == 11);
ASL_TEST_EXPECT(a.user() == kFlag2);
ASL_TEST_EXPECT(b.user() == kFlag1);
}

View File

@ -0,0 +1,378 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/base/integers.hpp"
#include "asl/base/meta.hpp"
#include "asl/containers/chunked_buffer.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/types/option.hpp"
namespace asl
{
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0
>
requires (kIndexBits_ > 0 && kGenBits_ > 0 && kUserBits_ >= 0)
struct index_pool_config
{
static constexpr bool kHasUser = !same_as<UserType_, empty>;
using UserType = UserType_;
using PrimitiveUserType = smallest_unsigned_integer_type_for_width<size_of<UserType> * 8>;
static_assert(trivially_copy_constructible<UserType>);
static_assert(trivially_destructible<UserType>);
static_assert(size_of<UserType> == size_of<PrimitiveUserType>, "UserType should be of size 1, 2 or 4");
static constexpr int kUserBits = []() static -> int {
if constexpr (!kHasUser) { return 0; };
return kUserBits_ == 0 ? size_of<UserType> * 8 : kUserBits_;
}();
static_assert(kUserBits <= size_of<UserType> * 8);
static constexpr int kIndexBits = kIndexBits_;
static constexpr int kGenBits = kGenBits_;
static_assert(kIndexBits + kGenBits + kUserBits <= 63);
using HandleType = smallest_unsigned_integer_type_for_width<kIndexBits + kGenBits + kUserBits + 1>;
static constexpr int kGenShift = kIndexBits;
static constexpr int kUserShift = kIndexBits + kGenBits;
static constexpr HandleType kValidMask = HandleType{1} << (size_of<HandleType> * 8 - 1);
static constexpr HandleType kIndexMask = (HandleType{1} << kIndexBits) - 1;
static constexpr HandleType kGenMask = ((HandleType{1} << kGenBits) - 1) << kGenShift;
static constexpr HandleType kUserMask = ((HandleType{1} << kUserBits) - 1) << kUserShift;
static constexpr HandleType kNiche = static_cast<HandleType>(~uint64_t{kValidMask});
static constexpr uint64_t kMaxGen = (uint64_t{1} << kGenBits) - 1;
static constexpr uint64_t kMaxIndex = (uint64_t{1} << kIndexBits) - 1;
};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0
>
class index_pool_handle
{
public:
using config = index_pool_config<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
private:
config::HandleType m_handle{};
public:
constexpr index_pool_handle() = default;
constexpr explicit index_pool_handle(niche_t)
: m_handle{config::kNiche}
{}
constexpr index_pool_handle(uint64_t index, uint64_t gen)
requires (!config::kHasUser)
: m_handle{static_cast<config::HandleType>(
config::kValidMask |
(index & config::kIndexMask) |
((gen << config::kGenShift) & config::kGenMask))}
{
ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index);
ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen);
}
constexpr index_pool_handle(uint64_t index, uint64_t gen, config::UserType user)
requires config::kHasUser
: m_handle{static_cast<config::HandleType>(
config::kValidMask |
(index & config::kIndexMask) |
((gen << config::kGenShift) & config::kGenMask) |
((static_cast<config::HandleType>(bit_cast<typename config::PrimitiveUserType>(user)) << config::kUserShift) & config::kUserMask))}
{
ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index);
ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen);
ASL_ASSERT((bit_cast<typename config::PrimitiveUserType>(user) & (uint64_t{config::kUserMask} >> config::kUserShift)) == bit_cast<typename config::PrimitiveUserType>(user));
}
constexpr bool is_null(this index_pool_handle self)
{
return !(self.m_handle & config::kValidMask);
}
constexpr uint64_t index(this index_pool_handle self)
{
return self.m_handle & config::kIndexMask;
}
constexpr uint64_t gen(this index_pool_handle self)
{
return (self.m_handle & config::kGenMask) >> config::kGenShift;
}
constexpr config::UserType user(this index_pool_handle self)
{
return bit_cast<typename config::UserType>(static_cast<config::PrimitiveUserType>(
((self.m_handle & config::kUserMask) >> config::kUserShift)));
}
constexpr bool operator==(this index_pool_handle self, index_pool_handle other) = default;
constexpr bool operator==(this index_pool_handle self, niche_t)
{
return self.m_handle == config::kNiche;
}
};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_,
int kUserBits_
>
struct is_uniquely_represented<index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>> : true_type {};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0,
typename Payload = empty,
allocator Allocator = DefaultAllocator
>
class IndexPool
{
public:
using handle = index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
private:
using config = handle::config;
using internal_handle = index_pool_handle<
kIndexBits_,
kGenBits_,
typename config::PrimitiveUserType,
kUserBits_>;
static constexpr bool kHasPayload = !same_as<Payload, empty>;
// @Todo Remove need for default constructible & trivially destructible for payload
// Use maybe_uninit for it
static_assert(default_constructible<Payload>);
static_assert(copy_constructible<Payload>);
static_assert(trivially_destructible<Payload>);
struct Slot
{
bool is_end_of_list : 1;
bool is_active : 1;
internal_handle handle;
ASL_NO_UNIQUE_ADDRESS Payload payload;
};
chunked_buffer<Slot, 256, Allocator> m_slots;
// We only use the index, this is essentially the head of the linked
// list to the first available slot.
// Then the index of each slot points to the next available one.
internal_handle m_first_available;
void allocate_new_slot()
{
const auto new_index = static_cast<uint64_t>(m_slots.size());
if (new_index > config::kMaxIndex) { return; }
const internal_handle new_handle = internal_handle(new_index, 0, 0);
m_slots.push(Slot{
.is_end_of_list = true,
.is_active = false,
.handle = new_handle,
.payload = Payload{}
});
m_first_available = new_handle;
}
option<internal_handle> acquire_handle(const Payload& payload)
{
if (m_first_available.is_null())
{
allocate_new_slot();
}
if (m_first_available.is_null())
{
return nullopt;
}
auto index = m_first_available.index();
Slot& slot = m_slots[static_cast<isize_t>(index)];
ASL_ASSERT(!slot.is_active);
m_first_available = slot.is_end_of_list ? internal_handle{} : slot.handle;
slot.is_active = true;
slot.payload = payload;
return internal_handle(index, slot.handle.gen(), 0);
}
auto get_slot_if_valid(this auto&& self, handle h)
-> copy_const_t<decltype(self), Slot>*
{
if (h.is_null()) { return nullptr; }
auto index = static_cast<isize_t>(h.index());
if (index < 0 || index >= self.m_slots.size()) { return nullptr; }
auto& slot = self.m_slots[index];
if (!slot.is_active || slot.handle.gen() != h.gen())
{
return nullptr;
}
return &slot;
}
public:
IndexPool() requires default_constructible<Allocator> = default;
explicit IndexPool(Allocator allocator) : m_slots{std::move(allocator)} {}
[[nodiscard]] bool is_full() const
{
return m_first_available.is_null()
&& static_cast<uint64_t>(m_slots.size()) > config::kMaxIndex;
}
option<handle> acquire()
requires (!kHasPayload && !config::kHasUser)
{
return acquire_handle({}).transform([](internal_handle h)
{
return handle(h.index(), h.gen());
});
}
handle acquire_ensure()
requires (!kHasPayload && !config::kHasUser)
{
auto opt = acquire();
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(const Payload& payload)
requires (kHasPayload && !config::kHasUser)
{
return acquire_handle(payload).transform([](internal_handle h)
{
return handle(h.index(), h.gen());
});
}
handle acquire_ensure(const Payload& payload)
requires (kHasPayload && !config::kHasUser)
{
auto opt = acquire(payload);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(config::UserType user)
requires (!kHasPayload && config::kHasUser)
{
return acquire_handle({}).transform([user](internal_handle h)
{
return handle(h.index(), h.gen(), user);
});
}
handle acquire_ensure(config::UserType user)
requires (!kHasPayload && config::kHasUser)
{
auto opt = acquire(user);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(config::UserType user, const Payload& payload)
requires (kHasPayload && config::kHasUser)
{
return acquire_handle(payload).transform([user](internal_handle h)
{
return handle(h.index(), h.gen(), user);
});
}
handle acquire_ensure(config::UserType user, const Payload& payload)
requires (kHasPayload && config::kHasUser)
{
auto opt = acquire(user, payload);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
// @Todo Add a policy to abandon slots that reached max generation
void release(handle h)
{
if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
const uint64_t next_gen = h.gen() == config::kMaxGen ? 0 : h.gen() + 1;
slot->is_active = false;
if (m_first_available.is_null())
{
slot->is_end_of_list = true;
slot->handle = internal_handle(h.index(), next_gen, 0);
}
else
{
slot->is_end_of_list = false;
slot->handle = internal_handle(m_first_available.index(), next_gen, 0);
}
m_first_available = internal_handle(h.index(), 0, 0);
}
}
bool is_valid(handle h) const
{
return get_slot_if_valid(h) != nullptr;
}
const Payload* get_payload(handle h) const
requires kHasPayload
{
if (const Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
return &slot->payload;
}
return nullptr;
}
option<Payload> exchange_payload(handle h, Payload new_payload)
requires kHasPayload
{
if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
return asl::exchange(slot->payload, new_payload);
}
return nullopt;
}
};
} // namespace asl

View File

@ -0,0 +1,348 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/handle_pool/index_pool.hpp"
#include "asl/hashing/hash.hpp"
enum Flags: uint8_t {
kFlag0 = 0,
kFlag1 = 1,
kFlag2 = 2,
};
using Cfg1 = asl::index_pool_config<4, 3>;
static_assert(!Cfg1::kHasUser);
static_assert(Cfg1::kUserBits == 0);
static_assert(asl::same_as<Cfg1::HandleType, uint8_t>);
static_assert(Cfg1::kValidMask == uint8_t{0x80});
static_assert(Cfg1::kIndexMask == uint8_t{0x0f});
static_assert(Cfg1::kGenMask == uint8_t{0x70});
static_assert(Cfg1::kGenShift == 4);
static_assert(Cfg1::kMaxGen == 7);
static_assert(Cfg1::kMaxIndex == 15);
using Cfg2 = asl::index_pool_config<5, 5, Flags>;
static_assert(Cfg2::kHasUser);
static_assert(Cfg2::kUserBits == 8);
static_assert(asl::same_as<Cfg2::PrimitiveUserType, uint8_t>);
static_assert(asl::same_as<Cfg2::HandleType, uint32_t>);
static_assert(Cfg2::kValidMask == uint32_t{0x8000'0000});
static_assert(Cfg2::kIndexMask == uint32_t{0x0000'001f});
static_assert(Cfg2::kGenMask == uint32_t{0x0000'03e0});
static_assert(Cfg2::kUserMask == uint32_t{0x0003'fc00});
static_assert(Cfg2::kGenShift == 5);
static_assert(Cfg2::kUserShift == 10);
static_assert(Cfg2::kMaxGen == 31);
static_assert(Cfg2::kMaxIndex == 31);
using Cfg3 = asl::index_pool_config<5, 6, Flags, 4>;
static_assert(Cfg3::kHasUser);
static_assert(Cfg3::kUserBits == 4);
static_assert(asl::same_as<Cfg3::PrimitiveUserType, uint8_t>);
static_assert(asl::same_as<Cfg3::HandleType, uint16_t>);
static_assert(Cfg3::kValidMask == uint16_t{0x8000});
static_assert(Cfg3::kIndexMask == uint16_t{0x001f});
static_assert(Cfg3::kGenMask == uint16_t{0x07e0});
static_assert(Cfg3::kUserMask == uint16_t{0x7800});
static_assert(Cfg3::kGenShift == 5);
static_assert(Cfg3::kUserShift == 11);
static_assert(Cfg3::kMaxGen == 63);
static_assert(Cfg3::kMaxIndex == 31);
static_assert(asl::default_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_copy_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_move_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_copy_assignable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_move_assignable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_destructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::hashable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::has_niche<asl::index_pool_handle<5, 5, uint8_t>>);
ASL_TEST(default_is_invalid)
{
const asl::index_pool_handle<5, 5, uint8_t> idx;
ASL_TEST_EXPECT(idx.is_null());
}
ASL_TEST(niche_is_invalid)
{
const asl::index_pool_handle<5, 5, uint8_t> idx{asl::niche_t{}};
ASL_TEST_EXPECT(idx.is_null());
}
ASL_TEST(construct)
{
const asl::index_pool_handle<5, 5> idx(9, 11);
ASL_TEST_EXPECT(!idx.is_null());
ASL_TEST_EXPECT(idx.index() == 9);
ASL_TEST_EXPECT(idx.gen() == 11);
}
ASL_TEST(construct_user)
{
const asl::index_pool_handle<5, 5, Flags, 4> idx(9, 11, kFlag2);
ASL_TEST_EXPECT(!idx.is_null());
ASL_TEST_EXPECT(idx.index() == 9);
ASL_TEST_EXPECT(idx.gen() == 11);
ASL_TEST_EXPECT(idx.user() == kFlag2);
static_assert(asl::same_as<Flags, decltype(idx.user())>);
}
ASL_TEST(compare) // NOLINT
{
const asl::index_pool_handle<5, 5, Flags, 4> idx_default;
const asl::index_pool_handle<5, 5, Flags, 4> idx0;
const asl::index_pool_handle<5, 5, Flags, 4> idx1(9, 11, kFlag2);
const asl::index_pool_handle<5, 5, Flags, 4> idx2(9, 11, kFlag1);
const asl::index_pool_handle<5, 5, Flags, 4> idx3(9, 11, kFlag1);
const asl::index_pool_handle<5, 5, Flags, 4> idx4(9, 10, kFlag2);
const asl::index_pool_handle<5, 5, Flags, 4> idx5(8, 11, kFlag2);
ASL_TEST_EXPECT(idx0 == idx_default);
ASL_TEST_EXPECT(idx0 != idx1);
ASL_TEST_EXPECT(idx0 != idx2);
ASL_TEST_EXPECT(idx0 != idx3);
ASL_TEST_EXPECT(idx0 != idx4);
ASL_TEST_EXPECT(idx0 != idx5);
ASL_TEST_EXPECT(idx1 != idx2);
ASL_TEST_EXPECT(idx1 != idx3);
ASL_TEST_EXPECT(idx1 != idx4);
ASL_TEST_EXPECT(idx1 != idx5);
ASL_TEST_EXPECT(idx2 == idx3);
ASL_TEST_EXPECT(idx2 != idx4);
ASL_TEST_EXPECT(idx2 != idx5);
ASL_TEST_EXPECT(idx3 != idx4);
ASL_TEST_EXPECT(idx3 != idx5);
ASL_TEST_EXPECT(idx4 != idx5);
}
ASL_TEST(hashing) // NOLINT
{
const asl::index_pool_handle<4, 4> idx0(asl::niche_t{});
const asl::index_pool_handle<4, 4> idx1{};
const asl::index_pool_handle<4, 4> idx2(1, 1);
const asl::index_pool_handle<4, 4> idx3(1, 1);
const asl::index_pool_handle<4, 4> idx4(2, 1);
const asl::index_pool_handle<4, 4> idx5(1, 2);
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5));
}
ASL_TEST(hashing_in_option) // NOLINT
{
const asl::option<asl::index_pool_handle<4, 4>> idx0;
const asl::option<asl::index_pool_handle<4, 4>> idx1{asl::index_pool_handle<4, 4>()};
const asl::option<asl::index_pool_handle<4, 4>> idx2{asl::index_pool_handle<4, 4>(1, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx3{asl::index_pool_handle<4, 4>(1, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx4{asl::index_pool_handle<4, 4>(2, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx5{asl::index_pool_handle<4, 4>(1, 2)};
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5));
}
ASL_TEST(simple_pool) // NOLINT
{
using Pool = asl::IndexPool<8, 8>;
Pool pool;
auto a = pool.acquire_ensure();
auto b = pool.acquire_ensure();
ASL_TEST_EXPECT(!a.is_null());
ASL_TEST_EXPECT(!b.is_null());
ASL_TEST_EXPECT(a.index() == 0);
ASL_TEST_EXPECT(b.index() == 1);
ASL_TEST_EXPECT(a.gen() == 0);
ASL_TEST_EXPECT(b.gen() == 0);
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(a != b);
ASL_TEST_EXPECT(!pool.is_valid({}));
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
auto c = pool.acquire_ensure();
ASL_TEST_EXPECT(!c.is_null());
ASL_TEST_EXPECT(c.index() == 0);
ASL_TEST_EXPECT(c.gen() == 1);
ASL_TEST_EXPECT(a != c);
ASL_TEST_EXPECT(b != c);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
pool.release(b);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(!pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
pool.release(c);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(!pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_valid(c));
}
ASL_TEST(pool_acquire_release_a_lot)
{
using Pool = asl::IndexPool<3, 3>;
Pool pool;
for (int i = 0; i < 80; ++i)
{
pool.release(pool.acquire_ensure());
ASL_TEST_EXPECT(!pool.is_full());
}
}
ASL_TEST(pool_acquire_past_capacity)
{
using Pool = asl::IndexPool<3, 3>;
Pool pool;
auto idx = pool.acquire_ensure();
for (int i = 0; i < 7; ++i)
{
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.acquire().has_value());
}
for (int i = 0; i < 8; ++i)
{
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(!pool.acquire().has_value());
}
pool.release(idx);
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.acquire().has_value());
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(!pool.acquire().has_value());
}
ASL_TEST(pool_with_user_type)
{
using Pool = asl::IndexPool<8, 8, Flags, 4>;
Pool pool;
const auto a = pool.acquire_ensure(kFlag0);
const auto b = pool.acquire_ensure(kFlag2);
ASL_TEST_EXPECT(a.user() == kFlag0);
ASL_TEST_EXPECT(b.user() == kFlag2);
}
ASL_TEST(pool_with_payload)
{
using Pool = asl::IndexPool<8, 8, asl::empty, 0, int>;
Pool pool;
const auto a = pool.acquire_ensure(37);
const auto b = pool.acquire_ensure(28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(pool.get_payload({}) == nullptr);
pool.release(a);
ASL_TEST_EXPECT(pool.get_payload(a) == nullptr);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
}
ASL_TEST(pool_with_payload_and_user)
{
using Pool = asl::IndexPool<8, 8, Flags, 8, int>;
Pool pool;
const auto a = pool.acquire_ensure(kFlag1, 37);
const auto b = pool.acquire_ensure(kFlag0, 28);
ASL_TEST_EXPECT(a.user() == kFlag1);
ASL_TEST_EXPECT(b.user() == kFlag0);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(pool.get_payload({}) == nullptr);
pool.release(a);
ASL_TEST_EXPECT(pool.get_payload(a) == nullptr);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
}
ASL_TEST(pool_exchange_payload)
{
using Pool = asl::IndexPool<8, 8, asl::empty, 0, int>;
Pool pool;
const auto a = pool.acquire_ensure(37);
const auto b = pool.acquire_ensure(28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(!pool.exchange_payload({}, 0).has_value());
ASL_TEST_EXPECT(pool.exchange_payload(a, 101).value() == 37);
ASL_TEST_EXPECT(pool.exchange_payload(b, 102).value() == 28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 101);
ASL_TEST_EXPECT(*pool.get_payload(b) == 102);
}

View File

@ -0,0 +1,158 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/handle_pool/index_pool.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
namespace asl
{
// @Todo If we want the allocator to be non-copyable, we could
// introduce a reference allocator type that is copyable, and store
// the "main" allocator in the pool.
template<
is_object T,
int kIndexBits,
int kGenBits,
typename UserType = empty,
int kUserBits = 0,
isize_t kChunkSize = 32,
allocator Allocator = DefaultAllocator>
requires copyable<Allocator>
class SparseHandlePool
{
using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, empty, Allocator>;
struct Slot
{
ThisIndexPool::handle h;
maybe_uninit<T> obj;
Slot() = default;
ASL_DELETE_COPY_MOVE(Slot);
~Slot()
{
if (!h.is_null())
{
obj.destroy_unsafe();
}
}
};
using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
ThisIndexPool m_index_pool{};
Buffer m_buffer{};
using config = ThisIndexPool::handle::config;
template<typename... Args>
void set_object(ThisIndexPool::handle h, Args&&... args)
{
const auto index = static_cast<isize_t>(h.index());
if (m_buffer.size() <= index)
{
m_buffer.resize(index + 1);
}
m_buffer[index].h = h;
m_buffer[index].obj.construct_unsafe(std::forward<Args>(args)...);
}
public:
using handle = ThisIndexPool::handle;
SparseHandlePool() requires default_constructible<Allocator> = default;
explicit SparseHandlePool(const Allocator& allocator)
: m_index_pool(allocator)
, m_buffer(allocator)
{}
ASL_DELETE_COPY(SparseHandlePool);
ASL_DEFAULT_MOVE(SparseHandlePool);
~SparseHandlePool() = default;
[[nodiscard]] bool is_full() const
{
return m_index_pool.is_full();
}
bool is_valid(handle h) const
{
return m_index_pool.is_valid(h);
}
template<typename... Args>
option<handle> acquire(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const auto handle = m_index_pool.acquire_ensure(user);
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
option<handle> acquire(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const auto handle = m_index_pool.acquire_ensure();
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
handle acquire_ensure(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const auto handle = m_index_pool.acquire_ensure(user);
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
handle acquire_ensure(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const auto handle = m_index_pool.acquire_ensure();
set_object(handle, std::forward<Args>(args)...);
return handle;
}
void release(handle h)
{
if (!is_valid(h)) { return; }
auto& slot = m_buffer[static_cast<isize_t>(h.index())];
slot.h = {};
slot.obj.destroy_unsafe();
m_index_pool.release(h);
}
auto get(this auto&& self, handle h)
-> copy_const_t<un_ref_t<decltype(self)>, T>*
{
if (!self.is_valid(h)) { return nullptr; }
return &std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
.obj.as_init_unsafe();
}
auto get_ensure(this auto&& self, handle h)
-> copy_cref_t<decltype(self), T>
{
ASL_ASSERT_RELEASE(self.is_valid(h));
return std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
.obj.as_init_unsafe();
}
};
} // namespace asl

View File

@ -0,0 +1,117 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/handle_pool/sparse_handle_pool.hpp"
ASL_TEST(acquire_release) // NOLINT
{
asl::SparseHandlePool<int, 1, 1> pool;
ASL_TEST_EXPECT(!pool.is_full());
const auto a = pool.acquire_ensure(6);
const auto b = pool.acquire_ensure(7);
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(pool.get_ensure(a) == 6);
ASL_TEST_EXPECT(pool.get_ensure(b) == 7);
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.get(a) == nullptr);
ASL_TEST_EXPECT(*pool.get(b) == 7);
const auto c = pool.acquire_ensure(8);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(*pool.get(b) == 7);
ASL_TEST_EXPECT(*pool.get(c) == 8);
ASL_TEST_EXPECT(pool.get(a) == nullptr);
}
ASL_TEST(element_destructor)
{
asl::SparseHandlePool<DestructorObserver, 8, 8> pool;
bool d[3]{};
const auto d0 = pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
const auto d2 = pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d0);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d2);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(destructor)
{
bool d[3]{};
{
asl::SparseHandlePool<DestructorObserver, 8, 8> pool;
pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
}
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
enum Flags : uint8_t
{
kFlag1 = 1,
kFlag2 = 2,
};
ASL_TEST(user_type)
{
asl::SparseHandlePool<int, 8, 8, Flags> pool;
auto a = pool.acquire_ensure(kFlag2, 22);
auto b = pool.acquire_ensure(kFlag1, 11);
ASL_TEST_EXPECT(pool.get_ensure(a) == 22);
ASL_TEST_EXPECT(pool.get_ensure(b) == 11);
ASL_TEST_EXPECT(a.user() == kFlag2);
ASL_TEST_EXPECT(b.user() == kFlag1);
}

View File

@ -80,7 +80,7 @@ struct HashState
{
auto bytes = as_bytes(s);
auto hashed = city_hash::CityHash128WithSeed(
reinterpret_cast<const char*>(bytes.data()),
reinterpret_cast<const char*>(bytes.data()), // NOLINT(*-reinterpret-cast)
static_cast<size_t>(bytes.size()),
h.state);
return HashState{hashed};
@ -89,7 +89,7 @@ struct HashState
{
for (const auto& value: s)
{
h = AslHashValue(ASL_MOVE(h), value);
h = AslHashValue(std::move(h), value);
}
return h;
}
@ -103,7 +103,7 @@ struct HashState
template<hashable_generic<HashState> Arg, hashable_generic<HashState>... Remaining>
static constexpr HashState combine(HashState h, const Arg& arg, const Remaining&... remaining)
{
return combine(AslHashValue(ASL_MOVE(h), arg), remaining...);
return combine(AslHashValue(std::move(h), arg), remaining...);
}
};
@ -113,13 +113,13 @@ concept hashable = hashable_generic<T, HashState>;
template<typename H, uniquely_represented T>
constexpr H AslHashValue(H h, const T& value)
{
return H::combine_contiguous(ASL_MOVE(h), span<const T>{&value, 1});
return H::combine_contiguous(std::move(h), span<const T>{&value, 1});
}
template<typename H>
constexpr H AslHashValue(H h, bool value)
{
return AslHashValue(ASL_MOVE(h), value ? 1 : 0);
return AslHashValue(std::move(h), value ? 1 : 0);
}
template<typename H, typename T>
@ -128,7 +128,7 @@ constexpr void AslHashValue(H h, T*); // Don't hash pointers
template<typename H, hashable T>
constexpr H AslHashValue(H h, const span<T>& s)
{
return H::combine_contiguous(ASL_MOVE(h), span<const T>{s.data(), s.size()});
return H::combine_contiguous(std::move(h), span<const T>{s.data(), s.size()});
}
template<hashable T>

View File

@ -29,10 +29,10 @@ static_assert(asl::hashable<int64_t>);
ASL_TEST(integers)
{
uint64_t a = asl::hash_value<uint16_t>(45);
uint64_t b = asl::hash_value<uint16_t>(45);
uint64_t c = asl::hash_value<uint16_t>(46);
uint64_t d = asl::hash_value<uint32_t>(45);
const uint64_t a = asl::hash_value<uint16_t>(45);
const uint64_t b = asl::hash_value<uint16_t>(45);
const uint64_t c = asl::hash_value<uint16_t>(46);
const uint64_t d = asl::hash_value<uint32_t>(45);
ASL_TEST_EXPECT(a == b);
ASL_TEST_EXPECT(a != c);
@ -187,19 +187,19 @@ static_assert(asl::uniquely_represented<asl::option<NonZero>>);
ASL_TEST(option)
{
asl::option<int> int1 = 0;
asl::option<int> int2 = 0;
asl::option<int> int3 = 1;
asl::option<int> int4 = asl::nullopt;
const asl::option<int> int1 = 0;
const asl::option<int> int2 = 0;
const asl::option<int> int3 = 1;
const asl::option<int> int4 = asl::nullopt;
ASL_TEST_EXPECT(asl::hash_value(int1) == asl::hash_value(int2));
ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int3));
ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int4));
asl::option<NonZero> noz1{8};
asl::option<NonZero> noz2{8};
asl::option<NonZero> noz3{9};
asl::option<NonZero> noz4 = asl::nullopt;
const asl::option<NonZero> noz1{8};
const asl::option<NonZero> noz2{8};
const asl::option<NonZero> noz3{9};
const asl::option<NonZero> noz4 = asl::nullopt;
ASL_TEST_EXPECT(asl::hash_value(noz1) == asl::hash_value(noz2));
ASL_TEST_EXPECT(asl::hash_value(noz1) != asl::hash_value(noz3));
@ -208,17 +208,18 @@ ASL_TEST(option)
static_assert(asl::hashable<asl::status>);
// NOLINTNEXTLINE(*-cognitive-complexity)
ASL_TEST(status)
{
asl::status s1 = asl::ok();
asl::status s2 = asl::ok();
asl::status s3 = asl::internal_error();
asl::status s4 = asl::internal_error();
asl::status s5 = asl::runtime_error();
asl::status s6 = asl::internal_error("Oh, no!");
asl::status s7 = asl::internal_error("Oh, no!");
asl::status s8 = asl::internal_error("Oh, no");
asl::status s9 = asl::runtime_error("Oh, no!");
const asl::status s1 = asl::ok();
const asl::status s2 = asl::ok();
const asl::status s3 = asl::internal_error();
const asl::status s4 = asl::internal_error();
const asl::status s5 = asl::runtime_error();
const asl::status s6 = asl::internal_error("Oh, no!");
const asl::status s7 = asl::internal_error("Oh, no!");
const asl::status s8 = asl::internal_error("Oh, no");
const asl::status s9 = asl::runtime_error("Oh, no!");
ASL_TEST_EXPECT(asl::hash_value(s1) == asl::hash_value(s2));
ASL_TEST_EXPECT(asl::hash_value(s3) == asl::hash_value(s4));
@ -244,13 +245,13 @@ static_assert(!asl::hashable<asl::status_or<int*>>);
ASL_TEST(status_or)
{
asl::status_or<int> s1 = 42;
asl::status_or<int> s2 = 42;
asl::status_or<int> s3 = 43;
asl::status_or<int> s4 = asl::runtime_error();
asl::status_or<int> s5 = asl::runtime_error();
asl::status_or<int> s6 = asl::runtime_error("Hello");
asl::status_or<int> s7 = asl::runtime_error("Hello");
const asl::status_or<int> s1 = 42;
const asl::status_or<int> s2 = 42;
const asl::status_or<int> s3 = 43;
const asl::status_or<int> s4 = asl::runtime_error();
const asl::status_or<int> s5 = asl::runtime_error();
const asl::status_or<int> s6 = asl::runtime_error("Hello");
const asl::status_or<int> s7 = asl::runtime_error("Hello");
ASL_TEST_EXPECT(asl::hash_value(s1) == asl::hash_value(s2));
ASL_TEST_EXPECT(asl::hash_value(s4) == asl::hash_value(s5));

View File

@ -18,7 +18,7 @@ public:
void write(asl::span<const asl::byte> s) override
{
fwrite(s.data(), 1, static_cast<size_t>(s.size()), m_handle);
(void)fwrite(s.data(), 1, static_cast<size_t>(s.size()), m_handle);
}
};

View File

@ -3,14 +3,22 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/logging/logging.hpp"
#include "asl/containers/intrusive_list.hpp"
#include "asl/formatting/format.hpp"
#include "asl/io/print.hpp"
#include "asl/io/writer.hpp"
#include "asl/strings/string_builder.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/types/span.hpp"
// @Todo Don't use internal get_stdout_writer, make console module
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
static asl::log::DefaultLogger<asl::Writer*> g_default_logger{asl::print_internals::get_stdout_writer()};
// @Todo Protect the loggers list being a mutex
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
static asl::IntrusiveList<asl::log::Logger> g_loggers(&g_default_logger);
void asl::log::register_logger(Logger* logger)
@ -56,7 +64,7 @@ void asl::log::log_inner(
StringWriter msg_writer{};
asl::format_internals::format(&msg_writer, fmt, args);
message m{
const message m{
.level = l,
.message = msg_writer.as_string_view(),
.location = sl,

View File

@ -48,7 +48,11 @@ class DefaultLogger : public DefaultLoggerBase
W m_writer;
public:
explicit constexpr DefaultLogger(W&& writer) : m_writer{ASL_FWD(writer)} {}
template<typename U>
explicit constexpr DefaultLogger(U&& writer)
requires constructible_from<W, U&&>
: m_writer{std::forward<U>(writer)}
{}
constexpr void log(const message& m) override
{
@ -74,7 +78,7 @@ void log(level l, const source_location& sl, string_view fmt, const Args&... arg
}
else
{
format_internals::type_erased_arg type_erased_args[] = {
const format_internals::type_erased_arg type_erased_args[] = {
format_internals::type_erased_arg(args)...
};
log_inner(l, fmt, type_erased_args, sl);
@ -83,9 +87,26 @@ void log(level l, const source_location& sl, string_view fmt, const Args&... arg
} // namespace asl::log
// @Todo Compile-time configuration of logging
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 4
#define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_WARNING(...) ::asl::log::log(::asl::log::kWarning, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_ERROR(...) ::asl::log::log(::asl::log::kError, ::asl::source_location{}, __VA_ARGS__)
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 3
#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 2
#define ASL_LOG_WARNING(...) ::asl::log::log(::asl::log::kWarning, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 1
#define ASL_LOG_ERROR(...) ::asl::log::log(::asl::log::kError, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif

View File

@ -2,10 +2,11 @@
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/logging/logging.hpp"
#include "asl/testing/testing.hpp"
#include "asl/strings/string_builder.hpp"
#include "asl/base/defer.hpp"
#include "asl/logging/logging.hpp"
#include "asl/strings/string_builder.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(log)
{
@ -27,6 +28,6 @@ ASL_TEST(custom_writer)
ASL_LOG_INFO("Hello");
auto sv = string_writer.as_string_view();
ASL_TEST_EXPECT(sv == "[ INFO ] asl/logging/logging_tests.cpp:27: Hello\n");
ASL_TEST_EXPECT(sv == "[ INFO ] asl/logging/logging_tests.cpp:28: Hello\n");
}

View File

@ -9,16 +9,27 @@ package(
cc_library(
name = "memory",
hdrs = [
"allocator.hpp",
"layout.hpp",
"memory.hpp",
],
srcs = [
"allocator.cpp",
],
deps = [
"//asl/base",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "allocator",
hdrs = [
"allocator.hpp",
],
srcs = [
"allocator.cpp",
],
deps = [
"//asl/base",
"//asl/memory",
],
visibility = ["//visibility:public"],
)

View File

@ -4,6 +4,7 @@
#include "asl/memory/allocator.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/numeric.hpp"
#include "asl/memory/layout.hpp"
#include <cstdlib>
@ -14,11 +15,11 @@
void* asl::GlobalHeap::alloc(const layout& layout)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
void* ptr = ::_aligned_malloc(
static_cast<size_t>(layout.size),
static_cast<size_t>(layout.align));
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
void* ptr = ::aligned_alloc(
static_cast<size_t>(layout.align),
static_cast<size_t>(layout.size));
@ -29,11 +30,11 @@ void* asl::GlobalHeap::alloc(const layout& layout)
void* asl::GlobalHeap::realloc(void* old_ptr, [[maybe_unused]] const layout& old_layout, const layout& new_layout)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
return ::_aligned_realloc(old_ptr,
static_cast<size_t>(new_layout.size),
static_cast<size_t>(new_layout.align));
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
if (new_layout.align <= old_layout.align)
{
void* new_ptr = ::realloc(old_ptr, static_cast<size_t>(new_layout.size));
@ -50,9 +51,9 @@ void* asl::GlobalHeap::realloc(void* old_ptr, [[maybe_unused]] const layout& old
void asl::GlobalHeap::dealloc(void* ptr, const layout&)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
::_aligned_free(ptr);
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
::free(ptr);
#endif
}

View File

@ -37,7 +37,22 @@ template<typename T>
T* alloc_new(allocator auto& a, auto&&... args)
{
void* ptr = a.alloc(layout::of<T>());
return construct_at<T>(ptr, ASL_FWD(args)...);
return construct_at<T>(ptr, std::forward<decltype(args)>(args)...);
}
template<typename T>
T* alloc_uninit(allocator auto& a)
requires trivially_default_constructible<T>
{
void* ptr = a.alloc(layout::of<T>());
return reinterpret_cast<T*>(ptr); // NOLINT(*-reinterpret-cast)
}
template<typename T>
T* alloc_uninit_unsafe(allocator auto& a)
{
void* ptr = a.alloc(layout::of<T>());
return reinterpret_cast<T*>(ptr); // NOLINT(*-reinterpret-cast)
}
template<typename T>
@ -50,13 +65,15 @@ void alloc_delete(allocator auto& a, T* ptr)
template<typename T>
constexpr T* alloc_new_default(auto&&... args)
{
return alloc_new<T>(DefaultAllocator{}, ASL_FWD(args)...);
DefaultAllocator allocator{};
return alloc_new<T>(allocator, std::forward<decltype(args)>(args)...);
}
template<typename T>
void alloc_delete_default(T* ptr)
{
alloc_delete(DefaultAllocator{}, ptr);
DefaultAllocator allocator{};
alloc_delete(allocator, ptr);
}
} // namespace asl

View File

@ -9,7 +9,7 @@
#include "asl/base/utility.hpp"
#include "asl/memory/layout.hpp"
constexpr void* operator new(size_t, void* ptr)
constexpr void* operator new(size_t, void* ptr) noexcept
{
return ptr;
}
@ -17,6 +17,17 @@ constexpr void* operator new(size_t, void* ptr)
namespace asl
{
template<typename T>
[[nodiscard]]
constexpr T* address_of(T& obj)
{
return __builtin_addressof(obj);
}
template<typename T>
void address_of(const T&& obj) = delete;
[[nodiscard]]
constexpr isize_t memcmp(const void* a, const void* b, isize_t size)
{
return __builtin_memcmp(a, b, static_cast<size_t>(size));
@ -41,7 +52,7 @@ template<typename T, typename... Args>
constexpr T* construct_at(void* ptr, Args&&... args)
requires constructible_from<T, Args&&...>
{
return new (ptr) T{ ASL_FWD(args)... };
return new (ptr) T{ std::forward<Args>(args)... }; // NOLINT(*-owning-memory)
}
template<typename T>
@ -112,7 +123,7 @@ constexpr void relocate_uninit_n(T* to, T* from, isize_t n)
for (isize_t i = 0; i < n; ++i)
{
// NOLINTNEXTLINE(*-pointer-arithmetic)
construct_at<T>(to + i, ASL_MOVE(from[i]));
construct_at<T>(to + i, std::move(from[i]));
}
destroy_n(from, n);
}
@ -131,7 +142,7 @@ constexpr void relocate_assign_n(T* to, T* from, isize_t n)
for (isize_t i = 0; i < n; ++i)
{
// NOLINTNEXTLINE(*-pointer-arithmetic)
to[i] = ASL_MOVE(from[i]);
to[i] = std::move(from[i]);
}
destroy_n(from, n);
}

View File

@ -45,6 +45,24 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "parse_number",
hdrs = [
"parse_number.hpp",
],
srcs = [
"parse_number_float.cpp",
"parse_number.cpp",
],
deps = [
"//asl/base",
"//asl/types:status",
":string_view",
"//vendor/fast_float",
],
visibility = ["//visibility:public"],
)
[cc_test(
name = "%s_tests" % name,
srcs = [
@ -54,6 +72,7 @@ cc_library(
":string",
":string_builder",
":string_view",
":parse_number",
"//asl/tests:utils",
"//asl/testing",
],
@ -61,4 +80,5 @@ cc_library(
"string",
"string_view",
"string_builder",
"parse_number",
]]

View File

@ -0,0 +1,162 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/strings/parse_number.hpp"
namespace asl
{
bool parse_float_impl(const char** begin, const char* end, float*);
bool parse_double_impl(const char** begin, const char* end, double*);
} // namespace asl
asl::status_or<asl::parse_number_result<float32_t>> asl::parse_float32(asl::string_view sv)
{
const auto* begin = sv.data();
// NOLINTNEXTLINE(*-pointer-arithmetic)
const auto* end = begin + sv.size();
if (float32_t value{}; parse_float_impl(&begin, end, &value))
{
return parse_number_result<float32_t>{
.value = value,
.remaining = string_view{begin, end},
};
}
return invalid_argument_error();
}
asl::status_or<asl::parse_number_result<float64_t>> asl::parse_float64(asl::string_view sv)
{
const auto* begin = sv.data();
// NOLINTNEXTLINE(*-pointer-arithmetic)
const auto* end = begin + sv.size();
if (float64_t value{}; parse_double_impl(&begin, end, &value))
{
return parse_number_result<float64_t>{
.value = value,
.remaining = string_view{begin, end},
};
}
return invalid_argument_error();
}
namespace
{
constexpr int8_t kBase16Table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
// @Todo Maybe monomorph this for common bases (2, 16, 10)?
template<typename T>
asl::status_or<asl::parse_number_result<T>> parse_integer(asl::string_view sv, int base)
{
ASL_ASSERT(base >= 2 && base <= 16);
if (sv.is_empty()) { return asl::invalid_argument_error(); }
T value = 0;
bool is_negative = false;
if (asl::is_signed_integer<T> && sv[0] == '-')
{
is_negative = true;
sv = sv.substr(1);
}
isize_t cursor = 0;
while (cursor < sv.size())
{
// NOLINTNEXTLINE(*-array-index)
int8_t digit = kBase16Table[static_cast<uint8_t>(sv[cursor])];
if (digit < 0 || digit >= base) { break; }
if (__builtin_mul_overflow(value, static_cast<T>(base), &value))
{
return asl::invalid_argument_error("overflow");
}
if (asl::is_signed_integer<T> && is_negative)
{
digit = static_cast<int8_t>(-digit);
}
if (__builtin_add_overflow(value, static_cast<T>(digit), &value))
{
return asl::invalid_argument_error("overflow");
}
cursor += 1;
}
if (cursor == 0)
{
return asl::invalid_argument_error();
}
return asl::parse_number_result<T>{
.value = value,
.remaining = sv.substr(cursor),
};
}
} // anonymous namespace
asl::status_or<asl::parse_number_result<uint8_t>> asl::parse_uint8(string_view sv, int base)
{
return parse_integer<uint8_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint16_t>> asl::parse_uint16(string_view sv, int base)
{
return parse_integer<uint16_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint32_t>> asl::parse_uint32(string_view sv, int base)
{
return parse_integer<uint32_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint64_t>> asl::parse_uint64(string_view sv, int base)
{
return parse_integer<uint64_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int8_t>> asl::parse_int8(string_view sv, int base)
{
return parse_integer<int8_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int16_t>> asl::parse_int16(string_view sv, int base)
{
return parse_integer<int16_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int32_t>> asl::parse_int32(string_view sv, int base)
{
return parse_integer<int32_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int64_t>> asl::parse_int64(string_view sv, int base)
{
return parse_integer<int64_t>(sv, base);
}

View File

@ -0,0 +1,35 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/types/status_or.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/base/float.hpp"
namespace asl
{
template<typename T>
struct parse_number_result
{
T value;
string_view remaining;
};
status_or<parse_number_result<float32_t>> parse_float32(string_view);
status_or<parse_number_result<float64_t>> parse_float64(string_view);
status_or<parse_number_result<uint8_t>> parse_uint8(string_view, int base = 10);
status_or<parse_number_result<uint16_t>> parse_uint16(string_view, int base = 10);
status_or<parse_number_result<uint32_t>> parse_uint32(string_view, int base = 10);
status_or<parse_number_result<uint64_t>> parse_uint64(string_view, int base = 10);
status_or<parse_number_result<int8_t>> parse_int8(string_view, int base = 10);
status_or<parse_number_result<int16_t>> parse_int16(string_view, int base = 10);
status_or<parse_number_result<int32_t>> parse_int32(string_view, int base = 10);
status_or<parse_number_result<int64_t>> parse_int64(string_view, int base = 10);
} // namespace asl

View File

@ -0,0 +1,35 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#include <fast_float.h>
#pragma clang diagnostic pop
// We need to isolate fast_float.h completely from asl
// because it conflicts with our redefinitions of things
// from the STL. In this case it's operator new, but there
// might be other conflicts.
#pragma clang diagnostic ignored "-Wmissing-prototypes"
namespace asl
{
extern bool parse_float_impl(const char** begin, const char* end, float* value)
{
auto res = fast_float::from_chars(*begin, end, *value);
*begin = res.ptr;
return res.ec == std::errc{};
}
extern bool parse_double_impl(const char** begin, const char* end, double* value)
{
auto res = fast_float::from_chars(*begin, end, *value);
*begin = res.ptr;
return res.ec == std::errc{};
}
} // namespace asl

View File

@ -0,0 +1,24 @@
a = ord('a')
f = ord('f')
A = ord('A')
F = ord('F')
n0 = ord('0')
n9 = ord('9')
output = ""
for i in range(0, 16):
for j in range(0, 16):
v = i * 16 + j
n = -1
if v >= a and v <= f:
n = v - a + 10
elif v >= A and v <= F:
n = v - A + 10
elif v >= n0 and v <= n9:
n = v - n0
output += f"{n:>2}, "
output += "\n"
print(output)

View File

@ -0,0 +1,201 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/strings/parse_number.hpp"
#include "asl/base/numeric.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(parse_float_error)
{
const asl::string_view sv = "this is not a number lmao";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_float_empty)
{
const asl::string_view sv = "";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_float_simple)
{
const asl::string_view sv = "3.1415";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_integer)
{
const asl::string_view sv = "31415";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 31415.0F);
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_scientific)
{
const asl::string_view sv = "314.15e-2";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_suffix)
{
const asl::string_view sv = "3.1415 yoyoyo";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining == " yoyoyo");
}
ASL_TEST(parse_int)
{
const asl::string_view sv = "926473";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 926473);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_negative)
{
const asl::string_view sv = "-926473";
auto res = asl::parse_int32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == -926473);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_suffix)
{
const asl::string_view sv = "926473 what's this then";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 926473);
ASL_TEST_EXPECT(res.value().remaining == " what's this then");
}
ASL_TEST(parse_uint_with_minus)
{
const asl::string_view sv = "-926473";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_int_with_only_minus)
{
const asl::string_view sv = "-@";
auto res = asl::parse_int32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_invalid)
{
const asl::string_view sv = "abcd";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_empty)
{
const asl::string_view sv = "";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_overflow)
{
ASL_TEST_EXPECT(!asl::parse_uint16("80000").ok());
ASL_TEST_EXPECT(!asl::parse_uint16("65536").ok());
}
ASL_TEST(parse_uint16_max)
{
const asl::string_view sv = "65535";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 65535);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_uint16_zero)
{
const asl::string_view sv = "0";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 0);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_uint16_zeros)
{
const asl::string_view sv = "00000";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 0);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_overflow)
{
ASL_TEST_EXPECT(!asl::parse_int16("80000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("40000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("32768").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-80000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-40000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-32769").ok());
}
ASL_TEST(parse_int16_max)
{
const asl::string_view sv = "32767";
auto res = asl::parse_int16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 32767);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int16_min)
{
const asl::string_view sv = "-32768";
auto res = asl::parse_int16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == -32768);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_hex)
{
const asl::string_view sv = "1000a";
auto res = asl::parse_uint32(sv, 16);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 65546);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_bin)
{
const asl::string_view sv = "101010";
auto res = asl::parse_uint32(sv, 2);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 42);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_oct)
{
const asl::string_view sv = "644";
auto res = asl::parse_uint32(sv, 8);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 6 * 64 + 4 * 8 + 4);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}

View File

@ -11,12 +11,12 @@ namespace asl
{
template<allocator Allocator = DefaultAllocator>
class string
class string : protected buffer<char, Allocator>
{
buffer<char, Allocator> m_buffer;
using Base = buffer<char, Allocator>;
explicit constexpr string(buffer<char, Allocator>&& buffer) :
m_buffer{ASL_MOVE(buffer)}
explicit constexpr string(buffer<char, Allocator>&& b) :
Base{std::move(b)}
{}
template<allocator A>
@ -24,38 +24,36 @@ class string
public:
constexpr string() requires default_constructible<Allocator> = default;
explicit constexpr string(Allocator allocator) : m_buffer{ASL_MOVE(allocator)} {}
// NOLINTNEXTLINE(*-explicit-conversions)
explicit constexpr string(Allocator allocator)
: Base{std::move(allocator)}
{}
// NOLINTNEXTLINE(*explicit*)
constexpr string(string_view sv)
requires default_constructible<Allocator>
: m_buffer{sv.as_span()}
: Base{sv.as_span()}
{}
constexpr string(string_view sv, Allocator allocator)
: m_buffer{sv.as_span(), ASL_MOVE(allocator)}
: Base{sv.as_span(), std::move(allocator)}
{}
constexpr ~string() = default;
using Base::size;
constexpr string(const string&) requires copy_constructible<Allocator> = default;
constexpr string(string&&) = default;
using Base::is_empty;
constexpr string& operator=(const string&) requires copy_assignable<Allocator> = default;
constexpr string& operator=(string&&) = default;
[[nodiscard]] constexpr const char* data() const { return Base::data(); }
constexpr isize_t size() const { return m_buffer.size(); }
constexpr const char* data() const { return m_buffer.data(); }
// NOLINTNEXTLINE(*-explicit-conversions)
// NOLINTNEXTLINE(*explicit*)
constexpr operator string_view() const
{
return as_string_view();
}
constexpr string_view as_string_view() const
[[nodiscard]] constexpr string_view as_string_view() const
{
auto span = m_buffer.as_span();
auto span = Base::as_span();
return string_view{span.data(), span.size()};
}

View File

@ -20,7 +20,7 @@ class StringBuilder
public:
constexpr StringBuilder() requires default_constructible<Allocator> = default;
explicit constexpr StringBuilder(Allocator allocator) : m_buffer{ASL_MOVE(allocator)} {}
explicit constexpr StringBuilder(Allocator allocator) : m_buffer{std::move(allocator)} {}
constexpr ~StringBuilder() = default;
@ -30,7 +30,7 @@ public:
constexpr StringBuilder& operator=(const StringBuilder&) requires copy_assignable<Allocator> = default;
constexpr StringBuilder& operator=(StringBuilder&&) = default;
constexpr string_view as_string_view() const
[[nodiscard]] constexpr string_view as_string_view() const
{
auto span = m_buffer.as_span();
return string_view{span.data(), span.size()};
@ -44,23 +44,23 @@ public:
auto push(this auto&& self, string_view sv) -> decltype(self)
requires (!is_const<un_ref_t<decltype(self)>>)
{
isize_t old_size = self.m_buffer.size();
self.m_buffer.resize_zero(old_size + sv.size());
const isize_t old_size = self.m_buffer.size();
self.m_buffer.resize_uninit(old_size + sv.size());
// NOLINTNEXTLINE(*-pointer-arithmetic)
asl::memcpy(self.m_buffer.data() + old_size, sv.data(), sv.size());
return ASL_FWD(self);
return std::forward<decltype(self)>(self);
}
auto push(this auto&& self, char c) -> decltype(self)
requires (!is_const<un_ref_t<decltype(self)>>)
{
self.m_buffer.push(c);
return ASL_FWD(self);
return std::forward<decltype(self)>(self);
}
string<Allocator> finish() &&
{
return string<Allocator>{ASL_MOVE(m_buffer)};
return string<Allocator>{std::move(m_buffer)};
}
template<allocator StringAllocator = Allocator>
@ -73,7 +73,7 @@ public:
template<allocator StringAllocator = Allocator>
string<StringAllocator> as_string(Allocator allocator) const
{
return string<StringAllocator>{as_string_view(), ASL_MOVE(allocator)};
return string<StringAllocator>{as_string_view(), std::move(allocator)};
}
};
@ -86,7 +86,7 @@ class StringWriter : public asl::Writer
public:
constexpr StringWriter() requires default_constructible<Allocator> = default;
explicit constexpr StringWriter(Allocator allocator) : m_builder{ASL_MOVE(allocator)} {}
explicit constexpr StringWriter(Allocator allocator) : m_builder{std::move(allocator)} {}
constexpr ~StringWriter() override = default;
@ -98,17 +98,18 @@ public:
void write(span<const byte> str) override
{
// NOLINTNEXTLINE(*-reinterpret-cast)
m_builder.push(string_view{reinterpret_cast<const char*>(str.data()), str.size()});
}
constexpr string_view as_string_view() const
[[nodiscard]] constexpr string_view as_string_view() const
{
return m_builder.as_string_view();
}
string<Allocator> finish() &&
{
return ASL_MOVE(m_builder).finish();
return std::move(m_builder).finish();
}
template<allocator StringAllocator = Allocator>
@ -121,7 +122,7 @@ public:
template<allocator StringAllocator = Allocator>
string<StringAllocator> as_string(Allocator allocator) const
{
return m_builder.as_string(ASL_MOVE(allocator));
return m_builder.as_string(std::move(allocator));
}
};
@ -133,15 +134,15 @@ string<Allocator> format_to_string(string_view fmt, const formattable auto&... a
{
StringWriter writer{};
format(&writer, fmt, args...);
return ASL_MOVE(writer).finish();
return std::move(writer).finish();
}
template<allocator Allocator = DefaultAllocator>
string<Allocator> format_to_string(Allocator allocator, string_view fmt, const formattable auto&... args)
{
StringWriter writer{ASL_MOVE(allocator)};
StringWriter writer{std::move(allocator)};
format(&writer, fmt, args...);
return ASL_MOVE(writer).finish();
return std::move(writer).finish();
}
} // namespace asl

View File

@ -14,13 +14,13 @@ ASL_TEST(string_builder)
ASL_TEST_EXPECT(b.as_string_view() == "abcdefg");
asl::string s = b.as_string();
const asl::string s = b.as_string();
ASL_TEST_EXPECT(s == "abcdefg");
}
ASL_TEST(string_builder_rvalue)
{
asl::string s = asl::StringBuilder{}.push('a').push("bcdef").push('g').finish();
const asl::string s = asl::StringBuilder{}.push('a').push("bcdef").push('g').finish();
ASL_TEST_EXPECT(s == "abcdefg");
}

View File

@ -8,7 +8,7 @@
ASL_TEST(default)
{
asl::string s;
const asl::string s;
ASL_TEST_ASSERT(s.size() == 0);
ASL_TEST_ASSERT(s.as_string_view().size() == 0);
ASL_TEST_ASSERT(s == ""_sv);
@ -17,7 +17,7 @@ ASL_TEST(default)
ASL_TEST(from_string_view)
{
asl::string s = "hello"_sv;
const asl::string s = "hello"_sv;
ASL_TEST_ASSERT(s.size() == 5);
ASL_TEST_ASSERT(s == "hello"_sv);
}

View File

@ -12,6 +12,7 @@
namespace asl
{
// NOLINTBEGIN(*-convert-member-functions-to-static)
class string_view
{
const char* m_data{};
@ -20,17 +21,24 @@ class string_view
public:
constexpr string_view() = default;
constexpr string_view(nullptr_t) : string_view() {} // NOLINT(*-explicit-conversions)
constexpr string_view(nullptr_t) : string_view() {} // NOLINT(*explicit*)
constexpr string_view(const char* data, isize_t size)
: m_data{data}
, m_size{size}
{}
constexpr string_view(const char* begin, const char* end)
: m_data{begin}
, m_size{end - begin}
{
ASL_ASSERT(begin <= end);
}
template<isize_t kSize>
constexpr string_view(const char (&str)[kSize]) // NOLINT(*-explicit-conversions)
constexpr string_view(const char (&str)[kSize]) // NOLINT(*explicit*)
requires (kSize >= 1)
: m_data{str}
: m_data{static_cast<const char*>(str)}
, m_size{kSize - 1}
{
ASL_ASSERT(m_data[kSize - 1] == '\0'); // NOLINT(*-pointer-arithmetic)
@ -38,7 +46,7 @@ public:
static constexpr string_view from_zstr(const char* str)
{
return string_view(str, asl::strlen(str));
return {str, asl::strlen(str)};
}
constexpr string_view(const string_view&) = default;
@ -49,51 +57,69 @@ public:
~string_view() = default;
constexpr isize_t size() const { return m_size; }
constexpr bool is_empty() const { return m_size == 0; }
constexpr const char* data() const { return m_data; }
constexpr contiguous_iterator<const char> begin() const { return contiguous_iterator{m_data}; }
// NOLINTNEXTLINE(*-pointer-arithmetic)
constexpr contiguous_iterator<const char> end() const { return contiguous_iterator{m_data + m_size}; }
constexpr span<const char> as_span() const { return span<const char>(m_data, m_size); }
constexpr char operator[](isize_t i) const
[[nodiscard]] constexpr isize_t size(this string_view self)
{
ASL_ASSERT(i >= 0 && i < m_size);
return m_data[i]; // NOLINT(*-pointer-arithmetic)
return self.m_size;
}
constexpr string_view substr(isize_t offset, isize_t size) const
[[nodiscard]] constexpr bool is_empty(this string_view self)
{
ASL_ASSERT(offset >= 0 && size >= 0 && offset + size <= m_size);
return string_view{m_data + offset, size}; // NOLINT(*-pointer-arithmetic)
return self.m_size == 0;
}
constexpr string_view substr(isize_t offset) const
[[nodiscard]] constexpr const char* data(this string_view self)
{
ASL_ASSERT(offset >= 0 && offset <= m_size);
return string_view{m_data + offset, m_size - offset}; // NOLINT(*-pointer-arithmetic)
return self.m_data;
}
constexpr string_view first(isize_t size) const
[[nodiscard]] constexpr contiguous_iterator<const char> begin(this string_view self)
{
return substr(0, size);
return contiguous_iterator{self.m_data};
}
constexpr string_view last(isize_t size) const
[[nodiscard]] constexpr contiguous_iterator<const char> end(this string_view self)
{
return substr(m_size - size);
// NOLINTNEXTLINE(*-pointer-arithmetic)
return contiguous_iterator{self.m_data + self.m_size};
}
constexpr bool operator==(string_view other) const
[[nodiscard]] constexpr span<const char> as_span(this string_view self)
{
if (m_size != other.m_size) { return false; }
return memcmp(m_data, other.m_data, m_size) == 0;
return {self.m_data, self.m_size};
}
[[nodiscard]] constexpr char operator[](this string_view self, isize_t i)
{
ASL_ASSERT(i >= 0 && i < self.m_size);
return self.m_data[i]; // NOLINT(*-pointer-arithmetic)
}
[[nodiscard]] constexpr string_view substr(this string_view self, isize_t offset, isize_t size)
{
ASL_ASSERT(offset >= 0 && size >= 0 && offset + size <= self.m_size);
return string_view{self.m_data + offset, size}; // NOLINT(*-pointer-arithmetic)
}
[[nodiscard]] constexpr string_view substr(this string_view self, isize_t offset)
{
ASL_ASSERT(offset >= 0 && offset <= self.m_size);
return string_view{self.m_data + offset, self.m_size - offset}; // NOLINT(*-pointer-arithmetic)
}
[[nodiscard]] constexpr string_view first(this string_view self, isize_t size)
{
return self.substr(0, size);
}
[[nodiscard]] constexpr string_view last(this string_view self, isize_t size)
{
return self.substr(self.m_size - size);
}
constexpr bool operator==(this string_view self, string_view other)
{
if (self.m_size != other.m_size) { return false; }
return asl::memcmp(self.m_data, other.m_data, self.m_size) == 0;
}
template<typename H>
@ -102,10 +128,11 @@ public:
return H::combine(H::combine_contiguous(h, as_bytes(sv.as_span())), sv.size());
}
};
// NOLINTEND(*-convert-member-functions-to-static)
} // namespace asl
constexpr asl::string_view operator ""_sv(const char* s, size_t len)
{
return asl::string_view(s, static_cast<isize_t>(len));
return {s, static_cast<isize_t>(len)};
}

View File

@ -10,20 +10,20 @@ static_assert(asl::trivially_copy_constructible<asl::string_view>);
ASL_TEST(default)
{
asl::string_view s1;
const asl::string_view s1;
ASL_TEST_EXPECT(s1.is_empty());
asl::string_view s2 = nullptr;
const asl::string_view s2 = nullptr;
ASL_TEST_EXPECT(s2.is_empty());
}
ASL_TEST(from_literal)
{
asl::string_view s1 = "Hello"_sv;
const asl::string_view s1 = "Hello"_sv;
ASL_TEST_ASSERT(s1.size() == 5);
ASL_TEST_EXPECT(asl::memcmp(s1.data(), "Hello", 5) == 0);
asl::string_view s2 = ""_sv;
const asl::string_view s2 = ""_sv;
ASL_TEST_EXPECT(s2.is_empty());
}
@ -47,7 +47,7 @@ ASL_TEST(from_zstr)
ASL_TEST(substr1)
{
asl::string_view s1 = "abcd";
const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.substr(0);
ASL_TEST_ASSERT(s2.size() == 4);
@ -63,7 +63,7 @@ ASL_TEST(substr1)
ASL_TEST(substr2)
{
asl::string_view s1 = "abcd";
const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.substr(0, 4);
ASL_TEST_ASSERT(s2.size() == 4);
@ -82,7 +82,7 @@ ASL_TEST(substr2)
ASL_TEST(first)
{
asl::string_view s1 = "abcd";
const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.first(0);
ASL_TEST_ASSERT(s2.size() == 0);
@ -98,7 +98,7 @@ ASL_TEST(first)
ASL_TEST(last)
{
asl::string_view s1 = "abcd";
const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.last(0);
ASL_TEST_ASSERT(s2.size() == 0);

View File

@ -4,37 +4,51 @@
#include "asl/testing/testing.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/utility.hpp"
#include "asl/io/print.hpp"
static asl::testing::Test* g_head = nullptr;
static asl::testing::Test* g_tail = nullptr;
namespace
{
struct TestingState
{
asl::testing::Test* head = nullptr;
asl::testing::Test* tail = nullptr;
bool current_test_fail = false;
};
} // namespace
// NOLINTNEXTLINE(*-avoid-non-const-global-variables)
static TestingState g_state{};
void asl::testing::register_test(Test* test)
{
if (g_head == nullptr && g_tail == nullptr)
if (g_state.head == nullptr && g_state.tail == nullptr)
{
g_head = test;
g_tail = test;
g_state.head = test;
g_state.tail = test;
}
else
{
g_tail->m_next = test;
test->m_prev = asl::exchange(g_tail, test);
g_state.tail->m_next = test;
test->m_prev = asl::exchange(g_state.tail, test);
}
}
static bool g_current_test_fail = false;
void asl::testing::report_failure(const char* msg, const asl::source_location& sl)
{
asl::eprint("--------------------------------------------------------------\n");
asl::eprint("Test assertion failed at {}, line {}:\n", sl.file, sl.line);
asl::eprint(" {}\n", msg);
asl::eprint("--------------------------------------------------------------\n");
g_current_test_fail = true;
g_state.current_test_fail = true;
}
static void report_assert_failure(const char* msg, const asl::source_location& sl, void*)
static void report_assert_failure(const char* msg, const asl::source_location& sl, void* /* userdata */)
{
asl::eprint("------------------------------------------------------------\n");
asl::eprint("Assertion failure at {}, line {}:\n", sl.file, sl.line);
@ -55,24 +69,28 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
asl::testing::Test* failed_head = nullptr;
for (auto* it = g_head; it != nullptr; it = it->m_next)
for (auto* it = g_state.head; it != nullptr;)
{
asl::eprint(GREEN("[ RUN ]") " {}\n", it->m_case_name);
g_current_test_fail = false;
g_state.current_test_fail = false;
it->m_fn();
if (!g_current_test_fail)
if (!g_state.current_test_fail)
{
asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name);
pass += 1;
it = it->m_next;
}
else
{
asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name);
fail += 1;
it->m_next = asl::exchange(failed_head, it);
auto* this_test = it;
it = it->m_next;
this_test->m_next = asl::exchange(failed_head, this_test);
}
}

View File

@ -4,7 +4,7 @@
#pragma once
#include "asl/base/utility.hpp"
#include "asl/base/meta.hpp"
namespace asl::testing
{
@ -35,7 +35,8 @@ struct Test
} // namespace asl::testing
#define ASL_TEST(CASE) \
static void asl_test_fn_##CASE(); \
static void asl_test_fn_##CASE(); /* NOLINT */ \
/* NOLINTNEXTLINE */ \
static ::asl::testing::Test asl_test_##CASE( \
#CASE, \
asl_test_fn_##CASE); \
@ -45,6 +46,6 @@ struct Test
if (EXPR) {} \
else { ::asl::testing::report_failure(#EXPR); return; }
#define ASL_TEST_EXPECT(EXPR) \
if (EXPR) {} \
else { ::asl::testing::report_failure(#EXPR); }
#define ASL_TEST_EXPECT(...) \
if (__VA_ARGS__) {} \
else { ::asl::testing::report_failure(#__VA_ARGS__); }

View File

@ -9,10 +9,12 @@ package(
cc_library(
name = "utils",
hdrs = [
"counting_allocator.hpp",
"types.hpp",
],
deps = [
"//asl/base",
"//asl/memory:allocator",
],
visibility = ["//asl:__subpackages__"],
)

View File

@ -0,0 +1,48 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/memory/allocator.hpp"
struct CountingAllocator
{
struct Stats
{
isize_t alloc_count{};
isize_t realloc_count{};
isize_t dealloc_count{};
isize_t alive_bytes{};
[[nodiscard]] constexpr isize_t any_alloc_count() const
{
return alloc_count + realloc_count;
}
};
Stats* stats;
[[nodiscard]]
void* alloc(const asl::layout& layout) const
{
stats->alloc_count += 1;
stats->alive_bytes += layout.size;
return asl::GlobalHeap::alloc(layout);
}
void* realloc(void* ptr, const asl::layout& old, const asl::layout& new_layout) const
{
stats->realloc_count += 1;
stats->alive_bytes += new_layout.size - old.size;
return asl::GlobalHeap::realloc(ptr, old, new_layout);
}
void dealloc(void* ptr, const asl::layout& layout) const
{
stats->dealloc_count += 1;
stats->alive_bytes -= layout.size;
asl::GlobalHeap::dealloc(ptr, layout);
}
constexpr bool operator==(const CountingAllocator&) const { return true; }
};
static_assert(asl::allocator<CountingAllocator>);

View File

@ -77,7 +77,7 @@ struct DestructorObserver
{
if (this != &other)
{
destroyed = asl::exchange(other.destroyed, nullptr);
asl::swap(destroyed, other.destroyed);
}
return *this;
}

View File

@ -6,6 +6,18 @@ package(
default_applicable_licenses = ["//:license"],
)
cc_library(
name = "array",
hdrs = [
"array.hpp",
],
deps = [
"//asl/base",
"//asl/types:span",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "box",
hdrs = [
@ -14,6 +26,7 @@ cc_library(
deps = [
"//asl/base",
"//asl/memory",
"//asl/memory:allocator",
"//asl/hashing",
],
visibility = ["//visibility:public"],
@ -78,6 +91,61 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "function",
hdrs = [
"function.hpp",
],
deps = [
"//asl/base",
"//asl/memory",
"//asl/memory:allocator",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "function_ref",
hdrs = [
"function_ref.hpp",
],
deps = [
"//asl/base",
"//asl/memory",
],
visibility = ["//visibility:public"],
)
cc_test(
name = "function_tests",
srcs = ["function_tests.cpp"],
deps = [
"//asl/tests:utils",
"//asl/testing",
"//asl/types:function",
],
)
cc_test(
name = "function_ref_tests",
srcs = ["function_ref_tests.cpp"],
deps = [
"//asl/tests:utils",
"//asl/testing",
"//asl/types:function_ref",
],
)
cc_test(
name = "array_tests",
srcs = ["array_tests.cpp"],
deps = [
"//asl/tests:utils",
"//asl/testing",
"//asl/types:array",
],
)
cc_test(
name = "box_tests",
srcs = ["box_tests.cpp"],

70
asl/types/array.hpp Normal file
View File

@ -0,0 +1,70 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/assert.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/utility.hpp"
#include "asl/types/span.hpp"
namespace asl
{
template<is_object T, isize_t kSize>
requires (kSize > 0)
struct array
{
T m_data[kSize];
[[nodiscard]] constexpr bool is_empty() const { return false; }
[[nodiscard]] constexpr isize_t size() const { return kSize; }
constexpr auto data(this auto&& self)
{
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return static_cast<return_type>(self.m_data);
}
constexpr auto begin(this auto&& self)
{
return contiguous_iterator{self.data()};
}
constexpr auto end(this auto&& self)
{
return contiguous_iterator{self.data() + kSize};
}
template<isize_t kSpanSize>
requires (kSpanSize == kSize || kSpanSize == dynamic_size)
constexpr operator span<const T, kSpanSize>() const // NOLINT(*explicit*)
{
return as_span();
}
template<isize_t kSpanSize>
requires (kSpanSize == kSize || kSpanSize == dynamic_size)
constexpr operator span<T, kSpanSize>() // NOLINT(*explicit*)
{
return as_span();
}
constexpr auto as_span(this auto&& self)
{
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return span<type, kSize>{self.data(), self.size()};
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i <= self.size());
return std::forward_like<decltype(self)>(std::forward<decltype(self)>(self).data()[i]);
}
};
} // namespace asl

122
asl/types/array_tests.cpp Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/types/array.hpp"
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
static_assert(sizeof(asl::array<int32_t, 8>) == 32);
static_assert(asl::default_constructible<asl::array<int, 6>>);
static_assert(asl::trivially_default_constructible<asl::array<int, 6>>);
static_assert(asl::default_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_default_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_copy_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_copy_assignable<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_move_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_move_assignable<asl::array<TrivialType, 6>>);
static_assert(asl::default_constructible<asl::array<TrivialTypeDefaultValue, 6>>);
static_assert(!asl::trivially_default_constructible<asl::array<TrivialTypeDefaultValue, 6>>);
static_assert(asl::trivially_destructible<asl::array<int, 6>>);
static_assert(asl::trivially_destructible<asl::array<TrivialType, 6>>);
static_assert(!asl::trivially_destructible<asl::array<WithDestructor, 6>>);
static_assert(asl::copyable<asl::array<Copyable, 6>>);
static_assert(!asl::copyable<asl::array<MoveableOnly, 6>>);
static_assert(!asl::copyable<asl::array<Pinned, 6>>);
static_assert(asl::moveable<asl::array<Copyable, 6>>);
static_assert(asl::moveable<asl::array<MoveableOnly, 6>>);
static_assert(!asl::moveable<asl::array<Pinned, 6>>);
ASL_TEST(construct_default)
{
asl::array<int, 4> arr{};
ASL_TEST_EXPECT(static_cast<void*>(&arr) == static_cast<void*>(arr.data()));
ASL_TEST_EXPECT(arr[0] == 0);
ASL_TEST_EXPECT(arr[1] == 0);
ASL_TEST_EXPECT(arr[2] == 0);
ASL_TEST_EXPECT(arr[3] == 0);
ASL_TEST_EXPECT(arr.data()[0] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[1] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[2] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[3] == 0); // NOLINT
}
ASL_TEST(construct)
{
asl::array<int, 4> arr{10, 11, 12, 13};
ASL_TEST_EXPECT(arr[0] == 10);
ASL_TEST_EXPECT(arr[1] == 11);
ASL_TEST_EXPECT(arr[2] == 12);
ASL_TEST_EXPECT(arr[3] == 13);
ASL_TEST_EXPECT(arr.data()[0] == 10); // NOLINT
ASL_TEST_EXPECT(arr.data()[1] == 11); // NOLINT
ASL_TEST_EXPECT(arr.data()[2] == 12); // NOLINT
ASL_TEST_EXPECT(arr.data()[3] == 13); // NOLINT
}
static_assert(asl::convertible_to<asl::array<int, 4>, asl::span<int, 4>>);
static_assert(asl::convertible_to<asl::array<int, 4>, asl::span<int>>);
ASL_TEST(sized_span)
{
asl::array<int, 4> arr{10, 11, 12, 13};
const asl::span<int, 4> s1 = arr;
ASL_TEST_EXPECT(s1.size() == 4);
ASL_TEST_EXPECT(s1.data() == arr.data());
ASL_TEST_EXPECT(s1[0] == 10);
ASL_TEST_EXPECT(s1[1] == 11);
ASL_TEST_EXPECT(s1[2] == 12);
ASL_TEST_EXPECT(s1[3] == 13);
}
ASL_TEST(unsized_span)
{
asl::array<int, 4> arr{10, 11, 12, 13};
const asl::span<int> s2 = arr;
ASL_TEST_EXPECT(s2.size() == 4);
ASL_TEST_EXPECT(s2[0] == 10);
ASL_TEST_EXPECT(s2[1] == 11);
ASL_TEST_EXPECT(s2[2] == 12);
ASL_TEST_EXPECT(s2[3] == 13);
}
ASL_TEST(iterator)
{
const asl::array<int, 4> arr{10, 11, 12, 13};
auto it = arr.begin();
auto end = arr.end();
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 10);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 11);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 12);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 13);
it++;
ASL_TEST_ASSERT(it == end);
}

View File

@ -29,21 +29,21 @@ public:
constexpr box(T* ptr, Allocator alloc)
: m_ptr{ptr}
, m_alloc{ASL_MOVE(alloc)}
, m_alloc{std::move(alloc)}
{
ASL_ASSERT(m_ptr != nullptr);
}
constexpr box(box&& other)
: m_ptr{exchange(other.m_ptr, nullptr)}
, m_alloc{ASL_MOVE(other.m_alloc)}
, m_alloc{std::move(other.m_alloc)}
{}
template<is_object U>
requires convertible_from<T*, U*>
constexpr box(box<U, Allocator>&& other) // NOLINT(*-explicit-conversions)
requires convertible_to<U*, T*>
constexpr box(box<U, Allocator>&& other) // NOLINT(*explicit*,*-not-moved)
: m_ptr{exchange(other.m_ptr, nullptr)}
, m_alloc{ASL_MOVE(other.m_alloc)}
, m_alloc{std::move(other.m_alloc)}
{}
constexpr box& operator=(box&& other)
@ -53,7 +53,7 @@ public:
if (m_ptr != nullptr) { reset(); }
m_ptr = exchange(other.m_ptr, nullptr);
m_alloc = ASL_MOVE(other.m_alloc);
m_alloc = std::move(other.m_alloc);
return *this;
}
@ -76,18 +76,21 @@ public:
}
}
constexpr T* get() const { return m_ptr; }
constexpr T& operator*() const
constexpr auto* get(this auto&& self)
{
ASL_ASSERT(m_ptr != nullptr);
return *m_ptr;
return self.m_ptr;
}
constexpr T* operator->() const
constexpr auto&& operator*(this auto&& self)
{
ASL_ASSERT(m_ptr != nullptr);
return m_ptr;
ASL_ASSERT(self.m_ptr != nullptr);
return std::forward_like<decltype(self)&&>(*self.m_ptr);
}
constexpr auto* operator->(this auto&& self)
{
ASL_ASSERT(self.m_ptr != nullptr);
return self.m_ptr;
}
constexpr bool operator==(niche_t) const
@ -99,7 +102,7 @@ public:
requires hashable<T>
friend H AslHashValue(H h, const box& b)
{
return H::combine(ASL_MOVE(h), *b);
return H::combine(std::move(h), *b);
}
template<is_object U, allocator A>
@ -114,8 +117,8 @@ constexpr box<T, Allocator> make_box_in(Allocator allocator, Args&&... args)
requires constructible_from<T, Args&&...>
{
void* raw_ptr = allocator.alloc(layout::of<T>());
auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...);
return box(ptr, ASL_MOVE(allocator));
auto* ptr = construct_at<T>(raw_ptr, std::forward<Args>(args)...);
return box(ptr, std::move(allocator));
}
template<is_object T, allocator Allocator = DefaultAllocator, typename... Args>
@ -124,14 +127,14 @@ constexpr box<T, Allocator> make_box(Args&&... args)
{
Allocator allocator{};
void* raw_ptr = allocator.alloc(layout::of<T>());
auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...);
return box<T>(ptr, ASL_MOVE(allocator));
auto* ptr = construct_at<T>(raw_ptr, std::forward<Args>(args)...);
return box<T>(ptr, std::move(allocator));
}
template<is_object T, allocator A>
constexpr T* leak(box<T, A>&& b)
{
return exchange(b.m_ptr, nullptr);
return exchange(std::move(b).m_ptr, nullptr);
}
} // namespace asl

View File

@ -23,7 +23,7 @@ ASL_TEST(destructor)
ASL_TEST_ASSERT(!d);
auto box3 = ASL_MOVE(box);
auto box3 = std::move(box);
ASL_TEST_ASSERT(!d);
}
@ -35,7 +35,7 @@ ASL_TEST(value)
auto b = asl::make_box<int>(24);
ASL_TEST_EXPECT(*b == 24);
auto b2 = ASL_MOVE(b);
auto b2 = std::move(b);
ASL_TEST_EXPECT(*b2 == 24);
}
@ -44,7 +44,7 @@ ASL_TEST(ptr)
auto b = asl::make_box<int>(24);
auto* ptr1 = b.get();
auto b2 = ASL_MOVE(b);
auto b2 = std::move(b);
auto* ptr2 = b2.get();
ASL_TEST_EXPECT(ptr1 == ptr2);
}
@ -81,7 +81,7 @@ ASL_TEST(niche)
ASL_TEST_EXPECT(destroyed);
}
class Base
class Base // NOLINT
{
public:
virtual ~Base() = default;
@ -94,10 +94,10 @@ public:
int number() override { return 2; }
};
static_assert(asl::convertible_from<asl::box<Base>, asl::box<Derived>>);
static_assert(asl::convertible_from<asl::box<Base>, asl::box<Base>>);
static_assert(!asl::convertible_from<asl::box<Derived>, asl::box<Base>>);
static_assert(!asl::convertible_from<asl::box<int>, asl::box<float>>);
static_assert(asl::convertible_to<asl::box<Derived>, asl::box<Base>>);
static_assert(asl::convertible_to<asl::box<Base>, asl::box<Base>>);
static_assert(!asl::convertible_to<asl::box<Base>, asl::box<Derived>>);
static_assert(!asl::convertible_to<asl::box<float>, asl::box<int>>);
ASL_TEST(derived)
{

288
asl/types/function.hpp Normal file
View File

@ -0,0 +1,288 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/utility.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/integers.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/base/functional.hpp"
namespace asl
{
namespace function_detail
{
static constexpr isize_t kStorageSize = size_of<void*> * 2;
struct Storage
{
alignas(align_of<void*>) byte raw[kStorageSize];
[[nodiscard]]
void* get_ptr() const
{
// NOLINTNEXTLINE(*-const-cast)
return const_cast<void*>(static_cast<const void*>(raw));
}
};
template<typename T>
concept can_be_stored_inline =
size_of<T> <= size_of<Storage> &&
align_of<Storage> % align_of<T> == 0;
enum class FunctionOp : uint8_t
{
kDestroyThis,
kCopyFromOtherToThisUninit,
kMoveFromOtherToThisUninit,
};
template<typename Functor, bool kStoreInline = can_be_stored_inline<Functor>>
struct FunctionImplBase
{
using Allocator = DefaultAllocator;
template<typename T>
static void create(Storage* storage, T&& t)
{
Allocator allocator{};
auto* ptr = alloc_new<Functor>(allocator, std::forward<T>(t));
asl::memcpy(storage->get_ptr(), static_cast<void*>(&ptr), size_of<void*>);
}
static Functor** get_functor_ptr(const Storage* storage)
{
// NOLINTNEXTLINE(*-reinterpret-cast)
return std::launder(reinterpret_cast<Functor**>(storage->get_ptr()));
}
static Functor* get_functor(const Storage* storage)
{
return *get_functor_ptr(storage);
}
static void op(Storage* this_storage, Storage* other_storage, FunctionOp op)
{
switch (op)
{
using enum FunctionOp;
case kDestroyThis:
{
Allocator allocator{};
alloc_delete(allocator, get_functor(this_storage));
break;
}
case kCopyFromOtherToThisUninit:
{
create(this_storage, *static_cast<const Functor*>(get_functor(other_storage)));
break;
}
case kMoveFromOtherToThisUninit:
{
auto* ptr = asl::exchange(*get_functor_ptr(other_storage), nullptr);
asl::memcpy(this_storage->get_ptr(), static_cast<void*>(&ptr), size_of<void*>);
break;
}
default: break;
}
}
};
template<typename Functor>
struct FunctionImplBase<Functor, true>
{
template<typename T>
static void create(Storage* storage, T&& t)
{
new (storage->get_ptr()) Functor(std::forward<T>(t));
}
static Functor* get_functor(const Storage* storage)
{
// NOLINTNEXTLINE(*-reinterpret-cast)
return std::launder(reinterpret_cast<Functor*>(storage->get_ptr()));
}
static void op(Storage* this_storage, Storage* other_storage, FunctionOp op)
{
switch (op)
{
using enum FunctionOp;
case kDestroyThis:
{
destroy(get_functor(this_storage));
break;
}
case kCopyFromOtherToThisUninit:
{
create(this_storage, *static_cast<const Functor*>(get_functor(other_storage)));
break;
}
case kMoveFromOtherToThisUninit:
{
auto* other_functor = get_functor(other_storage);
create(this_storage, std::move(*static_cast<const Functor*>(other_functor)));
destroy(other_functor);
break;
}
default: break;
}
}
};
template<typename Functor, typename R, typename... Args>
struct FunctionImpl : FunctionImplBase<Functor>
{
static R invoke(Args... args, const Storage& storage)
{
auto* functor = FunctionImplBase<Functor>::get_functor(&storage);
return asl::invoke(*functor, std::forward<Args>(args)...);
}
};
template<typename T, typename R, typename... Args>
concept valid_functor =
copy_constructible<T>
&& move_constructible<T>
&& invocable<T, Args...>
&& same_as<R, invoke_result_t<T, Args...>>;
} // namespace function_detail
template<typename T>
class function;
template<typename R, typename... Args>
class function<R(Args...)> // NOLINT(*-member-init)
{
using InvokeFn = R (*)(Args..., const function_detail::Storage&);
using OpFn = void (*)(function_detail::Storage*, function_detail::Storage*, function_detail::FunctionOp);
function_detail::Storage m_storage;
InvokeFn m_invoke{};
OpFn m_op{};
void destroy()
{
if (m_op != nullptr)
{
(*m_op)(&m_storage, nullptr, function_detail::FunctionOp::kDestroyThis);
}
}
public:
function() = default;
template<typename T>
function(T&& func) // NOLINT(*explicit*,*-member-init)
requires (
!same_as<function, un_cvref_t<T>>
&& function_detail::valid_functor<T, R, Args...>
)
{
using Functor = decay_t<T>;
using Impl = function_detail::FunctionImpl<Functor, R, Args...>;
Impl::create(&m_storage, std::forward<T>(func));
m_invoke = &Impl::invoke; // NOLINT(*-member-initializer)
m_op = &Impl::op; // NOLINT(*-member-initializer)
}
function(const function& other) // NOLINT(*-member-init)
: m_invoke{other.m_invoke}
, m_op{other.m_op}
{
if (m_op != nullptr)
{
m_op(
&m_storage,
const_cast<function_detail::Storage*>(&other.m_storage), // NOLINT(*-const-cast)
function_detail::FunctionOp::kCopyFromOtherToThisUninit);
}
}
function(function&& other) // NOLINT(*-member-init)
: m_invoke{asl::exchange(other.m_invoke, nullptr)}
, m_op{asl::exchange(other.m_op, nullptr)}
{
if (m_op != nullptr)
{
m_op(
&m_storage,
&other.m_storage,
function_detail::FunctionOp::kMoveFromOtherToThisUninit);
}
}
~function()
{
destroy();
}
function& operator=(const function& other)
{
if (this != &other)
{
destroy();
m_invoke = other.m_invoke;
m_op = other.m_op;
m_op(
&m_storage,
const_cast<function_detail::Storage*>(&other.m_storage), // NOLINT(*-const-cast)
function_detail::FunctionOp::kCopyFromOtherToThisUninit);
}
return *this;
}
function& operator=(function&& other)
{
if (this != &other)
{
destroy();
m_invoke = asl::exchange(other.m_invoke, nullptr);
m_op = asl::exchange(other.m_op, nullptr);
m_op(
&m_storage,
&other.m_storage,
function_detail::FunctionOp::kMoveFromOtherToThisUninit);
}
return *this;
}
template<typename T>
function& operator=(T&& func)
requires (
!same_as<function, un_cvref_t<T>>
&& function_detail::valid_functor<T, R, Args...>
)
{
destroy();
using Functor = decay_t<T>;
using Impl = function_detail::FunctionImpl<Functor, R, Args...>;
Impl::create(&m_storage, std::forward<T>(func));
m_invoke = &Impl::invoke;
m_op = &Impl::op;
return *this;
}
constexpr R operator()(Args... args) const
{
ASL_ASSERT(m_invoke);
return m_invoke(args..., m_storage);
}
};
} // namespace asl

View File

@ -0,0 +1,72 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/utility.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/functional.hpp"
#include "asl/memory/memory.hpp"
namespace asl
{
template<typename T>
class function_ref;
template<typename R, typename... Args>
class function_ref<R(Args...)>
{
using InvokeFn = R (*)(Args..., void*);
void* m_obj;
InvokeFn m_invoke;
template<typename T>
static R invoke(Args... args, void* obj)
{
// NOLINTNEXTLINE(*-reinterpret-cast)
return asl::invoke(*reinterpret_cast<T*>(obj), std::forward<Args>(args)...);
}
public:
function_ref() = delete;
ASL_DEFAULT_COPY_MOVE(function_ref);
~function_ref() = default;
template<typename T>
function_ref(T&& t) // NOLINT(*-missing-std-forward, *explicit*)
requires (
!same_as<un_cvref_t<T>, function_ref>
&& invocable<T, Args...>
&& same_as<invoke_result_t<T, Args...>, R>
)
// NOLINTNEXTLINE(*cast*)
: m_obj{const_cast<void*>(reinterpret_cast<const void*>(address_of(t)))}
, m_invoke{invoke<un_ref_t<T>>}
{}
template<typename T>
function_ref& operator=(T&& t) // NOLINT(*-missing-std-forward)
requires (
!same_as<un_cvref_t<T>, function_ref>
&& invocable<T, Args...>
&& same_as<invoke_result_t<T, Args...>, R>
)
{
// NOLINTNEXTLINE(*cast*)
m_obj = const_cast<void*>(reinterpret_cast<const void*>(address_of(t)));
m_invoke = invoke<un_ref_t<T>>;
return *this;
}
constexpr R operator()(this function_ref self, Args... args)
{
return self.m_invoke(std::forward<Args>(args)..., self.m_obj);
}
};
} // namespace asl

View File

@ -0,0 +1,54 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/types/function_ref.hpp"
static int add(int a, int b)
{
return a + b;
}
struct Functor
{
int state = 0;
int operator()(int x, int)
{
state += x;
return state;
}
};
static int invoke_fn_ref(asl::function_ref<int(int, int)> fn, int a, int b)
{
return fn(a, b);
}
ASL_TEST(function_ref)
{
const asl::function_ref<int(int, int)> fn(add);
ASL_TEST_EXPECT(invoke_fn_ref(fn, 4, 5) == 9);
ASL_TEST_EXPECT(invoke_fn_ref(add, 4, 5) == 9);
ASL_TEST_EXPECT(invoke_fn_ref([](int a, int b) { return a * b; }, 4, 5) == 20);
Functor fun;
ASL_TEST_EXPECT(invoke_fn_ref(fun, 4, 5) == 4);
ASL_TEST_EXPECT(invoke_fn_ref(fun, 4, 5) == 8);
ASL_TEST_EXPECT(invoke_fn_ref(fun, 4, 5) == 12);
asl::function_ref<int(int, int)> fn2 = fn;
ASL_TEST_EXPECT(invoke_fn_ref(fn2, 4, 5) == 9);
fn2 = [](int a, int b) { return a - b; };
ASL_TEST_EXPECT(invoke_fn_ref(fn2, 4, 5) == -1);
fn2 = fn;
ASL_TEST_EXPECT(invoke_fn_ref(fn2, 4, 5) == 9);
fn2 = add;
ASL_TEST_EXPECT(invoke_fn_ref(fn2, 4, 5) == 9);
}

View File

@ -0,0 +1,195 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/types/function.hpp"
static_assert(asl::function_detail::can_be_stored_inline<int(*)(int, int, int)>);
static_assert(asl::function_detail::can_be_stored_inline<decltype([](){})>);
static_assert(asl::function_detail::can_be_stored_inline<decltype([]() static {})>);
static_assert(asl::function_detail::can_be_stored_inline<decltype([a = 1ULL, b = 2ULL](){ return a + b; })>); // NOLINT
static_assert(asl::function_detail::can_be_stored_inline<decltype([a = 1ULL, b = 2ULL]() mutable { return a = b++; })>); // NOLINT
static_assert(!asl::function_detail::can_be_stored_inline<decltype([a = 1ULL, b = 2ULL, c = 3ULL](){ return a + b + c; })>); // NOLINT
static int add(int x, int y)
{
return x + y;
}
ASL_TEST(function_pointer)
{
const asl::function<int(int, int)> fn = add;
ASL_TEST_EXPECT(fn(4, 5) == 9);
ASL_TEST_EXPECT(fn(40, 50) == 90);
}
ASL_TEST(lambda)
{
const asl::function<int(int, int)> fn = [](int a, int b) {
return a + b;
};
ASL_TEST_EXPECT(fn(4, 5) == 9);
}
ASL_TEST(lambda_static)
{
const asl::function<int(int, int)> fn = [](int a, int b) static {
return a + b;
};
ASL_TEST_EXPECT(fn(4, 5) == 9);
}
ASL_TEST(lambda_static_state)
{
const asl::function<int(int)> fn = [state = 0](int b) mutable {
state += b;
return state;
};
ASL_TEST_EXPECT(fn(1) == 1);
ASL_TEST_EXPECT(fn(2) == 3);
ASL_TEST_EXPECT(fn(3) == 6);
ASL_TEST_EXPECT(fn(4) == 10);
}
ASL_TEST(lambda_state)
{
int state = 0;
const asl::function<void(int)> fn = [&state](int x) {
state += x;
};
ASL_TEST_EXPECT(state == 0);
fn(5);
ASL_TEST_EXPECT(state == 5);
fn(4);
ASL_TEST_EXPECT(state == 9);
}
ASL_TEST(lambda_big_state)
{
int s0 = 0;
int s1 = 0;
int s2 = 0;
int s3 = 0;
const asl::function<void(int)> fn = [&](int x) {
s0 += x;
s1 += x + 1;
s2 += x + 2;
s3 += x + 3;
};
ASL_TEST_EXPECT(s0 == 0);
ASL_TEST_EXPECT(s1 == 0);
ASL_TEST_EXPECT(s2 == 0);
ASL_TEST_EXPECT(s3 == 0);
fn(5);
ASL_TEST_EXPECT(s0 == 5);
ASL_TEST_EXPECT(s1 == 6);
ASL_TEST_EXPECT(s2 == 7);
ASL_TEST_EXPECT(s3 == 8);
fn(4);
ASL_TEST_EXPECT(s0 == 9);
ASL_TEST_EXPECT(s1 == 11);
ASL_TEST_EXPECT(s2 == 13);
ASL_TEST_EXPECT(s3 == 15);
}
struct Functor
{
int state{};
int operator()(int x)
{
state += x;
return state;
}
};
ASL_TEST(functor)
{
const asl::function<int(int)> fn = Functor{};
ASL_TEST_EXPECT(fn(1) == 1);
ASL_TEST_EXPECT(fn(2) == 3);
ASL_TEST_EXPECT(fn(3) == 6);
ASL_TEST_EXPECT(fn(4) == 10);
}
ASL_TEST(copy_move_construct_small)
{
asl::function<int(int, int)> fn = [x = 0](int a, int b) mutable { return x++ + a + b; };
ASL_TEST_EXPECT(fn(1, 3) == 4);
asl::function<int(int, int)> fn2 = fn;
ASL_TEST_EXPECT(fn(1, 3) == 5);
ASL_TEST_EXPECT(fn2(5, 3) == 9);
asl::function<int(int, int)> fn3 = std::move(fn2);
ASL_TEST_EXPECT(fn(1, 3) == 6);
ASL_TEST_EXPECT(fn3(5, 3) == 10);
fn2 = fn;
ASL_TEST_EXPECT(fn(1, 3) == 7);
ASL_TEST_EXPECT(fn2(5, 3) == 11);
ASL_TEST_EXPECT(fn3(5, 3) == 11);
fn3 = std::move(fn);
ASL_TEST_EXPECT(fn2(5, 3) == 12);
ASL_TEST_EXPECT(fn3(5, 3) == 12);
}
ASL_TEST(copy_move_construct_big)
{
const int64_t v1 = 1;
const int64_t v2 = 2;
const int64_t v3 = 3;
const int64_t v4 = 4;
asl::function<int64_t(int)> fn = [=](int x) { return v1 + v2 + v3 + v4 + x; };
ASL_TEST_EXPECT(fn(1) == 11);
asl::function<int64_t(int)> fn2 = fn;
ASL_TEST_EXPECT(fn(3) == 13);
ASL_TEST_EXPECT(fn2(5) == 15);
asl::function<int64_t(int)> fn3 = std::move(fn2);
ASL_TEST_EXPECT(fn(1) == 11);
ASL_TEST_EXPECT(fn3(3) == 13);
fn2 = fn;
ASL_TEST_EXPECT(fn(1) == 11);
ASL_TEST_EXPECT(fn2(5) == 15);
ASL_TEST_EXPECT(fn3(3) == 13);
fn3 = std::move(fn);
ASL_TEST_EXPECT(fn2(5) == 15);
ASL_TEST_EXPECT(fn3(3) == 13);
}
ASL_TEST(replace)
{
asl::function<int(int)> fn = [](int x) { return x + 1; };
ASL_TEST_EXPECT(fn(5) == 6);
fn = [](int x) { return x + 3; };
ASL_TEST_EXPECT(fn(5) == 8);
}
static int foo(const asl::function<int(int, int)>& fn)
{
return fn(5, 5);
}
ASL_TEST(function_parameter)
{
ASL_TEST_EXPECT(foo(add) == 10);
ASL_TEST_EXPECT(foo([](int a, int b) { return a + b; }) == 10);
}

View File

@ -23,7 +23,7 @@ public:
explicit constexpr maybe_uninit(in_place_t, auto&&... args)
requires constructible_from<T, decltype(args)...>
: m_value{ASL_FWD(args)...}
: m_value{std::forward<decltype(args)>(args)...}
{}
constexpr maybe_uninit(const maybe_uninit&) requires trivially_copy_constructible<T> = default;
@ -33,10 +33,10 @@ public:
constexpr maybe_uninit(maybe_uninit&&) requires (!trivially_move_constructible<T>) {} // NOLINT
constexpr maybe_uninit& operator=(const maybe_uninit&) requires trivially_copy_assignable<T> = default;
constexpr maybe_uninit& operator=(const maybe_uninit&) requires (!trivially_copy_assignable<T>) {}
constexpr maybe_uninit& operator=(const maybe_uninit&) requires (!trivially_copy_assignable<T>) { return *this; } // NOLINT
constexpr maybe_uninit& operator=(maybe_uninit&&) requires trivially_move_assignable<T> = default;
constexpr maybe_uninit& operator=(maybe_uninit&&) requires (!trivially_move_assignable<T>) {}
constexpr maybe_uninit& operator=(maybe_uninit&&) requires (!trivially_move_assignable<T>) { return *this; } // NOLINT
constexpr ~maybe_uninit() requires trivially_destructible<T> = default;
constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {} // NOLINT
@ -45,14 +45,14 @@ public:
constexpr void construct_unsafe(auto&&... args)
requires constructible_from<T, decltype(args)...>
{
construct_at<T>(&m_value, ASL_FWD(args)...);
construct_at<T>(&m_value, std::forward<decltype(args)>(args)...);
}
// @Safety Value must have been initialized
constexpr void assign_unsafe(auto&& value)
requires assignable_from<T&, decltype(value)>
{
m_value = ASL_FWD(value);
m_value = std::forward<decltype(value)>(value);
}
// @Safety Value must have been initialized
@ -67,7 +67,7 @@ public:
// @Safety Value must have been initialized
constexpr auto&& as_init_unsafe(this auto&& self)
{
return ASL_FWD(self).m_value;
return std::forward<decltype(self)>(self).m_value;
}
};

View File

@ -10,6 +10,8 @@ static_assert(asl::size_of<int> == asl::size_of<asl::maybe_uninit<int>>);
static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
#define TEST_TYPE_PROPERTIES(PRP) \
static_assert(asl::PRP<asl::maybe_uninit<int>> == asl::PRP<int>); \
static_assert(asl::PRP<asl::maybe_uninit<void*>> == asl::PRP<void*>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialType>> == asl::PRP<TrivialType>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \
static_assert(asl::PRP<asl::maybe_uninit<WithDestructor>> == asl::PRP<WithDestructor>); \
@ -17,6 +19,8 @@ static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
static_assert(asl::PRP<asl::maybe_uninit<MoveableOnly>> == asl::PRP<MoveableOnly>); \
static_assert(asl::PRP<asl::maybe_uninit<Pinned>> == asl::PRP<Pinned>);
// @Todo(C++26) We expect this to break once trivial unions land.
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3074r7.html#just-make-it-work
TEST_TYPE_PROPERTIES(trivially_default_constructible);
TEST_TYPE_PROPERTIES(trivially_copy_constructible);
TEST_TYPE_PROPERTIES(trivially_move_constructible);

View File

@ -74,19 +74,19 @@ class option
if constexpr (!kHasNiche)
{
m_payload.construct_unsafe(ASL_FWD(args)...);
m_payload.construct_unsafe(std::forward<Args>(args)...);
m_has_value = true;
}
else
{
if constexpr (move_assignable<T>)
{
m_payload.assign_unsafe(ASL_MOVE(T{ASL_FWD(args)...}));
m_payload.assign_unsafe(T{std::forward<Args>(args)...});
}
else
{
m_payload.destroy_unsafe();
m_payload.construct_unsafe(ASL_FWD(args)...);
m_payload.construct_unsafe(std::forward<Args>(args)...);
}
}
}
@ -95,7 +95,7 @@ class option
constexpr void assign(U&& arg)
{
ASL_ASSERT(has_value());
m_payload.assign_unsafe(ASL_FWD(arg));
m_payload.assign_unsafe(std::forward<U>(arg));
}
public:
@ -103,32 +103,32 @@ public:
constexpr option() : option{nullopt} {}
// NOLINTNEXTLINE(*-explicit-conversions)
// NOLINTNEXTLINE(*explicit*)
constexpr option(nullopt_t) requires (!kHasNiche) {}
// NOLINTNEXTLINE(*-explicit-conversions)
// NOLINTNEXTLINE(*explicit*)
constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
constexpr explicit (!convertible_to<U&&, T>)
option(U&& value)
requires (
kHasNiche &&
constructible_from<T, U&&> &&
!same_as<un_cvref_t<U>, option>
)
: m_payload{in_place, ASL_FWD(value)}
: m_payload{in_place, std::forward<U>(value)}
{}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
constexpr explicit (!convertible_to<U&&, T>)
option(U&& value)
requires (
!kHasNiche &&
constructible_from<T, U&&> &&
!is_option<U>
)
: m_payload{in_place, ASL_FWD(value)}
: m_payload{in_place, std::forward<U>(value)}
, m_has_value{true}
{}
@ -154,12 +154,12 @@ public:
{
if (other.has_value())
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
construct(std::move(other.m_payload.as_init_unsafe()));
}
}
template<typename U>
constexpr explicit (!convertible_from<T, const U&>)
constexpr explicit (!convertible_to<const U&, T>)
option(const option<U>& other)
requires (
constructible_from<T, const U&> &&
@ -174,7 +174,7 @@ public:
}
template<typename U>
constexpr explicit (!convertible_from<T, U&&>)
constexpr explicit (!convertible_to<U&&, T>)
option(option<U>&& other)
requires (
constructible_from<T, U&&> &&
@ -184,7 +184,7 @@ public:
{
if (other.has_value())
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
construct(std::move(other).m_payload.as_init_unsafe());
}
}
@ -204,11 +204,11 @@ public:
{
if (has_value())
{
assign(ASL_FWD(value));
assign(std::forward<U>(value));
}
else
{
construct(ASL_FWD(value));
construct(std::forward<U>(value));
}
return *this;
@ -259,11 +259,11 @@ public:
{
if (has_value())
{
assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
assign(std::move(other.m_payload.as_init_unsafe()));
}
else
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
construct(std::move(other.m_payload.as_init_unsafe()));
}
}
else if (has_value())
@ -313,11 +313,11 @@ public:
{
if (has_value())
{
assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
assign(std::move(other).m_payload.as_init_unsafe());
}
else
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
construct(std::move(other).m_payload.as_init_unsafe());
}
}
else if (has_value())
@ -342,7 +342,7 @@ public:
{
if constexpr (move_assignable<T>)
{
m_payload.assign_unsafe(ASL_MOVE(T{niche_t{}}));
m_payload.assign_unsafe(std::move(T{niche_t{}}));
}
else
{
@ -357,7 +357,7 @@ public:
}
}
constexpr bool has_value() const
[[nodiscard]] constexpr bool has_value() const
{
if constexpr (kHasNiche)
{
@ -372,28 +372,28 @@ public:
constexpr auto&& value(this auto&& self)
{
ASL_ASSERT_RELEASE(self.has_value());
return ASL_FWD(self).m_payload.as_init_unsafe();
return std::forward<decltype(self)>(self).m_payload.as_init_unsafe();
}
template<typename U>
constexpr T value_or(U&& other_value) const&
requires copy_constructible<T> && convertible_from<T, U&&>
requires copy_constructible<T> && convertible_to<U&&, T>
{
return has_value() ? value() : static_cast<T>(ASL_FWD(other_value));
return has_value() ? value() : static_cast<T>(std::forward<U>(other_value));
}
template<typename U>
constexpr T value_or(U&& other_value) &&
requires move_constructible<T> && convertible_from<T, U&&>
requires move_constructible<T> && convertible_to<U&&, T>
{
return has_value() ? ASL_MOVE(value()) : static_cast<T>(ASL_FWD(other_value));
return has_value() ? std::move(value()) : static_cast<T>(std::forward<U>(other_value));
}
constexpr T& emplace(auto&&... args) &
requires constructible_from<T, decltype(args)...>
{
if (has_value()) { reset(); }
construct(ASL_FWD(args)...);
construct(std::forward<decltype(args)>(args)...);
return value();
}
@ -405,7 +405,7 @@ public:
if (self.has_value())
{
return invoke(ASL_FWD(f), ASL_FWD(self).value());
return invoke(std::forward<F>(f), std::forward<decltype(self)>(self).value());
}
return Result{ asl::nullopt };
}
@ -417,7 +417,7 @@ public:
if (self.has_value())
{
return option<un_cvref_t<Result>>{
invoke(ASL_FWD(f), ASL_FWD(self).value())
invoke(std::forward<F>(f), std::forward<decltype(self)>(self).value())
};
}
return option<un_cvref_t<Result>>{ asl::nullopt };
@ -427,14 +427,14 @@ public:
constexpr option or_else(F&& f) const&
requires same_as<un_cvref_t<invoke_result_t<F>>, option>
{
return has_value() ? *this : invoke(ASL_FWD(f));
return has_value() ? *this : invoke(std::forward<F>(f));
}
template<typename F>
constexpr option or_else(F&& f) &&
requires same_as<un_cvref_t<invoke_result_t<F>>, option>
{
return has_value() ? ASL_MOVE(*this) : invoke(ASL_FWD(f));
return has_value() ? std::move(*this) : invoke(std::forward<F>(f));
}
template<typename H>
@ -443,9 +443,9 @@ public:
{
if (!opt.has_value())
{
return H::combine(ASL_MOVE(h), 0);
return H::combine(std::move(h), 0);
}
return H::combine(ASL_MOVE(h), 1, opt.value());
return H::combine(std::move(h), 1, opt.value());
}
};

View File

@ -62,20 +62,20 @@ static_assert(!asl::move_assignable<asl::option<Pinned>>);
static_assert(asl::assignable_from<asl::option<Base*>&, asl::option<Derived*>>);
static_assert(!asl::assignable_from<asl::option<Derived*>&, asl::option<Base*>>);
static_assert(asl::convertible_from<asl::option<Base*>, asl::option<Derived*>>);
static_assert(!asl::convertible_from<asl::option<Derived*>, asl::option<Base*>>);
static_assert(asl::convertible_to<asl::option<Derived*>, asl::option<Base*>>);
static_assert(!asl::convertible_to<asl::option<Base*>, asl::option<Derived*>>);
class ExplicitConversion { public: explicit ExplicitConversion(int) {} };
class ImplicitConversion { public: ImplicitConversion(int) {} }; // NOLINT
static_assert(!asl::convertible_from<ExplicitConversion, int>);
static_assert(asl::convertible_from<ImplicitConversion, int>);
static_assert(!asl::convertible_to<int, ExplicitConversion>);
static_assert(asl::convertible_to<int, ImplicitConversion>);
static_assert(!asl::convertible_from<asl::option<ExplicitConversion>, int>);
static_assert(asl::convertible_from<asl::option<ImplicitConversion>, int>);
static_assert(!asl::convertible_to<int, asl::option<ExplicitConversion>>);
static_assert(asl::convertible_to<int, asl::option<ImplicitConversion>>);
static_assert(!asl::convertible_from<asl::option<ExplicitConversion>, asl::option<int>>);
static_assert(asl::convertible_from<asl::option<ImplicitConversion>, asl::option<int>>);
static_assert(!asl::convertible_to<asl::option<int>, asl::option<ExplicitConversion>>);
static_assert(asl::convertible_to<asl::option<int>, asl::option<ImplicitConversion>>);
static_assert(asl::trivially_copy_constructible<asl::option<int>>);
static_assert(asl::trivially_copy_constructible<asl::option<TrivialType>>);
@ -99,8 +99,8 @@ static_assert(!asl::trivially_move_assignable<asl::option<MoveableOnly>>);
ASL_TEST(make_null)
{
asl::option<int> a;
asl::option<int> b = asl::nullopt;
const asl::option<int> a;
const asl::option<int> b = asl::nullopt;
ASL_TEST_EXPECT(!a.has_value());
ASL_TEST_EXPECT(!b.has_value());
@ -108,7 +108,7 @@ ASL_TEST(make_null)
ASL_TEST(make_value)
{
asl::option<int> a = 48;
const asl::option<int> a = 48;
ASL_TEST_EXPECT(a.has_value());
}
@ -129,10 +129,10 @@ ASL_TEST(call_destructor)
{
DestructorObserver obs(&destroyed);
asl::option<DestructorObserver> opt(ASL_MOVE(obs));
asl::option<DestructorObserver> opt(std::move(obs));
ASL_TEST_EXPECT(!destroyed);
asl::option<DestructorObserver> opt2 = ASL_MOVE(opt);
const asl::option<DestructorObserver> opt2 = std::move(opt);
ASL_TEST_EXPECT(!destroyed);
}
@ -172,7 +172,7 @@ ASL_TEST(value_move)
ASL_TEST_EXPECT(!destroyed);
{
auto x = ASL_MOVE(opt).value();
auto x = std::move(opt).value();
ASL_TEST_EXPECT(!destroyed);
}
@ -208,27 +208,27 @@ ASL_TEST(convert_copy)
ASL_TEST(convert_move)
{
asl::option<uint8_t> opt8 = uint8_t{8};
asl::option<uint16_t> opt16 = ASL_MOVE(opt8);
asl::option<uint16_t> opt16 = std::move(opt8);
ASL_TEST_ASSERT(opt16.has_value());
ASL_TEST_EXPECT(opt16.value() == 8);
opt8 = ASL_MOVE(uint8_t{10});
opt8 = uint8_t{10};
ASL_TEST_ASSERT(opt8.has_value());
ASL_TEST_EXPECT(opt8.value() == 10);
opt16 = asl::nullopt;
ASL_TEST_EXPECT(!opt16.has_value());
opt16 = ASL_MOVE(opt8);
opt16 = std::move(opt8);
ASL_TEST_ASSERT(opt16.has_value());
ASL_TEST_EXPECT(opt16.value() == 10);
}
ASL_TEST(value_or)
{
asl::option<int> a = asl::nullopt;
asl::option<int> b = 2;
const asl::option<int> a = asl::nullopt;
const asl::option<int> b = 2;
ASL_TEST_EXPECT(a.value_or(5) == 5);
ASL_TEST_EXPECT(b.value_or(5) == 2);
@ -296,8 +296,8 @@ ASL_TEST(transform)
ASL_TEST(or_else)
{
asl::option<int> a = 5;
asl::option<int> b;
const asl::option<int> a = 5;
const asl::option<int> b;
auto fn = []() -> asl::option<int> { return 12; };

View File

@ -14,6 +14,7 @@ namespace asl
static constexpr isize_t dynamic_size = -1;
// NOLINTBEGIN(*-convert-member-functions-to-static)
template<typename T>
class contiguous_iterator
{
@ -22,7 +23,7 @@ class contiguous_iterator
public:
constexpr explicit contiguous_iterator(T* ptr) : m_ptr{ptr} {}
constexpr bool operator==(const contiguous_iterator& other) const = default;
constexpr bool operator==(this contiguous_iterator self, contiguous_iterator other) = default;
constexpr contiguous_iterator& operator++()
{
@ -35,9 +36,9 @@ public:
return contiguous_iterator{ exchange(m_ptr, m_ptr + 1) };
}
constexpr T& operator*() const { return *m_ptr; }
constexpr T& operator*(this contiguous_iterator self) { return *self.m_ptr; }
constexpr T* operator->() const { return m_ptr; }
constexpr T* operator->(this contiguous_iterator self) { return self.m_ptr; }
};
template<typename T>
@ -67,7 +68,7 @@ public:
, m_size{size}
{}
constexpr explicit span(T* data, isize_t size)
constexpr explicit span(T* data, [[maybe_unused]] isize_t size)
requires (!kIsDynamic)
: m_data{data}
{
@ -75,27 +76,27 @@ public:
}
template<isize_t N>
constexpr span(T (&array)[N]) // NOLINT(*-explicit-conversions)
constexpr span(T (&array)[N]) // NOLINT(*explicit*)
requires (kIsDynamic)
: m_data{array}
: m_data{static_cast<T*>(array)}
, m_size{N}
{}
template<isize_t N>
constexpr span(T (&array)[N]) // NOLINT(*-explicit-conversions)
constexpr span(T (&array)[N]) // NOLINT(*explicit*)
requires (!kIsDynamic) && (N == kSize)
: m_data{array}
: m_data{static_cast<T*>(array)}
{}
template<is_object U, isize_t kOtherSize>
constexpr explicit(!kIsDynamic)
span(const span<U, kOtherSize>& other) // NOLINT(*-explicit-conversions)
span(const span<U, kOtherSize>& other)
requires (
(
kIsDynamic ||
is_dynamic(kOtherSize) ||
kOtherSize == kSize
) && convertible_from<T(&)[], U(&)[]>
) && convertible_to<U(&)[], T(&)[]>
)
: span{static_cast<U*>(other.data()), other.size()}
{
@ -109,115 +110,116 @@ public:
~span() = default;
constexpr isize_t size() const
[[nodiscard]] constexpr isize_t size(this span self)
{
if constexpr (kIsDynamic) { return m_size; }
if constexpr (kIsDynamic) { return self.m_size; }
else { return kSize; }
}
constexpr isize_t size_bytes() const { return size() * size_of<T>; }
[[nodiscard]] constexpr isize_t size_bytes(this span self) { return self.size() * size_of<T>; }
constexpr bool is_empty() const { return size() == 0; }
[[nodiscard]] constexpr bool is_empty(this span self) { return self.size() == 0; }
constexpr T* data() const { return m_data; }
[[nodiscard]] constexpr T* data(this span self) { return self.m_data; }
constexpr contiguous_iterator<T> begin() const
[[nodiscard]] constexpr contiguous_iterator<T> begin(this span self)
{
return contiguous_iterator{m_data};
return contiguous_iterator{self.m_data};
}
constexpr contiguous_iterator<T> end() const
[[nodiscard]] constexpr contiguous_iterator<T> end(this span self)
{
return contiguous_iterator{m_data + size()};
return contiguous_iterator{self.m_data + self.size()};
}
constexpr T& operator[](isize_t i) const
constexpr T& operator[](this span self, isize_t i)
{
ASL_ASSERT(i >= 0 && i < size());
return m_data[i]; // NOLINT(*-pointer-arithmetic)
ASL_ASSERT(i >= 0 && i < self.size());
return self.m_data[i]; // NOLINT(*-pointer-arithmetic)
}
template<isize_t kOffset, isize_t kSubSize = dynamic_size>
constexpr auto subspan() const
[[nodiscard]] constexpr auto subspan(this span self)
requires (
kOffset >= 0 &&
(kIsDynamic || kOffset <= kSize) &&
(kIsDynamic || is_dynamic(kSubSize) || kSubSize <= kSize - kOffset)
)
{
ASL_ASSERT(kOffset <= size());
ASL_ASSERT(kOffset <= self.size());
if constexpr (is_dynamic(kSubSize))
{
if constexpr (kIsDynamic)
{
return span<T>(data() + kOffset, size() - kOffset);
return span<T>(self.data() + kOffset, self.size() - kOffset);
}
else
{
return span<T, kSize - kOffset>(data() + kOffset, size() - kOffset);
return span<T, kSize - kOffset>(self.data() + kOffset, self.size() - kOffset);
}
}
else
{
ASL_ASSERT(kSubSize <= size() - kOffset);
return span<T, kSubSize>(data() + kOffset, kSubSize);
ASL_ASSERT(kSubSize <= self.size() - kOffset);
return span<T, kSubSize>(self.data() + kOffset, kSubSize);
}
}
constexpr span<T> subspan(isize_t offset) const
[[nodiscard]] constexpr span<T> subspan(this span self, isize_t offset)
{
ASL_ASSERT(offset <= size());
return span<T>{ data() + offset, size() - offset };
ASL_ASSERT(offset <= self.size());
return span<T>{ self.data() + offset, self.size() - offset };
}
constexpr span<T> subspan(isize_t offset, isize_t sub_size) const
[[nodiscard]] constexpr span<T> subspan(this span self, isize_t offset, isize_t sub_size)
{
ASL_ASSERT(offset <= size() && !is_dynamic(sub_size));
ASL_ASSERT(sub_size <= size() - offset);
return span<T>{ data() + offset, sub_size };
ASL_ASSERT(offset <= self.size() && !is_dynamic(sub_size));
ASL_ASSERT(sub_size <= self.size() - offset);
return span<T>{ self.data() + offset, sub_size };
}
template<isize_t kSubSize>
constexpr auto first() const
[[nodiscard]] constexpr auto first(this span self)
requires (
kSubSize >= 0 &&
(kIsDynamic || kSubSize <= kSize)
)
{
ASL_ASSERT(kSubSize <= size());
return span<T, kSubSize>{ data(), kSubSize };
ASL_ASSERT(kSubSize <= self.size());
return span<T, kSubSize>{ self.data(), kSubSize };
}
constexpr span<T> first(isize_t sub_size) const
[[nodiscard]] constexpr span<T> first(this span self, isize_t sub_size)
{
ASL_ASSERT(sub_size >= 0 && sub_size <= size());
return span<T>{ data(), sub_size };
ASL_ASSERT(sub_size >= 0 && sub_size <= self.size());
return span<T>{ self.data(), sub_size };
}
template<isize_t kSubSize>
constexpr auto last() const
[[nodiscard]] constexpr auto last(this span self)
requires (
kSubSize >= 0 &&
(kIsDynamic || kSubSize <= kSize)
)
{
ASL_ASSERT(kSubSize <= size());
return span<T, kSubSize>{ data() + size() - kSubSize, kSubSize };
ASL_ASSERT(kSubSize <= self.size());
return span<T, kSubSize>{ self.data() + self.size() - kSubSize, kSubSize };
}
constexpr span<T> last(isize_t sub_size) const
[[nodiscard]] constexpr span<T> last(this span self, isize_t sub_size)
{
ASL_ASSERT(sub_size >= 0 && sub_size <= size());
return span<T>{ data() + size() - sub_size, sub_size };
ASL_ASSERT(sub_size >= 0 && sub_size <= self.size());
return span<T>{ self.data() + self.size() - sub_size, sub_size };
}
};
// NOLINTEND(*-convert-member-functions-to-static)
template<is_object T, isize_t kSize>
inline span<const byte> as_bytes(span<T, kSize> s)
{
return span<const byte>(
reinterpret_cast<const byte*>(s.data()),
reinterpret_cast<const byte*>(s.data()), // NOLINT(*-reinterpret-cast)
s.size_bytes());
}
@ -226,7 +228,7 @@ inline span<byte> as_mutable_bytes(span<T, kSize> s)
requires (!is_const<T>)
{
return span<byte>(
reinterpret_cast<byte*>(s.data()),
reinterpret_cast<byte*>(s.data()), // NOLINT(*-reinterpret-cast)
s.size_bytes());
}

View File

@ -17,7 +17,7 @@ static_assert(asl::size_of<asl::span<int, 2>> == asl::size_of<void*>);
ASL_TEST(empty_dynamic)
{
asl::span<int> s;
const asl::span<int> s;
ASL_TEST_EXPECT(s.size() == 0);
ASL_TEST_EXPECT(s.size_bytes() == 0);
ASL_TEST_EXPECT(s.is_empty());
@ -26,7 +26,7 @@ ASL_TEST(empty_dynamic)
ASL_TEST(empty_static)
{
asl::span<int, 0> s;
const asl::span<int, 0> s;
ASL_TEST_EXPECT(s.size() == 0);
ASL_TEST_EXPECT(s.size_bytes() == 0);
ASL_TEST_EXPECT(s.is_empty());
@ -35,7 +35,7 @@ ASL_TEST(empty_static)
ASL_TEST(from_array_dynamic)
{
int array[] = {1, 2, 3};
asl::span<int> span = array;
const asl::span<int> span = array;
ASL_TEST_ASSERT(span.size() == 3);
ASL_TEST_EXPECT(span[0] == 1);
ASL_TEST_EXPECT(span[1] == 2);
@ -56,7 +56,7 @@ static_assert(!asl::constructible_from<asl::span<int32_t, 8>, int32_t(&)[10]>);
ASL_TEST(from_array_static)
{
int array[] = {1, 2, 3};
asl::span<int, 3> span = array;
const asl::span<int, 3> span = array;
ASL_TEST_ASSERT(span.size() == 3);
ASL_TEST_EXPECT(span[0] == 1);
ASL_TEST_EXPECT(span[1] == 2);
@ -77,21 +77,21 @@ ASL_TEST(conversion)
{
int array[] = {1, 2, 3};
asl::span<int> span1 = array;
const asl::span<int> span1 = array;
asl::span<int, 3> span2{span1};
const asl::span<int, 3> span2{span1};
ASL_TEST_ASSERT(span2.size() == 3);
ASL_TEST_EXPECT(span2[0] == 1);
ASL_TEST_EXPECT(span2[1] == 2);
ASL_TEST_EXPECT(span2[2] == 3);
asl::span<int> span3 = span2;
const asl::span<int> span3 = span2;
ASL_TEST_ASSERT(span3.size() == 3);
ASL_TEST_EXPECT(span3[0] == 1);
ASL_TEST_EXPECT(span3[1] == 2);
ASL_TEST_EXPECT(span3[2] == 3);
asl::span<const int, 3> span4{span2};
const asl::span<const int, 3> span4{span2};
ASL_TEST_ASSERT(span4.size() == 3);
ASL_TEST_EXPECT(span4[0] == 1);
ASL_TEST_EXPECT(span4[1] == 2);
@ -138,7 +138,7 @@ static_assert(!IsValidSubspan<asl::span<int, 4>, 2, 3>);
ASL_TEST(subspan_static_from_static)
{
int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array};
const asl::span<int, 4> span{array};
auto s1 = span.subspan<0>();
ASL_TEST_ASSERT(s1.size() == 4);
@ -164,7 +164,7 @@ ASL_TEST(subspan_static_from_static)
ASL_TEST(subspan_static_from_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.subspan<0>();
ASL_TEST_ASSERT(s1.size() == 4);
@ -190,7 +190,7 @@ ASL_TEST(subspan_static_from_dynamic)
ASL_TEST(subspan_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.subspan(0);
ASL_TEST_ASSERT(s1.size() == 4);
@ -249,7 +249,7 @@ static_assert(!IsValidFirst<asl::span<int, 4>, asl::dynamic_size>);
ASL_TEST(first_static_from_static)
{
int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array};
const asl::span<int, 4> span{array};
auto s1 = span.first<0>();
ASL_TEST_ASSERT(s1.size() == 0);
@ -270,7 +270,7 @@ ASL_TEST(first_static_from_static)
ASL_TEST(first_static_from_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.first<0>();
ASL_TEST_ASSERT(s1.size() == 0);
@ -291,7 +291,7 @@ ASL_TEST(first_static_from_dynamic)
ASL_TEST(first_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.first(0);
ASL_TEST_ASSERT(s1.size() == 0);
@ -345,7 +345,7 @@ static_assert(!IsValidLast<asl::span<int, 4>, asl::dynamic_size>);
ASL_TEST(last_static_from_static)
{
int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array};
const asl::span<int, 4> span{array};
auto s1 = span.last<0>();
ASL_TEST_ASSERT(s1.size() == 0);
@ -366,7 +366,7 @@ ASL_TEST(last_static_from_static)
ASL_TEST(last_static_from_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.last<0>();
ASL_TEST_ASSERT(s1.size() == 0);
@ -387,7 +387,7 @@ ASL_TEST(last_static_from_dynamic)
ASL_TEST(last_dynamic)
{
int array[] = {1, 2, 3, 4};
asl::span<int> span{array};
const asl::span<int> span{array};
auto s1 = span.last(0);
ASL_TEST_ASSERT(s1.size() == 0);
@ -416,8 +416,8 @@ static_assert(HasAsMutableBytes<const int*>);
ASL_TEST(as_bytes)
{
uint32_t data[] = {0x01020304, 0x05060708};
asl::span s1(data);
asl::span s2 = asl::as_bytes(s1);
const asl::span s1(data);
const asl::span s2 = asl::as_bytes(s1);
ASL_TEST_ASSERT(s2.size() == 8);
ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04);
@ -433,8 +433,8 @@ ASL_TEST(as_bytes)
ASL_TEST(as_mutable_bytes)
{
uint32_t data[] = {0x01020304, 0x05060708};
asl::span s1(data);
asl::span s2 = asl::as_mutable_bytes(s1);
const asl::span s1(data);
const asl::span s2 = asl::as_mutable_bytes(s1);
ASL_TEST_ASSERT(s2.size() == 8);
ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04);

View File

@ -11,9 +11,11 @@
// @Todo Use custom allocator
using Allocator = asl::DefaultAllocator;
// NOLINTNEXTLINE(*-non-const-global-variables)
static Allocator g_allocator{};
namespace
namespace asl
{
struct StatusInternal
@ -23,7 +25,7 @@ struct StatusInternal
asl::atomic<int32_t> ref_count;
constexpr StatusInternal(asl::string<Allocator>&& msg_, asl::status_code code_)
: msg{ASL_MOVE(msg_)}
: msg{std::move(msg_)}
, code{code_}
{
ASL_ASSERT(code != asl::status_code::ok);
@ -31,7 +33,7 @@ struct StatusInternal
}
};
} // anonymous namespace
} // namespace asl
asl::status::status(status_code code, string_view msg)
: m_payload{alloc_new<StatusInternal>(g_allocator, msg, code)}
@ -41,36 +43,35 @@ asl::status::status(status_code code, string_view fmt, span<format_internals::ty
{
StringWriter<Allocator> sink{g_allocator};
format_internals::format(&sink, fmt, args);
m_payload = alloc_new<StatusInternal>(g_allocator, ASL_MOVE(sink).finish(), code);
m_payload = alloc_new<StatusInternal>(g_allocator, std::move(sink).finish(), code);
}
asl::status_code asl::status::code_internal() const
{
ASL_ASSERT(!is_inline());
return reinterpret_cast<const StatusInternal*>(m_payload)->code;
return m_payload->code;
}
asl::string_view asl::status::message_internal() const
{
ASL_ASSERT(!is_inline());
return reinterpret_cast<const StatusInternal*>(m_payload)->msg;
return m_payload->msg;
}
void asl::status::ref()
{
ASL_ASSERT(!is_inline());
auto* internal = reinterpret_cast<StatusInternal*>(m_payload);
atomic_fetch_increment(&internal->ref_count, memory_order::relaxed);
atomic_fetch_increment(&m_payload->ref_count, memory_order::relaxed);
}
void asl::status::unref()
{
ASL_ASSERT(!is_inline());
auto* internal = reinterpret_cast<StatusInternal*>(m_payload);
if (atomic_fetch_decrement(&internal->ref_count, memory_order::release) == 1)
if (atomic_fetch_decrement(&m_payload->ref_count, memory_order::release) == 1)
{
atomic_fence(memory_order::acquire);
alloc_delete(g_allocator, internal);
alloc_delete(g_allocator, m_payload);
m_payload = nullptr;
}
}

View File

@ -22,15 +22,17 @@ enum class status_code : uint8_t
invalid_argument = 4,
};
struct StatusInternal;
class status
{
void* m_payload{};
StatusInternal* m_payload{};
static constexpr void* status_to_payload(status_code code)
static constexpr StatusInternal* status_to_payload(status_code code)
{
return code == status_code::ok
? nullptr
: bit_cast<void*>((static_cast<uintptr_t>(code) << 1) | 1);
: bit_cast<StatusInternal*>((static_cast<uintptr_t>(code) << 1) | 1);
}
static constexpr status_code payload_to_status(void* payload)
@ -38,12 +40,12 @@ class status
return static_cast<status_code>(bit_cast<uintptr_t>(payload) >> 1);
}
constexpr bool is_inline() const
[[nodiscard]] constexpr bool is_inline() const
{
return m_payload == nullptr || (bit_cast<uintptr_t>(m_payload) & 1) != 0;
}
constexpr status_code code_inline() const
[[nodiscard]] constexpr status_code code_inline() const
{
ASL_ASSERT(is_inline());
if (m_payload == nullptr)
@ -53,8 +55,8 @@ class status
return payload_to_status(m_payload);
}
status_code code_internal() const;
string_view message_internal() const;
[[nodiscard]] status_code code_internal() const;
[[nodiscard]] string_view message_internal() const;
void ref();
void unref();
@ -103,17 +105,17 @@ public:
return *this;
}
constexpr bool ok() const
[[nodiscard]] constexpr bool ok() const
{
return m_payload == nullptr;
}
constexpr status_code code() const
[[nodiscard]] constexpr status_code code() const
{
return is_inline() ? code_inline() : code_internal();
}
constexpr string_view message() const
[[nodiscard]] constexpr string_view message() const
{
if (!is_inline())
{
@ -122,7 +124,7 @@ public:
return {};
}
constexpr status&& throw_status() && { return ASL_MOVE(*this); }
constexpr status&& throw_status() && { return std::move(*this); }
friend void AslFormat(Formatter& f, const status&);
@ -131,9 +133,9 @@ public:
{
if (s.is_inline())
{
return H::combine(ASL_MOVE(h), s.code());
return H::combine(std::move(h), s.code());
}
return H::combine(ASL_MOVE(h), s.code(), s.message());
return H::combine(std::move(h), s.code(), s.message());
}
};
@ -156,6 +158,6 @@ ASL_DEFINE_ERROR_(internal)
ASL_DEFINE_ERROR_(runtime)
ASL_DEFINE_ERROR_(invalid_argument)
#define ASL_TRY(VALUE) if (VALUE.ok()) {} else { return ASL_MOVE(VALUE).throw_status(); }
#define ASL_TRY(VALUE) if ((VALUE).ok()) {} else { return std::move(VALUE).throw_status(); }
} // namespace asl

View File

@ -33,11 +33,11 @@ public:
constexpr status_or(status_or&& other)
requires move_constructible<T>
: m_status{ASL_MOVE(other.m_status)}
: m_status{std::move(other.m_status)}
{
if (other.ok())
{
m_value.construct_unsafe(ASL_MOVE(other.m_value.as_init_unsafe()));
m_value.construct_unsafe(std::move(other.m_value.as_init_unsafe()));
}
}
@ -75,7 +75,7 @@ public:
{
if (other.ok())
{
m_value.assign_unsafe(ASL_MOVE(other.m_value.as_init_unsafe()));
m_value.assign_unsafe(std::move(other.m_value.as_init_unsafe()));
}
else
{
@ -84,9 +84,9 @@ public:
}
else if (other.ok())
{
m_value.construct_unsafe(ASL_MOVE(other.m_value.as_init_unsafe()));
m_value.construct_unsafe(std::move(other.m_value.as_init_unsafe()));
}
m_status = ASL_MOVE(other.m_status);
m_status = std::move(other.m_status);
}
return *this;
}
@ -99,14 +99,14 @@ public:
}
}
// NOLINTNEXTLINE(*-explicit-conversions)
// NOLINTNEXTLINE(*explicit*)
constexpr status_or(const status& status) : m_status{status}
{
ASL_ASSERT_RELEASE(!m_status.ok());
}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr status_or(status&& status) : m_status{ASL_MOVE(status)}
// NOLINTNEXTLINE(*explicit*)
constexpr status_or(status&& status) : m_status{std::move(status)}
{
ASL_ASSERT_RELEASE(!m_status.ok());
}
@ -114,7 +114,7 @@ public:
status_or& operator=(status status) = delete;
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
constexpr explicit (!convertible_to<U&&, T>)
status_or(U&& value)
requires (
constructible_from<T, U&&> &&
@ -122,35 +122,35 @@ public:
!same_as<un_cvref_t<U>, status>
)
: m_status{status_code::ok}
, m_value{in_place, ASL_FWD(value)}
, m_value{in_place, std::forward<U>(value)}
{}
constexpr bool ok() const { return m_status.ok(); }
[[nodiscard]] constexpr bool ok() const { return m_status.ok(); }
constexpr status_code code() const { return m_status.code(); }
[[nodiscard]] constexpr status_code code() const { return m_status.code(); }
constexpr string_view message() const { return m_status.message(); }
[[nodiscard]] constexpr string_view message() const { return m_status.message(); }
constexpr status&& throw_status() && { return ASL_MOVE(m_status); }
constexpr status&& throw_status() && { return std::move(m_status); }
constexpr auto&& value(this auto&& self)
{
ASL_ASSERT_RELEASE(self.ok());
return ASL_FWD(self).m_value.as_init_unsafe();
return std::forward<decltype(self)>(self).m_value.as_init_unsafe();
}
template<typename U>
constexpr T value_or(U&& other_value) const&
requires copy_constructible<T> && convertible_from<T, U&&>
requires copy_constructible<T> && convertible_to<U&&, T>
{
return ok() ? value() : static_cast<T>(ASL_FWD(other_value));
return ok() ? value() : static_cast<T>(std::forward<U>(other_value));
}
template<typename U>
constexpr T value_or(U&& other_value) &&
requires move_constructible<T> && convertible_from<T, U&&>
requires move_constructible<T> && convertible_to<U&&, T>
{
return ok() ? ASL_MOVE(value()) : static_cast<T>(ASL_FWD(other_value));
return ok() ? std::move(value()) : static_cast<T>(std::forward<U>(other_value));
}
friend void AslFormat(Formatter& f, const status_or& status)
@ -164,9 +164,9 @@ public:
{
if (s.ok())
{
return H::combine(ASL_MOVE(h), s.m_status, s.value());
return H::combine(std::move(h), s.m_status, s.value());
}
return H::combine(ASL_MOVE(h), s.m_status);
return H::combine(std::move(h), s.m_status);
}
};

View File

@ -20,24 +20,24 @@ static_assert(!asl::moveable<asl::status_or<Pinned>>);
ASL_TEST(ok)
{
asl::status_or<int> s = 6;
const asl::status_or<int> s = 6;
ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(s.code() == asl::status_code::ok);
}
ASL_TEST(from_status)
{
asl::status_or<char> s = asl::internal_error();
const asl::status_or<char> s = asl::internal_error();
ASL_TEST_EXPECT(!s.ok());
ASL_TEST_EXPECT(s.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s.message() == ""_sv);
asl::status_or<int> s2 = asl::internal_error("oh no");
const asl::status_or<int> s2 = asl::internal_error("oh no");
ASL_TEST_EXPECT(!s2.ok());
ASL_TEST_EXPECT(s2.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s2.message() == "oh no"_sv);
asl::status_or<int> s3 = asl::internal_error("{} {}", 1, 2);
const asl::status_or<int> s3 = asl::internal_error("{} {}", 1, 2);
ASL_TEST_EXPECT(!s3.ok());
ASL_TEST_EXPECT(s3.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s3.message() == "1 2"_sv);
@ -52,11 +52,11 @@ ASL_TEST(destructor)
ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(!d);
asl::status_or s2 = ASL_MOVE(s);
ASL_TEST_EXPECT(s.ok());
asl::status_or s2 = std::move(s);
ASL_TEST_EXPECT(s2.ok());
ASL_TEST_EXPECT(!d);
s = ASL_MOVE(s2);
s = std::move(s2);
ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(!d);
}
@ -79,8 +79,8 @@ ASL_TEST(copy)
ASL_TEST(value_or)
{
asl::status_or<int> s = 7;
asl::status_or<int> s2 = asl::internal_error();
const asl::status_or<int> s = 7;
const asl::status_or<int> s2 = asl::internal_error();
ASL_TEST_EXPECT(s.value_or(45) == 7);
ASL_TEST_EXPECT(s2.value_or(45) == 45);

View File

@ -9,14 +9,14 @@
ASL_TEST(simple_ok)
{
asl::status s = asl::ok();
const asl::status s = asl::ok();
ASL_TEST_ASSERT(s.ok());
ASL_TEST_ASSERT(s.code() == asl::status_code::ok);
}
ASL_TEST(simple_code)
{
asl::status s = asl::runtime_error();
const asl::status s = asl::runtime_error();
ASL_TEST_ASSERT(!s.ok());
ASL_TEST_ASSERT(s.code() == asl::status_code::runtime);
ASL_TEST_ASSERT(s.message() == ""_sv);
@ -24,7 +24,7 @@ ASL_TEST(simple_code)
ASL_TEST(with_message)
{
asl::status s = asl::internal_error("We done goofed");
const asl::status s = asl::internal_error("We done goofed");
ASL_TEST_ASSERT(!s.ok());
ASL_TEST_ASSERT(s.code() == asl::status_code::internal);
ASL_TEST_ASSERT(s.message() == "We done goofed"_sv);
@ -32,8 +32,8 @@ ASL_TEST(with_message)
ASL_TEST(copy_inline)
{
asl::status s = asl::ok();
asl::status s2 = asl::internal_error();
const asl::status s = asl::ok();
const asl::status s2 = asl::internal_error();
asl::status s3 = s;
ASL_TEST_ASSERT(s3.code() == asl::status_code::ok);
@ -47,7 +47,7 @@ ASL_TEST(copy_message)
asl::status s2 = asl::ok();
{
asl::status s = asl::internal_error("Oh no!");
const asl::status s = asl::internal_error("Oh no!");
ASL_TEST_ASSERT(!s.ok());
ASL_TEST_ASSERT(s.code() == asl::status_code::internal);
ASL_TEST_ASSERT(s.message() == "Oh no!"_sv);

View File

@ -0,0 +1,4 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause

View File

@ -0,0 +1,8 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
CLANG_BINDIR = "{CLANG_BINDIR}"
CLANG_LIBDIR = "{CLANG_LIBDIR}"
CLANG_INCLUDE_DIRS = {CLANG_INCLUDE_DIRS}

View File

@ -0,0 +1,18 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load(":toolchain_config.bzl", "declare_toolchain")
filegroup(name = "empty")
PAIRS = [
("windows", "x86_64"),
("linux", "x86_64"),
]
[
declare_toolchain(name = "%s_%s_toolchain" % (os, arch), os = os, arch = arch)
for (os, arch) in PAIRS
]

View File

@ -0,0 +1,113 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
def _copy_file(ctx, from_path, to_path):
content = ctx.read(from_path)
ctx.file(to_path, content = content)
def _get_clang_info(ctx, clang_path, line_prefix):
result = ctx.execute([clang_path, "--version"])
if result.return_code != 0:
return None
for l in result.stdout.splitlines():
if l.startswith(line_prefix):
l = l[len(line_prefix):]
return l.split(" ")[0]
return None
def _get_clang_version(ctx, clang_path):
return _get_clang_info(ctx, clang_path, "clang version ")
def _get_clang_lib_dir(ctx, clang_path):
result = ctx.execute([clang_path, "-no-canonical-prefixes", "--print-resource-dir"])
if result.return_code != 0:
return None
return result.stdout
def _get_include_dirs(ctx, clang_path):
ctx.file("detect.cpp", content = "")
detect_path = ctx.path("detect.cpp")
res = ctx.execute([
clang_path,
"-v",
"-no-canonical-prefixes",
"-fsyntax-only",
"-x", "c++",
detect_path,
])
lines = res.stderr.splitlines()
listing_include_dirs = False
include_dirs = []
for l in lines:
if l.startswith("#include <...> search starts here:"):
listing_include_dirs = True
elif l.startswith("End of search list."):
listing_include_dirs = False
elif listing_include_dirs:
include_dirs.append(l.strip())
return include_dirs
def _impl(ctx):
_copy_file(ctx, ctx.attr._build_bazel, "BUILD.bazel")
_copy_file(ctx, ctx.attr._toolchain_config_bzl, "toolchain_config.bzl")
clang_path = ctx.which("clang")
if not clang_path:
fail("Could not find clang")
clang_version = _get_clang_version(ctx, clang_path)
if not clang_version:
fail("Could not detect clang version")
clang_lib_dir = _get_clang_lib_dir(ctx, clang_path)
if not clang_lib_dir:
fail("Could not detect clang lib dir")
include_dirs = _get_include_dirs(ctx, clang_path)
if not include_dirs:
fail("Could not detect include dirs")
ctx.template(
"clang_config.bzl",
ctx.attr._clang_config_bzl_tpl,
executable = False,
substitutions = {
"{CLANG_BINDIR}": str(clang_path.dirname),
"{CLANG_LIBDIR}": str(clang_lib_dir).strip().replace("\\", "/"),
"{CLANG_INCLUDE_DIRS}": str(include_dirs),
},
)
_config_detection = repository_rule(
implementation = _impl,
local = True,
configure = True,
attrs = {
"_build_bazel": attr.label(
default = Label("//bazel/clang_toolchain:clang_toolchain.BUILD.bazel"),
allow_single_file = True,
),
"_toolchain_config_bzl": attr.label(
default = Label("//bazel/clang_toolchain:toolchain_config.bzl"),
allow_single_file = True,
),
"_clang_config_bzl_tpl": attr.label(
default = Label("//bazel/clang_toolchain:clang_config.bzl.tpl"),
allow_single_file = True,
),
},
)
clang_toolchain = module_extension(
implementation = lambda ctx: _config_detection(
name = "clang_toolchain",
),
)

View File

@ -0,0 +1,596 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load(
"//:clang_config.bzl",
"CLANG_BINDIR",
"CLANG_LIBDIR",
"CLANG_INCLUDE_DIRS",
)
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load(
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
"action_config",
"artifact_name_pattern",
"feature",
"feature_set",
"flag_group",
"flag_set",
"tool",
"tool_path",
"variable_with_value",
)
CPP_COMPILE_ACTIONS = [
ACTION_NAMES.cpp_compile,
ACTION_NAMES.cpp_header_parsing
]
C_COMPILE_ACTIONS = [
ACTION_NAMES.c_compile,
]
COMPILE_ACTIONS = C_COMPILE_ACTIONS + CPP_COMPILE_ACTIONS
LINK_ACTIONS = [
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
ACTION_NAMES.cpp_link_executable,
]
ARCHIVE_ACTIONS = [
ACTION_NAMES.cpp_link_static_library,
]
def _impl(ctx):
os = ctx.attr.os
is_windows = os == "windows"
exe_suffix = ".exe" if is_windows else ""
tool_paths = [
tool_path(name = "ar", path = CLANG_BINDIR + "/llvm-ar" + exe_suffix),
tool_path(name = "ld", path = CLANG_BINDIR + "/ld.lld" + exe_suffix),
tool_path(name = "nm", path = CLANG_BINDIR + "/llvm-nm" + exe_suffix),
tool_path(name = "objdump", path = CLANG_BINDIR + "/llvm-objdump" + exe_suffix),
tool_path(name = "strip", path = CLANG_BINDIR + "/llvm-strip" + exe_suffix),
tool_path(name = "gcc", path = CLANG_BINDIR + "/clang" + exe_suffix),
tool_path(name = "cpp", path = CLANG_BINDIR + "/clang++" + exe_suffix),
]
action_configs = [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang" + exe_suffix)])
for name in C_COMPILE_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang++" + exe_suffix)])
for name in CPP_COMPILE_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang++" + exe_suffix)])
for name in LINK_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/llvm-ar" + exe_suffix)])
for name in ARCHIVE_ACTIONS
] + [
action_config(ACTION_NAMES.strip, enabled = True, tools = [tool(path = CLANG_BINDIR + "/llvm-strip" + exe_suffix)]),
]
artifact_name_patterns = [
artifact_name_pattern(
category_name = "object_file",
prefix = "",
extension = ".obj" if is_windows else ".o",
),
artifact_name_pattern(
category_name = "static_library",
prefix = "" if is_windows else "lib",
extension = ".lib" if is_windows else ".a",
),
artifact_name_pattern(
category_name = "dynamic_library",
prefix = "" if is_windows else "lib",
extension = ".dll" if is_windows else ".so",
),
artifact_name_pattern(
category_name = "executable",
prefix = "",
extension = exe_suffix,
),
]
asan_link_flag_sets = []
ubsan_link_flag_sets = []
if is_windows:
asan_link_flag_sets.append(flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-l\"" + CLANG_LIBDIR + "/lib/windows/clang_rt.asan_dynamic-x86_64.lib\"",
],
),
],
))
ubsan_link_flag_sets.append(flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-l\"" + CLANG_LIBDIR + "/lib/windows/clang_rt.ubsan_standalone-x86_64.lib\"",
],
),
],
))
features = [
feature(name = "no_legacy_features"),
feature(name = "opt"),
feature(name = "fastbuild"),
feature(name = "dbg"),
feature(
name = "default_flags",
enabled = True,
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-no-canonical-prefixes",
"-fcolor-diagnostics",
],
),
],
),
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fuse-ld=lld",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-Wno-builtin-macro-redefined",
"-D__DATE__=\"redacted\"",
"-D__TIMESTAMP__=\"redacted\"",
"-D__TIME__=\"redacted\"",
"-c",
],
),
flag_group(
flags = ["-frandom-seed=%{output_file}"],
expand_if_available = "output_file",
),
flag_group(
flags = ["-D%{preprocessor_defines}"],
iterate_over = "preprocessor_defines",
),
flag_group(
flags = ["-I%{include_paths}"],
iterate_over = "include_paths",
),
flag_group(
flags = ["-iquote", "%{quote_include_paths}"],
iterate_over = "quote_include_paths",
),
flag_group(
flags = ["-isystem", "%{system_include_paths}"],
iterate_over = "system_include_paths",
),
flag_group(
flags = ["-MD", "-MF", "%{dependency_file}"],
expand_if_available = "dependency_file",
),
],
),
flag_set(
actions = ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
flags = ["rcsD"],
),
flag_group(
flags = ["%{output_execpath}"],
expand_if_available = "output_execpath",
),
],
),
flag_set(
actions = LINK_ACTIONS + ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
iterate_over = "libraries_to_link",
expand_if_available = "libraries_to_link",
flag_groups = [
flag_group(
flags = ["-Wl,--start-lib"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
),
flag_group(
iterate_over = "libraries_to_link.object_files",
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.object_files}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.object_files}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "interface_library",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "static_library",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
flags = ["-l%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "dynamic_library",
),
),
flag_group(
flags = ["-l:%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "versioned_dynamic_library",
),
),
flag_group(
expand_if_true = "libraries_to_link.is_whole_archive",
flag_groups = [
flag_group(
expand_if_false = "macos_flags",
flags = ["-Wl,-no-whole-archive"],
),
],
),
flag_group(
flags = ["-Wl,--end-lib"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
),
],
),
],
),
],
),
feature(
name = "windows_linker_flags",
enabled = is_windows,
flag_sets = [
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-Xlinker",
"/subsystem:console",
],
),
],
),
],
),
feature(
name = "dbg_flags",
enabled = True,
requires = [feature_set(["dbg"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-DDEBUG=1",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-g3",
],
),
],
),
],
),
feature(
name = "fastbuild_flags",
enabled = True,
requires = [feature_set(["fastbuild"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-O1",
"-DDEBUG=1",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-g1",
],
),
],
),
],
),
feature(
name = "opt_flags",
enabled = True,
requires = [feature_set(["opt"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-O3",
"-DNDEBUG=1",
],
),
],
),
],
),
feature(
name = "c++20",
provides = ["c++_version"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["-std=c++20"],
),
],
),
],
),
feature(
name = "c++23",
provides = ["c++_version"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["-std=c++23"],
),
],
),
],
),
feature(
name = "asan",
implies = ["sanitize"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fsanitize=address",
],
),
],
),
] + asan_link_flag_sets,
),
feature(
name = "ubsan",
implies = ["sanitize"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fsanitize=undefined",
],
),
],
),
] + ubsan_link_flag_sets,
),
feature(
name = "sanitize",
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fno-omit-frame-pointer",
"-fno-sanitize-ignorelist",
"-fno-sanitize-recover=all",
],
),
],
),
],
),
feature(
name = "final_flags",
enabled = True,
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["%{user_compile_flags}"],
expand_if_available = "user_compile_flags",
iterate_over = "user_compile_flags",
),
flag_group(
flags = ["-o", "%{output_file}"],
expand_if_available = "output_file",
),
flag_group(
flags = ["%{source_file}"],
expand_if_available = "source_file",
),
],
),
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = ["%{user_link_flags}"],
expand_if_available = "user_link_flags",
iterate_over = "user_link_flags",
),
flag_group(
flags = ["-o", "%{output_execpath}"],
expand_if_available = "output_execpath",
),
],
),
flag_set(
actions = LINK_ACTIONS + ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
flags = ["@%{linker_param_file}"],
expand_if_available = "linker_param_file",
),
],
),
],
),
]
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
toolchain_identifier = "%s-toolchain" % os,
compiler = "clang",
artifact_name_patterns = artifact_name_patterns,
cxx_builtin_include_directories = CLANG_INCLUDE_DIRS,
tool_paths = tool_paths,
action_configs = action_configs,
features = features,
)
toolchain_config = rule(
implementation = _impl,
attrs = {
"os": attr.string(mandatory = True),
},
provides = [CcToolchainConfigInfo],
)
def _declare_toolchain_impl(name, os, arch, visibility):
toolchain_config(
name = name + "_config",
os = os,
)
native.cc_toolchain(
name = name + "_cc",
toolchain_config = name + "_config",
all_files = ":empty",
compiler_files = ":empty",
linker_files = ":empty",
objcopy_files = ":empty",
strip_files = ":empty",
dwp_files = ":empty",
)
native.toolchain(
name = name + "_def",
toolchain = name + "_cc",
toolchain_type = "@rules_cc//cc:toolchain_type",
exec_compatible_with = [
"@platforms//cpu:%s" % arch,
"@platforms//os:%s" % os,
],
target_compatible_with = [
"@platforms//cpu:%s" % arch,
"@platforms//os:%s" % os,
],
visibility = ["//visibility:public"],
)
declare_toolchain = macro(
implementation = _declare_toolchain_impl,
attrs = {
"os": attr.string(mandatory = True, configurable = False),
"arch": attr.string(mandatory = True, configurable = False),
}
)

Some files were not shown because too many files have changed in this diff Show More