45 Commits
v0.1.0 ... main

Author SHA1 Message Date
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
99 changed files with 10039 additions and 861 deletions

View File

@ -1,11 +1,14 @@
common --registry=https://bcr.bazel.build/
common --registry=https://bazel.stevenlr.com/registry/
startup --windows_enable_symlinks startup --windows_enable_symlinks
build:windows --enable_runfiles=true build:windows --enable_runfiles=true
build --enable_platform_specific_config
build --build_python_zip=false build --build_python_zip=false
build:windows --extra_execution_platforms=//:x64_windows-clang-cl build:windows --extra_execution_platforms=//:x64_windows-clang-cl
# @Todo(bazel) We should be able to use @local_config_cc... build:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl
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:linux --repo_env=CC=clang
@ -24,14 +27,15 @@ build --cxxopt=-Wno-extra-semi
build --cxxopt=-Wno-global-constructors build --cxxopt=-Wno-global-constructors
build --cxxopt=-Wno-unsafe-buffer-usage build --cxxopt=-Wno-unsafe-buffer-usage
build --cxxopt=-Wno-covered-switch-default build --cxxopt=-Wno-covered-switch-default
build --cxxopt=-Wno-unused-command-line-argument
build:windows_san --config=windows build:windows_san --config=windows
build:windows_san --copt=-fno-sanitize-ignorelist build:windows_san --copt=-fno-sanitize-ignorelist
build:windows_san --copt=-fsanitize=address build:windows_san --copt=-fsanitize=address
build:windows_san --copt=-fsanitize=undefined build:windows_san --copt=-fsanitize=undefined
build:windows_san --copt=-fno-sanitize-recover=all build:windows_san --copt=-fno-sanitize-recover=all
build:windows_san --linkopt=clang_rt.asan-x86_64.lib build:windows_san --linkopt=clang_rt.asan_dynamic-x86_64.lib
build:windows_san --copt=/MT build:windows_san --linkopt=clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
build:linux_san --config=linux build:linux_san --config=linux
build:linux_san --copt=-fsanitize=address build:linux_san --copt=-fsanitize=address

View File

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

@ -2,19 +2,17 @@
# #
# SPDX-License-Identifier: BSD-3-Clause # 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 = "platforms", version = "0.0.11")
bazel_dep(name = "rules_cc", version = "0.0.17")
bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "hedron_compile_commands", dev_dependency = True) bazel_dep(name = "rules_cc", version = "0.1.1")
git_override( cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
module_name = "hedron_compile_commands", use_repo(cc_configure, "local_config_cc")
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
commit = "4f28899228fb3ad0126897876f147ca15026151e",
)
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 = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.13", is_default = True) python.toolchain(python_version = "3.13", is_default = True)

102
MODULE.bazel.lock generated
View File

@ -1,6 +1,9 @@
{ {
"lockFileVersion": 18, "lockFileVersion": 18,
"registryFileHashes": { "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/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", "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.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.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/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.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "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", "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/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.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "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/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "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/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/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.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "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.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.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.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.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.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.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.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_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/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", "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.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.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/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.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.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/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.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "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.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.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/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.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://bcr.bazel.build/modules/rules_python/1.1.0/source.json": "29f1fdfd23a40808c622f813bc93e29c3aae277333f03293f667e76159750a0f", "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/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", "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", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
@ -134,53 +140,15 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "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/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.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.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"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/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
}, },
"selectedYankedVersions": {}, "selectedYankedVersions": {},
"moduleExtensions": { "moduleExtensions": {
"@@platforms//host:extension.bzl%host_platform": {
"general": {
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
"usagesDigest": "SeQiIN/f8/Qt9vYQk7qcXp4I4wJeEC0RnQDiaaJ4tb8=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"host_platform": {
"repoRuleId": "@@platforms//host:extension.bzl%host_platform_repo",
"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": { "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": { "general": {
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
"recordedFileInputs": {}, "recordedFileInputs": {},
"recordedDirentsInputs": {}, "recordedDirentsInputs": {},
@ -241,6 +209,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 = [ hdrs = [
"annotations.hpp", "annotations.hpp",
"assert.hpp", "assert.hpp",
"bit.hpp",
"config.hpp", "config.hpp",
"defer.hpp", "defer.hpp",
"float.hpp", "float.hpp",
"functional.hpp", "functional.hpp",
"integers.hpp", "integers.hpp",
"meta.hpp", "meta.hpp",
"numeric.hpp",
"utility.hpp", "utility.hpp",
], ],
srcs = [ srcs = [
"assert.cpp", "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"], visibility = ["//visibility:public"],
) )
@ -37,10 +46,12 @@ cc_library(
"//asl/types:box", "//asl/types:box",
], ],
) for name in [ ) for name in [
"bit",
"defer", "defer",
"float", "float",
"functional", "functional",
"integers", "integers",
"meta", "meta",
"numeric",
"utility", "utility",
]] ]]

View File

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

View File

@ -24,8 +24,7 @@ void report_assert_failure(const char* msg, const source_location& sl = source_l
#define ASL_DEBUG_BREAK() __builtin_debugtrap() #define ASL_DEBUG_BREAK() __builtin_debugtrap()
#endif #endif
// @Todo Configure asserts at build time #if !ASL_OPTIMIZED
#define ASL_ASSERT(...) \ #define ASL_ASSERT(...) \
if (__VA_ARGS__) {} \ if (__VA_ARGS__) {} \
else \ else \
@ -33,6 +32,9 @@ void report_assert_failure(const char* msg, const source_location& sl = source_l
::asl::report_assert_failure(#__VA_ARGS__); \ ::asl::report_assert_failure(#__VA_ARGS__); \
ASL_DEBUG_BREAK(); \ ASL_DEBUG_BREAK(); \
} }
#else
#define ASL_ASSERT(...)
#endif
#define ASL_ASSERT_RELEASE(...) \ #define ASL_ASSERT_RELEASE(...) \
if (__VA_ARGS__) {} \ 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

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

@ -0,0 +1,198 @@
// 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));
}
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 // SPDX-License-Identifier: BSD-3-Clause
// NOLINTBEGIN(*-macro-to-enum)
#pragma once #pragma once
#if defined(_WIN32) #if defined(_WIN32)
@ -19,3 +21,16 @@
#else #else
#error Unknown compiler #error Unknown compiler
#endif #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: public:
template<typename T> 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); ASL_DELETE_COPY(DeferCallback);
DeferCallback(DeferCallback&& other) : 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> template<invocable Callback>
DeferCallback<Callback> operator<<(Callback&& callback) const 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; uint32_t a = 0;
{ {
ASL_DEFER [&a]() { a |= 1; }; ASL_DEFER [&a]() { a |= 1U; };
ASL_TEST_EXPECT(a == 0); ASL_TEST_EXPECT(a == 0);
{ {
ASL_DEFER [&a]() { a |= 2; }; ASL_DEFER [&a]() { a |= 2U; };
ASL_DEFER [&a]() { a |= 4; }; ASL_DEFER [&a]() { a |= 4U; };
ASL_TEST_EXPECT(a == 0); ASL_TEST_EXPECT(a == 0);
} }
ASL_TEST_EXPECT(a == 6); ASL_TEST_EXPECT(a == 6);
{ {
ASL_DEFER [&a]() { a |= 8; }; ASL_DEFER [&a]() { a |= 8U; };
ASL_TEST_EXPECT(a == 6); ASL_TEST_EXPECT(a == 6);
} }
ASL_TEST_EXPECT(a == 14); ASL_TEST_EXPECT(a == 14);
ASL_DEFER [&a]() { a |= 16; }; ASL_DEFER [&a]() { a |= 16U; };
ASL_TEST_EXPECT(a == 14); ASL_TEST_EXPECT(a == 14);
} }

View File

@ -6,12 +6,42 @@
#include "asl/base/meta.hpp" #include "asl/base/meta.hpp"
using float32_t = float;
using float64_t = double;
namespace asl 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); } template<is_floating_point T> constexpr bool is_infinity(T f) { return __builtin_isinf(f); }

View File

@ -9,56 +9,62 @@
namespace asl { 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) constexpr auto invoke(is_func auto C::* f, auto&& self, Args&&... args)
-> decltype((self.*f)(ASL_FWD(args)...)) -> decltype(((*std::forward<decltype(self)>(self)).*f)(std::forward<Args>(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)
requires ( requires (
sizeof...(Args) == 0 && !same_or_derived_from<decltype(self), C>
requires { self.*m; } && 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> template<typename R, typename F, typename... Args>
constexpr auto invoke(is_object auto C::* m, auto* self, Args&&...) constexpr R invoke_r(F&& f, Args&&... args)
-> decltype(self->*m)
requires (
sizeof...(Args) == 0 &&
requires { self->*m; }
)
{ {
return self->*m; if constexpr (is_void<R>)
}
template<typename... Args>
constexpr auto invoke(auto&& f, Args&&... args)
-> decltype(f(ASL_FWD(args)...))
requires requires {
f(ASL_FWD(args)...);
}
{ {
return ASL_FWD(f)(ASL_FWD(args)...); static_cast<void>(invoke(std::forward<F>(f), std::forward<Args>(args)...));
}
else
{
return invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
} }
template<typename Void, typename F, typename... 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> template<typename F, typename... Args>
concept invocable = requires (F&& f, Args&&... 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 } // namespace asl

View File

@ -7,9 +7,12 @@
struct HasFunction struct HasFunction
{ {
void do_something(int, float) {} void do_something(int, float) const {}
int& do_something2(int, float) &;
}; };
struct HasFunction2 : public HasFunction {};
struct HasMember struct HasMember
{ {
int member{}; int member{};
@ -17,6 +20,8 @@ struct HasMember
void (*member_fn)(){}; void (*member_fn)(){};
}; };
struct HasMember2 : public HasMember {};
struct Functor struct Functor
{ {
int64_t operator()() { return 35; } 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<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(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(&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()>);
static_assert(!asl::invocable<int(), 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<Functor, void*>);
static_assert(asl::invocable<decltype(static_cast<float(*)(float)>(some_func1)), float>); 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), 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(&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) 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([](){ return 35; }) == 35);
ASL_TEST_EXPECT(asl::invoke([](int x){ return x + 2; }, 6) == 8); 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

@ -40,5 +40,25 @@ namespace asl
enum class byte : uint8_t {}; 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 } // namespace asl

View File

@ -25,6 +25,9 @@ struct empty {};
template<typename T> struct id { using type = T; }; 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... Args> static constexpr isize_t types_count = sizeof...(Args);
template<typename T, T kValue> struct integral_constant { static constexpr T value = kValue; }; 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> 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 { 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; };
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 copyable = copy_constructible<T> && copy_assignable<T>;
template<typename T> concept moveable = move_constructible<T> && move_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); using nullptr_t = decltype(nullptr);
template<typename T> struct _un_const_helper { using type = T; }; 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 = template<typename From, typename To> using copy_cref_t =
_copy_ref_helper<From, typename _copy_const_helper<From, un_cvref_t<To>>::type>::type; _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 : false_type {};
template<typename T> struct _is_ptr_helper<T*> : true_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 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; }; template<typename T> struct _tame_helper { using type = T; };
#define TAME_HELPER_IMPL(TRAILING) \ #define TAME_HELPER_IMPL(TRAILING) \
template<typename R, typename... Args> \ 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();
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> 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> 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 _array_helper : false_type { using type = T; };
template<typename T> struct _is_array_helper<T[]> : true_type {}; template<typename T> struct _array_helper<T[]> : true_type { using type = T; };
template<typename T, int N> struct _is_array_helper<T[N]> : true_type {}; 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<typename T> struct _is_floating_point_helper : false_type {};
template<> struct _is_floating_point_helper<float> : true_type {}; template<> struct _is_floating_point_helper<float> : true_type {};
@ -203,20 +239,106 @@ 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> concept is_floating_point = _is_floating_point_helper<un_cv_t<T>>::value;
template<typename T> struct _is_integer_helper : false_type {}; template<typename T> struct _integer_traits
template<> struct _is_integer_helper<int8_t> : true_type {}; {
template<> struct _is_integer_helper<int16_t> : true_type {}; static constexpr bool kSigned = false;
template<> struct _is_integer_helper<int32_t> : true_type {}; static constexpr bool kUnsigned = false;
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> 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> concept is_enum = __is_enum(T);
template<is_enum T> using underlying_t = __underlying_type(T);
template<typename T> struct is_uniquely_represented : false_type {}; template<typename T> struct is_uniquely_represented : false_type {};
template<is_integer T> struct is_uniquely_represented<T> : true_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> struct is_uniquely_represented<T> : true_type {};
@ -244,29 +366,28 @@ concept has_niche = constructible_from<T, niche_t> && equality_comparable_with<T
template<typename T> template<typename T>
concept is_niche = same_as<un_cvref_t<T>, niche_t>; concept is_niche = same_as<un_cvref_t<T>, niche_t>;
template<typename T, typename U> template<typename From, typename To>
concept _derefs_with_indirection_as = requires(T& t) concept _dereferenceable_as_convertible = requires(From& t)
{ {
*t; { *t } -> convertible_to<To&>;
requires convertible_from<U&, decltype(*t)>;
}; };
template<typename T, typename U> template<typename From, typename To>
concept _derefs_reference_as = is_ref<T> && convertible_from<U&, T>; 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> template<typename To, derefs_as<To> From>
concept _derefs_value_as = !is_ref<T> && convertible_from<U&, T&>; constexpr auto&& deref(From&& from) // NOLINT(*-missing-std-forward)
{
template<typename U, _derefs_with_indirection_as<U> T> if constexpr (_dereferenceable_as_convertible<From, To>)
constexpr U& deref(T&& t) { return static_cast<U&>(*t); } {
using deref_type = decltype(*declval<From&&>());
template<typename U, _derefs_reference_as<U> T> return static_cast<copy_cref_t<deref_type, To>>(*static_cast<From&&>(from));
constexpr U& deref(T&& t) { return static_cast<U&>(t); } }
else
template<typename U, _derefs_value_as<U> T> {
constexpr U& deref(T&& t) { return static_cast<U&>(t); } return static_cast<copy_cref_t<From&&, To>>(from);
}
template<typename T, typename U> }
concept derefs_as = _derefs_with_indirection_as<T, U> || _derefs_reference_as<T, U> || _derefs_value_as<T, U>;
} // namespace asl } // 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()>);
static_assert(!asl::is_ref<void() const &&>); 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<int*>);
static_assert(asl::is_ptr<const int* const>); static_assert(asl::is_ptr<const int* const>);
static_assert(asl::is_ptr<const volatile int*>); 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()>); static_assert(!asl::is_ptr<void()>);
static_assert(!asl::is_ptr<void() const &&>); 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>>);
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<void(int)>);
static_assert(!asl::is_array<int(float) const && noexcept>); 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&>>); 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 D { public: operator C() { return c; } C c; }; // NOLINT
class E { public: template<class T> E(T&&) {} }; // NOLINT class E { public: template<class T> E(T&&) {} }; // NOLINT
static_assert(asl::convertible_from<Base*, Derived*>); static_assert(asl::convertible_to<Derived*, Base*>);
static_assert(!asl::convertible_from<Derived*, Base*>); static_assert(!asl::convertible_to<Base*, Derived*>);
static_assert(asl::convertible_from<C, D>); static_assert(asl::convertible_to<D, C>);
static_assert(!asl::convertible_from<C*, Derived*>); static_assert(!asl::convertible_to<Derived*, C*>);
static_assert(asl::convertible_from<E, Base>); static_assert(asl::convertible_to<Base, E>);
static_assert(!asl::convertible_from<int16_t(&)[], int32_t(&)[]>); static_assert(!asl::convertible_to<int32_t(&)[], int16_t(&)[]>);
static_assert(asl::convertible_from<const int16_t(&)[], int16_t(&)[]>); static_assert(asl::convertible_to<int16_t(&)[], const int16_t(&)[]>);
static_assert(asl::convertible_from<const int16_t(&)[], const int16_t(&)[]>); static_assert(asl::convertible_to<const int16_t(&)[], const int16_t(&)[]>);
static_assert(asl::convertible_from<int16_t(&)[], int16_t(&)[]>); static_assert(asl::convertible_to<int16_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_from<int32_t(&)[], int16_t(&)[]>); static_assert(!asl::convertible_to<int16_t(&)[], int32_t(&)[]>);
static_assert(!asl::convertible_from<int16_t(&)[], const int16_t(&)[]>); static_assert(!asl::convertible_to<const int16_t(&)[], int16_t(&)[]>);
static_assert(!asl::convertible_from<C(&)[], D(&)[]>); static_assert(!asl::convertible_to<D(&)[], C(&)[]>);
static_assert(asl::derived_from<Derived, Base>); 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<Base, Derived>);
static_assert(!asl::derived_from<D, C>); static_assert(!asl::derived_from<D, C>);
static_assert(!asl::derived_from<C, D>); 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<uint16_t, uint8_t>);
static_assert(!asl::derived_from<int, int>); 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<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<const int*>);
static_assert(!asl::is_const<const int&>);
static_assert(asl::is_const<int* const>); static_assert(asl::is_const<int* const>);
static_assert(asl::is_floating_point<float>); 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<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<int&, int>);
static_assert(!asl::derefs_as<const int&, int>);
static_assert(asl::derefs_as<asl::box<int>, int>); static_assert(asl::derefs_as<asl::box<int>, int>);
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<Derived*, Base>); static_assert(asl::derefs_as<Derived*, Base>);
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>, 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_int(int) {}
static void wants_base(Base&) {} static void wants_base(const Base&) {}
static void wants_base_ptr(Base*) {} static void wants_base_ptr(Base*) {}
ASL_TEST(deref) ASL_TEST(deref)
@ -285,10 +336,11 @@ ASL_TEST(deref)
wants_base(asl::deref<Base>(&c)); wants_base(asl::deref<Base>(&c));
wants_base(asl::deref<Base>(d)); 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>(&c)); wants_base_ptr(&asl::deref<Base>(&c));
wants_base_ptr(&asl::deref<Base>(d)); 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>); 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&, 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_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/meta.hpp"
#include "asl/base/assert.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 namespace asl
{ {
struct in_place_t {};
static constexpr in_place_t in_place{};
template<moveable T> template<moveable T>
constexpr void swap(T& a, T& b) constexpr void swap(T& a, T& b)
{ {
T tmp{ASL_MOVE(a)}; T tmp{std::move(a)};
a = ASL_MOVE(b); a = std::move(b);
b = ASL_MOVE(tmp); b = std::move(tmp);
} }
template<typename T, typename U> template<typename T, typename U>
T exchange(T& obj, U&& new_value) T exchange(T& obj, U&& new_value)
{ {
T old_value = ASL_MOVE(obj); T old_value = std::move(obj);
obj = ASL_FWD(new_value); obj = std::forward<U>(new_value);
return old_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); return __builtin_bit_cast(U, value);
} }
template<typename T> template<is_enum T>
constexpr T min(T a, T b) constexpr auto to_underlying(T value)
{ {
return (a <= b) ? a : b; return static_cast<underlying_t<T>>(value);
}
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;
} }
// NOLINTBEGIN(*-macro-parentheses)
#define ASL_DELETE_COPY(T) \ #define ASL_DELETE_COPY(T) \
T(const T&) = delete; \ T(const T&) = delete; \
T& operator=(const T&) = delete; T& operator=(const T&) = delete
#define ASL_DELETE_MOVE(T) \ #define ASL_DELETE_MOVE(T) \
T(T&&) = delete; \ T(T&&) = delete; \
T& operator=(T&&) = delete; T& operator=(T&&) = delete
#define ASL_DELETE_COPY_MOVE(T) \ #define ASL_DELETE_COPY_MOVE(T) \
ASL_DELETE_COPY(T) \ ASL_DELETE_COPY(T); \
ASL_DELETE_MOVE(T) ASL_DELETE_MOVE(T)
#define ASL_DEFAULT_COPY(T) \ #define ASL_DEFAULT_COPY(T) \
T(const T&) = default; \ T(const T&) = default; \
T& operator=(const T&) = default; T& operator=(const T&) = default
#define ASL_DEFAULT_MOVE(T) \ #define ASL_DEFAULT_MOVE(T) \
T(T&&) = default; \ T(T&&) = default; \
T& operator=(T&&) = default; T& operator=(T&&) = default
#define ASL_DEFAULT_COPY_MOVE(T) \ #define ASL_DEFAULT_COPY_MOVE(T) \
ASL_DEFAULT_COPY(T) \ ASL_DEFAULT_COPY(T); \
ASL_DEFAULT_MOVE(T) ASL_DEFAULT_MOVE(T)
// NOLINTEND(*-macro-parentheses)
#define ASL_CONCAT2(A, B) A##B #define ASL_CONCAT2(A, B) A##B
#define ASL_CONCAT(A, B) ASL_CONCAT2(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 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_lref(const IdentifySelf& i) { return i.get(); }
static int get_const_rref(const IdentifySelf&& i) { return ASL_FWD(i).get(); } static int get_const_rref(const IdentifySelf&& i) { return std::move(i).get(); } // NOLINT
static int get_lref(IdentifySelf& i) { return ASL_FWD(i).get(); } static int get_lref(IdentifySelf& i) { return i.get(); }
static int get_rref(IdentifySelf&& i) { return ASL_FWD(i).get(); } static int get_rref(IdentifySelf&& i) { return std::move(i).get(); } // NOLINT
ASL_TEST(forward) ASL_TEST(forward)
{ {
IdentifySelf id; IdentifySelf id{};
ASL_TEST_EXPECT(get_const_lref(id) == 1); ASL_TEST_EXPECT(get_const_lref(IdentifySelf{}) == 1);
ASL_TEST_EXPECT(get_lref(id) == 3); ASL_TEST_EXPECT(get_lref(id) == 3);
ASL_TEST_EXPECT(get_const_rref(ASL_MOVE(id)) == 2); ASL_TEST_EXPECT(get_const_rref(IdentifySelf{}) == 2);
ASL_TEST_EXPECT(get_rref(ASL_MOVE(id)) == 4); ASL_TEST_EXPECT(get_rref(IdentifySelf{}) == 4);
} }
ASL_TEST(move) ASL_TEST(move)
{ {
IdentifySelf id; IdentifySelf id;
ASL_TEST_EXPECT(id.get() == 3); ASL_TEST_EXPECT(id.get() == 3);
ASL_TEST_EXPECT(ASL_MOVE(id).get() == 4); ASL_TEST_EXPECT(IdentifySelf{}.get() == 4);
} }
struct Level1 struct Level1
@ -51,33 +51,50 @@ struct Level3
Level2 deeper; Level2 deeper;
}; };
static int get_const_lref(const 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 ASL_FWD(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 ASL_FWD(i).deeper.deeper.id.get(); } static int get_lref(Level3& i) { return i.deeper.deeper.id.get(); }
static int get_rref(Level3&& i) { return ASL_FWD(i).deeper.deeper.id.get(); } static int get_rref(Level3&& i) { return std::move(i).deeper.deeper.id.get(); }
ASL_TEST(forward2) ASL_TEST(forward2)
{ {
Level3 id{}; Level3 id{};
ASL_TEST_EXPECT(get_const_lref(id) == 1); ASL_TEST_EXPECT(get_const_lref(id) == 1);
ASL_TEST_EXPECT(get_lref(id) == 3); ASL_TEST_EXPECT(get_lref(id) == 3);
ASL_TEST_EXPECT(get_const_rref(ASL_MOVE(id)) == 2); ASL_TEST_EXPECT(get_const_rref(Level3{}) == 2);
ASL_TEST_EXPECT(get_rref(ASL_MOVE(id)) == 4); ASL_TEST_EXPECT(get_rref(Level3{}) == 4);
} }
template<typename T> template<typename T>
static int test_fwd_like(T&& t) static int test_fwd_like(T) // NOLINT
{ {
IdentifySelf id; const IdentifySelf id;
return ASL_FWD_LIKE(decltype(t), id).get(); return std::forward_like<T>(id).get();
} }
ASL_TEST(forward_like) ASL_TEST(forward_like)
{ {
int x{}; 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<int&>(x) == 3);
ASL_TEST_EXPECT(test_fwd_like<const int&&>(ASL_MOVE(x)) == 2); ASL_TEST_EXPECT(test_fwd_like<const int&&>(8) == 2);
ASL_TEST_EXPECT(test_fwd_like<int&&>(ASL_MOVE(x)) == 4); 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 = [ deps = [
"//asl/memory", "//asl/memory",
"//asl/memory:allocator",
"//asl/base", "//asl/base",
"//asl/types:span", "//asl/types:span",
"//asl/hashing", "//asl/hashing",
@ -20,6 +21,22 @@ cc_library(
visibility = ["//visibility:public"], 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( cc_library(
name = "hash_set", name = "hash_set",
hdrs = [ hdrs = [
@ -28,6 +45,7 @@ cc_library(
deps = [ deps = [
"//asl/base", "//asl/base",
"//asl/memory", "//asl/memory",
"//asl/memory:allocator",
"//asl/types:maybe_uninit", "//asl/types:maybe_uninit",
"//asl/hashing", "//asl/hashing",
], ],
@ -42,6 +60,7 @@ cc_library(
deps = [ deps = [
"//asl/base", "//asl/base",
"//asl/memory", "//asl/memory",
"//asl/memory:allocator",
"//asl/hashing", "//asl/hashing",
":hash_set", ":hash_set",
], ],
@ -72,6 +91,7 @@ cc_library(
], ],
) for name in [ ) for name in [
"buffer", "buffer",
"chunked_buffer",
"hash_map", "hash_map",
"hash_set", "hash_set",
"intrusive_list", "intrusive_list",

View File

@ -9,13 +9,15 @@
#include "asl/memory/memory.hpp" #include "asl/memory/memory.hpp"
#include "asl/base/annotations.hpp" #include "asl/base/annotations.hpp"
#include "asl/base/assert.hpp" #include "asl/base/assert.hpp"
#include "asl/base/bit.hpp"
#include "asl/types/span.hpp" #include "asl/types/span.hpp"
#include "asl/hashing/hash.hpp" #include "asl/hashing/hash.hpp"
namespace asl namespace asl
{ {
template<is_object T, allocator Allocator = DefaultAllocator> template<typename T, allocator Allocator = DefaultAllocator>
requires is_object<T> && moveable<T>
class buffer class buffer
{ {
T* m_data{}; T* m_data{};
@ -46,7 +48,7 @@ private:
static_assert(align_of<T*> == align_of<isize_t>); static_assert(align_of<T*> == align_of<isize_t>);
static_assert(align_of<T*> == align_of<size_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{}; size_t s{};
asl::memcpy(&s, &m_size_encoded_, sizeof(size_t)); asl::memcpy(&s, &m_size_encoded_, sizeof(size_t));
@ -80,29 +82,32 @@ private:
{ {
return is_on_heap(size_encoded) return is_on_heap(size_encoded)
? static_cast<isize_t>(size_encoded & (~kOnHeapMask)) ? 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()); 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); resize_uninit_inner(sz + 1);
return data() + sz; return data() + sz;
} }
constexpr void resize_uninit_inner(isize_t new_size) constexpr void resize_uninit_inner(isize_t new_size)
{ {
isize_t old_size = size(); if constexpr (!trivially_destructible<T>)
if (!trivially_destructible<T> && new_size < old_size) {
const isize_t old_size = size();
if (new_size < old_size)
{ {
destroy_n(data() + new_size, old_size - new_size); destroy_n(data() + new_size, old_size - new_size);
} }
}
reserve_capacity(new_size); reserve_capacity(new_size);
set_size(new_size); set_size(new_size);
} }
@ -110,7 +115,9 @@ private:
constexpr void set_size_inline(isize_t new_size) constexpr void set_size_inline(isize_t new_size)
{ {
ASL_ASSERT(new_size >= 0 && new_size <= kInlineCapacity); 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); store_size_encoded(size_encoded);
} }
@ -132,20 +139,28 @@ private:
{ {
if (other.is_on_heap()) 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(); destroy();
m_data = other.m_data; m_data = other.m_data;
m_capacity = other.m_capacity; m_capacity = other.m_capacity;
store_size_encoded(other.load_size_encoded()); 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) else if (!assign || m_allocator == other.m_allocator)
{ {
isize_t other_n = other.size(); // If allocators are compatible, we can move other's inline
isize_t this_n = size(); // 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); resize_uninit_inner(other_n);
if (other_n <= this_n) if (other_n <= this_n)
{ {
@ -159,25 +174,42 @@ private:
} }
else 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(); destroy();
isize_t n = other.size(); 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); ASL_ASSERT(n <= kInlineCapacity);
resize_uninit_inner(n);
ASL_ASSERT(!is_on_heap());
relocate_uninit_n(data(), other.data(), n); relocate_uninit_n(data(), other.data(), n);
set_size_inline(n); }
} }
other.set_size_inline(0); other.set_size_inline(0);
if (assign) if (assign)
{ {
m_allocator = ASL_MOVE(other.m_allocator); m_allocator = std::move(other.m_allocator);
} }
} }
void copy_range(span<const T> to_copy) void copy_range(span<const T> to_copy)
{ {
isize_t this_size = size(); const isize_t this_size = size();
isize_t new_size = to_copy.size(); const isize_t new_size = to_copy.size();
resize_uninit_inner(to_copy.size()); resize_uninit_inner(to_copy.size());
ASL_ASSERT(capacity() >= new_size); ASL_ASSERT(capacity() >= new_size);
@ -209,7 +241,7 @@ private:
// NOLINTNEXTLINE(*-pointer-arithmetic) // NOLINTNEXTLINE(*-pointer-arithmetic)
for (T* it = data_ptr + old_size; it < end; ++it) 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) explicit constexpr buffer(Allocator allocator)
: m_allocator{ASL_MOVE(allocator)} : m_allocator{std::move(allocator)}
{} {}
explicit constexpr buffer(span<const T> s, Allocator allocator) explicit constexpr buffer(span<const T> s, Allocator allocator)
: m_allocator{ASL_MOVE(allocator)} : m_allocator{std::move(allocator)}
{ {
copy_range(s); copy_range(s);
} }
@ -241,10 +273,9 @@ public:
} }
constexpr buffer(buffer&& other) constexpr buffer(buffer&& other)
requires moveable<T> : buffer(std::move(other.m_allocator))
: buffer(ASL_MOVE(other.m_allocator))
{ {
move_from_other(ASL_MOVE(other), false); move_from_other(std::move(other), false);
} }
constexpr buffer& operator=(const buffer& other) constexpr buffer& operator=(const buffer& other)
@ -256,10 +287,9 @@ public:
} }
constexpr buffer& operator=(buffer&& other) constexpr buffer& operator=(buffer&& other)
requires moveable<T>
{ {
if (&other == this) { return *this; } if (&other == this) { return *this; }
move_from_other(ASL_MOVE(other), true); move_from_other(std::move(other), true);
return *this; return *this;
} }
@ -268,12 +298,22 @@ public:
destroy(); 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()); 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) if constexpr (kInlineCapacity == 0)
{ {
@ -287,7 +327,7 @@ public:
void clear() void clear()
{ {
isize_t current_size = size(); const isize_t current_size = size();
if (current_size == 0) { return; } if (current_size == 0) { return; }
destroy_n(data(), current_size); destroy_n(data(), current_size);
@ -311,12 +351,11 @@ public:
void reserve_capacity(isize_t new_capacity) void reserve_capacity(isize_t new_capacity)
{ {
ASL_ASSERT(new_capacity >= 0); ASL_ASSERT(new_capacity >= 0);
ASL_ASSERT_RELEASE(new_capacity <= 0x4000'0000'0000'0000);
if (new_capacity <= capacity()) { return; } if (new_capacity <= capacity()) { return; }
ASL_ASSERT(new_capacity > kInlineCapacity); 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(); T* old_data = data();
const isize_t old_capacity = capacity(); const isize_t old_capacity = capacity();
@ -328,12 +367,12 @@ public:
if (currently_on_heap && trivially_move_constructible<T>) 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; m_capacity = new_capacity;
return; 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); relocate_uninit_n(new_data, old_data, current_size);
@ -348,17 +387,16 @@ public:
} }
constexpr void resize_uninit(isize_t new_size) 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); resize_uninit_inner(new_size);
set_size(new_size);
} }
constexpr void resize_zero(isize_t 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(); const isize_t old_size = size();
resize_uninit(new_size); resize_uninit_inner(new_size);
if (new_size > old_size) if (new_size > old_size)
{ {
@ -368,9 +406,16 @@ public:
void resize(isize_t new_size) void resize(isize_t new_size)
requires default_constructible<T> requires default_constructible<T>
{
if constexpr (trivially_default_constructible<T>)
{
resize_zero(new_size);
}
else
{ {
resize_inner(new_size); resize_inner(new_size);
} }
}
void resize(isize_t new_size, const T& value) void resize(isize_t new_size, const T& value)
{ {
@ -380,63 +425,68 @@ public:
constexpr T& push(auto&&... args) constexpr T& push(auto&&... args)
requires constructible_from<T, decltype(args)&&...> requires constructible_from<T, decltype(args)&&...>
{ {
T* uninit = push_uninit(); void* uninit = push_uninit();
T* init = construct_at<T>(uninit, ASL_FWD(args)...); T* init = construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
return *init; return *init;
} }
auto data(this auto&& self) 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) if constexpr (kInlineCapacity == 0)
{ {
return return_type{ self.m_data }; return return_type{ buffer.m_data };
} }
else 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) 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()}; return contiguous_iterator<type>{self.data()};
} }
constexpr auto end(this auto&& self) 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()}; 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(); return as_span();
} }
constexpr operator span<T>() // NOLINT(*-explicit-conversions) constexpr operator span<T>() // NOLINT(*explicit*)
{ {
return as_span(); return as_span();
} }
constexpr auto as_span(this auto&& self) 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()}; return span<type>{self.data(), self.size()};
} }
constexpr auto&& operator[](this auto&& self, isize_t i) constexpr auto&& operator[](this auto&& self, isize_t i)
{ {
ASL_ASSERT(i >= 0 && i <= self.size()); ASL_ASSERT(i >= 0 && i < self.size());
return ASL_FWD_LIKE(decltype(self), ASL_FWD(self).data()[i]); return std::forward_like<decltype(self)>(std::forward<decltype(self)>(self).data()[i]);
} }
template<typename H> template<typename H>
requires hashable<T> requires hashable<T>
friend H AslHashValue(H h, const buffer& b) 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/testing/testing.hpp"
#include "asl/tests/types.hpp" #include "asl/tests/types.hpp"
#include "asl/tests/counting_allocator.hpp"
struct Big struct Big
{ {
@ -14,6 +15,7 @@ struct Big
static_assert(asl::buffer<int32_t>::kInlineCapacity == 5); static_assert(asl::buffer<int32_t>::kInlineCapacity == 5);
static_assert(asl::buffer<int64_t>::kInlineCapacity == 2); 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<char>::kInlineCapacity == 23);
static_assert(asl::buffer<Big>::kInlineCapacity == 0); static_assert(asl::buffer<Big>::kInlineCapacity == 0);
@ -24,37 +26,12 @@ ASL_TEST(default_size)
ASL_TEST_EXPECT(b1.capacity() == 5); ASL_TEST_EXPECT(b1.capacity() == 5);
ASL_TEST_EXPECT(static_cast<const void*>(b1.data()) == &b1); 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.size() == 0);
ASL_TEST_EXPECT(b2.capacity() == 0); ASL_TEST_EXPECT(b2.capacity() == 0);
ASL_TEST_EXPECT(b2.data() == nullptr); 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 struct IncompatibleAllocator
{ {
static void* alloc(const asl::layout& layout) static void* alloc(const asl::layout& layout)
@ -76,35 +53,37 @@ struct IncompatibleAllocator
}; };
static_assert(asl::allocator<IncompatibleAllocator>); static_assert(asl::allocator<IncompatibleAllocator>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(reserve_capacity) ASL_TEST(reserve_capacity)
{ {
isize_t count = 0; CountingAllocator::Stats stats;
asl::buffer<int32_t, CounterAllocator> b(CounterAllocator{&count}); asl::buffer<int32_t, CountingAllocator> b(CountingAllocator{&stats});
ASL_TEST_EXPECT(b.size() == 0); ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5); ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0); ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(4); b.reserve_capacity(4);
ASL_TEST_EXPECT(b.size() == 0); ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5); ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0); ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(12); b.reserve_capacity(12);
ASL_TEST_EXPECT(b.size() == 0); ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 12); ASL_TEST_EXPECT(b.capacity() >= 12);
ASL_TEST_EXPECT(count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(13); b.reserve_capacity(13);
ASL_TEST_EXPECT(b.size() == 0); ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 13); ASL_TEST_EXPECT(b.capacity() >= 13);
ASL_TEST_EXPECT(count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(130); b.reserve_capacity(130);
ASL_TEST_EXPECT(b.size() == 0); ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 130); 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_TEST(push)
{ {
asl::buffer<int32_t> b; asl::buffer<int32_t> b;
@ -137,7 +116,7 @@ ASL_TEST(push)
ASL_TEST(from_span) ASL_TEST(from_span)
{ {
int data[] = {1, 2, 4, 8}; const int data[] = {1, 2, 4, 8};
asl::buffer<int> b{data}; asl::buffer<int> b{data};
ASL_TEST_EXPECT(b.size() == 4); ASL_TEST_EXPECT(b.size() == 4);
@ -156,13 +135,21 @@ struct MoveableType
MoveableType(const MoveableType&) = delete; MoveableType(const MoveableType&) = delete;
MoveableType(MoveableType&& other) : moved{other.moved + 1}, value{other.value} {} MoveableType(MoveableType&& other) : moved{other.moved + 1}, value{other.value} {}
MoveableType& operator=(const MoveableType&) = delete; 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_copy_constructible<MoveableType>);
static_assert(!asl::trivially_move_constructible<MoveableType>); static_assert(!asl::trivially_move_constructible<MoveableType>);
static_assert(!asl::copyable<MoveableType>); static_assert(!asl::copyable<MoveableType>);
static_assert(asl::move_constructible<MoveableType>); static_assert(asl::moveable<MoveableType>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_move) ASL_TEST(push_move)
{ {
asl::buffer<MoveableType> b; asl::buffer<MoveableType> b;
@ -208,6 +195,30 @@ ASL_TEST(push_move)
ASL_TEST_EXPECT(b[3].moved == 1); ASL_TEST_EXPECT(b[3].moved == 1);
ASL_TEST_EXPECT(b[4].value == 4); ASL_TEST_EXPECT(b[4].value == 4);
ASL_TEST_EXPECT(b[4].moved == 0); 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) ASL_TEST(clear)
@ -275,14 +286,13 @@ ASL_TEST(move_construct_from_heap)
buf.push(&d[2]); 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(buf2.size() == 3);
ASL_TEST_EXPECT(d[0] == false); ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false); ASL_TEST_EXPECT(d[1] == false);
ASL_TEST_EXPECT(d[2] == false); ASL_TEST_EXPECT(d[2] == false);
} }
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(d[0] == true); ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true); ASL_TEST_EXPECT(d[1] == true);
ASL_TEST_EXPECT(d[2] == true); ASL_TEST_EXPECT(d[2] == true);
@ -294,12 +304,11 @@ ASL_TEST(move_construct_inline_trivial)
buf.push(1U); buf.push(1U);
buf.push(2U); 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[0] == 1U);
ASL_TEST_EXPECT(buf2[1] == 2U); ASL_TEST_EXPECT(buf2[1] == 2U);
ASL_TEST_EXPECT(buf2.size() == 2); ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(buf.size() == 0);
} }
ASL_TEST(move_construct_from_inline_non_trivial) ASL_TEST(move_construct_from_inline_non_trivial)
@ -310,17 +319,17 @@ ASL_TEST(move_construct_from_inline_non_trivial)
buf.push(&d[1]); 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(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false); ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false); ASL_TEST_EXPECT(d[1] == false);
} }
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(d[0] == true); ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true); ASL_TEST_EXPECT(d[1] == true);
} }
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_from_heap) ASL_TEST(move_assign_from_heap)
{ {
bool d[6]{}; bool d[6]{};
@ -344,9 +353,8 @@ ASL_TEST(move_assign_from_heap)
ASL_TEST_EXPECT(d[4] == false); ASL_TEST_EXPECT(d[4] == false);
ASL_TEST_EXPECT(d[5] == 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(buf2.size() == 3);
ASL_TEST_EXPECT(d[0] == false); ASL_TEST_EXPECT(d[0] == false);
@ -367,24 +375,23 @@ ASL_TEST(move_assign_from_heap)
ASL_TEST(move_assign_trivial_heap_to_inline) ASL_TEST(move_assign_trivial_heap_to_inline)
{ {
isize_t alloc_count = 0; CountingAllocator::Stats stats;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}}; asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}}; asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1); buf.push(1);
buf.push(2); buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0); ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3); buf2.push(3);
buf2.push(4); buf2.push(4);
buf2.push(5); buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf = ASL_MOVE(buf2); buf = std::move(buf2);
ASL_TEST_EXPECT(alloc_count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf.size() == 3); ASL_TEST_EXPECT(buf.size() == 3);
ASL_TEST_EXPECT(buf2.size() == 0);
ASL_TEST_EXPECT(buf[0] == 3); ASL_TEST_EXPECT(buf[0] == 3);
ASL_TEST_EXPECT(buf[1] == 4); ASL_TEST_EXPECT(buf[1] == 4);
ASL_TEST_EXPECT(buf[2] == 5); 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) ASL_TEST(move_assign_trivial_inline_to_heap)
{ {
isize_t alloc_count = 0; CountingAllocator::Stats stats;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}}; asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}}; asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1); buf.push(1);
buf.push(2); buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0); ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3); buf2.push(3);
buf2.push(4); buf2.push(4);
buf2.push(5); buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf2 = ASL_MOVE(buf); buf2 = std::move(buf);
ASL_TEST_EXPECT(alloc_count == 1); ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf.size() == 0);
ASL_TEST_EXPECT(buf2.size() == 2); ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(buf2[0] == 1); ASL_TEST_EXPECT(buf2[0] == 1);
ASL_TEST_EXPECT(buf2[1] == 2); ASL_TEST_EXPECT(buf2[1] == 2);
} }
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_inline_to_heap) ASL_TEST(move_assign_inline_to_heap)
{ {
bool d[6]{}; bool d[6]{};
@ -430,26 +437,26 @@ ASL_TEST(move_assign_inline_to_heap)
buf2.push(&d[4]); buf2.push(&d[4]);
buf2.push(&d[5]); 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(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false); ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false); ASL_TEST_EXPECT(d[1] == false);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true); ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true); ASL_TEST_EXPECT(d[5] == true);
} }
ASL_TEST_EXPECT(d[0] == true); ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true); ASL_TEST_EXPECT(d[1] == true);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true); ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true); ASL_TEST_EXPECT(d[5] == true);
} }
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(move_assign_from_inline_incompatible_allocator) ASL_TEST(move_assign_from_inline_incompatible_allocator)
{ {
bool d[6]{}; bool d[6]{};
@ -466,9 +473,8 @@ ASL_TEST(move_assign_from_inline_incompatible_allocator)
buf2.push(&d[4]); buf2.push(&d[4]);
buf2.push(&d[5]); 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(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false); ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == 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; constexpr hash_map() requires default_constructible<Allocator> = default;
explicit constexpr hash_map(Allocator allocator) 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::destroy;
using Base::clear; using Base::clear;
using Base::size; using Base::size;
using Base::is_empty;
using Base::remove; using Base::remove;
using Base::contains; using Base::contains;
@ -122,7 +114,7 @@ public:
{ {
ASL_ASSERT((Base::m_tags[result.first_available_index] & Base::kHasValue) == 0); 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_values[result.already_present_index].destroy_unsafe();
Base::m_tags[result.first_available_index] = result.tag; Base::m_tags[result.first_available_index] = result.tag;
@ -133,17 +125,17 @@ public:
if constexpr (sizeof...(Args1) == 0 && assignable_from<V&, Arg0&&>) 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 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 else
{ {
ASL_ASSERT((Base::m_tags[result.first_available_index] & Base::kHasValue) == 0); 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_tags[result.first_available_index] = result.tag;
Base::m_size += 1; Base::m_size += 1;
} }
@ -155,7 +147,7 @@ public:
auto get(this auto&& self, const U& value) auto get(this auto&& self, const U& value)
requires key_hasher<KeyHasher, U> && key_comparator<KeyComparator, K, U> 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); isize_t index = self.find_slot_lookup(value);
if (index >= 0) if (index >= 0)
{ {

View File

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

View File

@ -7,6 +7,8 @@
#include "asl/base/annotations.hpp" #include "asl/base/annotations.hpp"
#include "asl/base/utility.hpp" #include "asl/base/utility.hpp"
#include "asl/base/meta.hpp" #include "asl/base/meta.hpp"
#include "asl/base/bit.hpp"
#include "asl/base/numeric.hpp"
#include "asl/memory/allocator.hpp" #include "asl/memory/allocator.hpp"
#include "asl/memory/memory.hpp" #include "asl/memory/memory.hpp"
#include "asl/types/maybe_uninit.hpp" #include "asl/types/maybe_uninit.hpp"
@ -72,10 +74,10 @@ protected:
ASL_NO_UNIQUE_ADDRESS Allocator m_allocator; 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% // 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) static isize_t size_to_capacity(isize_t size)
@ -83,7 +85,7 @@ protected:
ASL_ASSERT(size > 0); ASL_ASSERT(size > 0);
return max<isize_t>( return max<isize_t>(
kMinCapacity, 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( static void insert_inner(
@ -115,7 +117,7 @@ protected:
*size += 1; *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; 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); 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_tags = static_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_values = static_cast<maybe_uninit<T>*>(m_allocator.alloc(layout::array<maybe_uninit<T>>(new_capacity)));
asl::memzero(new_tags, new_capacity); asl::memzero(new_tags, new_capacity);
isize_t new_size = 0; isize_t new_size = 0;
@ -144,7 +146,7 @@ protected:
{ {
if ((m_tags[i] & kHasValue) == 0) { continue; } 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 // Destroy now so that destroy() has less things to do
m_values[i].destroy_unsafe(); m_values[i].destroy_unsafe();
@ -228,7 +230,6 @@ protected:
result.tag = static_cast<uint8_t>(hash & kHashMask) | kHasValue; result.tag = static_cast<uint8_t>(hash & kHashMask) | kHasValue;
// NOLINTBEGIN(*-pointer-arithmetic) // NOLINTBEGIN(*-pointer-arithmetic)
for ( for (
isize_t i = starting_index; isize_t i = starting_index;
i != starting_index || result.first_available_index < 0; i != starting_index || result.first_available_index < 0;
@ -312,7 +313,7 @@ public:
{} {}
explicit constexpr hash_set(Allocator allocator) explicit constexpr hash_set(Allocator allocator)
: m_allocator{ASL_MOVE(allocator)} : m_allocator{std::move(allocator)}
{} {}
hash_set(const hash_set& other) hash_set(const hash_set& other)
@ -339,7 +340,7 @@ public:
, m_values{exchange(other.m_values, nullptr)} , m_values{exchange(other.m_values, nullptr)}
, m_capacity{exchange(other.m_capacity, 0)} , m_capacity{exchange(other.m_capacity, 0)}
, m_size{exchange(other.m_size, 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) hash_set& operator=(hash_set&& other)
@ -351,7 +352,7 @@ public:
m_values = exchange(other.m_values, nullptr); m_values = exchange(other.m_values, nullptr);
m_capacity = exchange(other.m_capacity, 0); m_capacity = exchange(other.m_capacity, 0);
m_size = exchange(other.m_size, 0); m_size = exchange(other.m_size, 0);
m_allocator = ASL_MOVE(other.m_allocator); m_allocator = std::move(other.m_allocator);
} }
return *this; 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> template<typename... Args>
void insert(Args&&... args) void insert(Args&&... args)
@ -393,7 +396,7 @@ public:
{ {
maybe_grow_to_fit_one_more(); maybe_grow_to_fit_one_more();
ASL_ASSERT(m_size < max_size()); 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> template<typename U>

View File

@ -10,7 +10,7 @@
ASL_TEST(empty) ASL_TEST(empty)
{ {
asl::hash_set<int> set; const asl::hash_set<int> set;
ASL_TEST_EXPECT(set.size() == 0); ASL_TEST_EXPECT(set.size() == 0);
@ -42,7 +42,7 @@ ASL_TEST(a_bunch_of_ints)
{ {
asl::hash_set<int> set; asl::hash_set<int> set;
int count = 3000; const int count = 3000;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
@ -98,6 +98,7 @@ struct CustomHasher
} }
}; };
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(destructor_and_remove) ASL_TEST(destructor_and_remove)
{ {
static constexpr int kCount = 200; static constexpr int kCount = 200;
@ -113,9 +114,9 @@ ASL_TEST(destructor_and_remove)
ASL_TEST_EXPECT(set.size() == kCount); 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) 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); set1.insert(i);
} }
asl::hash_set<int> set2 = set1; const asl::hash_set<int> set2 = set1;
asl::hash_set<int> set3; asl::hash_set<int> set3;
set3 = set1; set3 = set1;
@ -170,7 +171,7 @@ ASL_TEST(move)
set1.insert(i); 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); ASL_TEST_EXPECT(set2.size() == 100);
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i)
@ -178,7 +179,7 @@ ASL_TEST(move)
ASL_TEST_EXPECT(set2.contains(i)); ASL_TEST_EXPECT(set2.contains(i));
} }
set1 = ASL_MOVE(set2); set1 = std::move(set2);
ASL_TEST_EXPECT(set1.size() == 100); ASL_TEST_EXPECT(set1.size() == 100);
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i)

View File

@ -19,7 +19,7 @@ struct intrusive_list_node
}; };
template<typename T> 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> template<is_intrusive_list_node T>
class IntrusiveList class IntrusiveList
@ -43,11 +43,11 @@ public:
push_front(head); push_front(head);
} }
ASL_DELETE_COPY(IntrusiveList) ASL_DELETE_COPY(IntrusiveList);
ASL_DEFAULT_MOVE(IntrusiveList) ASL_DEFAULT_MOVE(IntrusiveList);
~IntrusiveList() = default; ~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) void push_front(T* node)
{ {
@ -82,13 +82,13 @@ public:
constexpr auto front(this auto&& self) 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 }; return return_type{ self.m_head };
} }
constexpr auto back(this auto&& self) 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 }; return return_type{ self.m_head != nullptr ? self.m_head->m_prev : nullptr };
} }
@ -151,7 +151,7 @@ public:
, m_advanced{end} , 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++() 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>; 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); ASL_TEST_EXPECT(list.back() == nullptr);
} }
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_front) ASL_TEST(push_front)
{ {
IntNode one{1}; IntNode one{1};
@ -75,6 +76,7 @@ ASL_TEST(push_front)
ASL_TEST_ASSERT(it == end); ASL_TEST_ASSERT(it == end);
} }
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_back) ASL_TEST(push_back)
{ {
IntNode one{1}; 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) while (v >= 100)
{ {
uint64_t x = v % 100; const uint64_t x = v % 100;
v /= 100; v /= 100;
write_two(s_pairs.subspan(static_cast<isize_t>(x * 2)).first<2>()); 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) if (v < 0)
{ {
f.write("-"); 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); AslFormat(f, absolute_value);
} }
else else

View File

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

View File

@ -4,10 +4,14 @@
#include "asl/formatting/format.hpp" #include "asl/formatting/format.hpp"
#include "asl/base/float.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_STD_REPLACEMENT_NAMESPACE_DEFINED 0
#define JKJ_STATIC_DATA_SECTION_DEFINED 0 #define JKJ_STATIC_DATA_SECTION_DEFINED 0
#include <dragonbox.h> #include <dragonbox.h>
#pragma clang diagnostic pop
static constexpr isize_t kZeroCount = 100; static constexpr isize_t kZeroCount = 100;
static constexpr char kZeros[kZeroCount] = { static constexpr char kZeros[kZeroCount] = {
@ -23,14 +27,14 @@ static constexpr char kZeros[kZeroCount] = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '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> 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("-"); } if (decimal.is_negative) { f.write("-"); }
char buffer[20]; 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) if (decimal.exponent >= 0)
{ {
f.write(digits); f.write(digits);
while (decimal.exponent > 0) while (decimal.exponent > 0)
{ {
isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount); const isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(kZeros, to_write)); f.write(asl::string_view(static_cast<const char*>(kZeros), to_write));
decimal.exponent -= 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()); decimal.exponent = -decimal.exponent - static_cast<int>(digits.size());
while (decimal.exponent > 0) while (decimal.exponent > 0)
{ {
isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount); const isize_t to_write = asl::min(static_cast<isize_t>(decimal.exponent), kZeroCount);
f.write(asl::string_view(kZeros, to_write)); f.write(asl::string_view(static_cast<const char*>(kZeros), to_write));
decimal.exponent -= to_write; decimal.exponent -= to_write;
} }
f.write(digits); 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); format_float(f, value);
} }
void asl::AslFormat(Formatter& f, double value) void asl::AslFormat(Formatter& f, float64_t value)
{ {
format_float(f, value); format_float(f, value);
} }

View File

@ -79,7 +79,7 @@ ASL_TEST(format_floats)
s = asl::format_to_string("{}", 123e-8); s = asl::format_to_string("{}", 123e-8);
ASL_TEST_EXPECT(s == "0.00000123"_sv); 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); ASL_TEST_EXPECT(s == "Infinity -Infinity"_sv);
s = asl::format_to_string("{}", asl::nan<float>()); 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 bytes = as_bytes(s);
auto hashed = city_hash::CityHash128WithSeed( 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()), static_cast<size_t>(bytes.size()),
h.state); h.state);
return HashState{hashed}; return HashState{hashed};
@ -89,7 +89,7 @@ struct HashState
{ {
for (const auto& value: s) for (const auto& value: s)
{ {
h = AslHashValue(ASL_MOVE(h), value); h = AslHashValue(std::move(h), value);
} }
return h; return h;
} }
@ -103,7 +103,7 @@ struct HashState
template<hashable_generic<HashState> Arg, hashable_generic<HashState>... Remaining> template<hashable_generic<HashState> Arg, hashable_generic<HashState>... Remaining>
static constexpr HashState combine(HashState h, const Arg& arg, const Remaining&... 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> template<typename H, uniquely_represented T>
constexpr H AslHashValue(H h, const T& value) 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> template<typename H>
constexpr H AslHashValue(H h, bool value) 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> template<typename H, typename T>
@ -128,7 +128,7 @@ constexpr void AslHashValue(H h, T*); // Don't hash pointers
template<typename H, hashable T> template<typename H, hashable T>
constexpr H AslHashValue(H h, const span<T>& s) 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> template<hashable T>

View File

@ -29,10 +29,10 @@ static_assert(asl::hashable<int64_t>);
ASL_TEST(integers) ASL_TEST(integers)
{ {
uint64_t a = asl::hash_value<uint16_t>(45); const uint64_t a = asl::hash_value<uint16_t>(45);
uint64_t b = asl::hash_value<uint16_t>(45); const uint64_t b = asl::hash_value<uint16_t>(45);
uint64_t c = asl::hash_value<uint16_t>(46); const uint64_t c = asl::hash_value<uint16_t>(46);
uint64_t d = asl::hash_value<uint32_t>(45); const uint64_t d = asl::hash_value<uint32_t>(45);
ASL_TEST_EXPECT(a == b); ASL_TEST_EXPECT(a == b);
ASL_TEST_EXPECT(a != c); ASL_TEST_EXPECT(a != c);
@ -187,19 +187,19 @@ static_assert(asl::uniquely_represented<asl::option<NonZero>>);
ASL_TEST(option) ASL_TEST(option)
{ {
asl::option<int> int1 = 0; const asl::option<int> int1 = 0;
asl::option<int> int2 = 0; const asl::option<int> int2 = 0;
asl::option<int> int3 = 1; const asl::option<int> int3 = 1;
asl::option<int> int4 = asl::nullopt; 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(int2));
ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int3)); ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int3));
ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int4)); ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int4));
asl::option<NonZero> noz1{8}; const asl::option<NonZero> noz1{8};
asl::option<NonZero> noz2{8}; const asl::option<NonZero> noz2{8};
asl::option<NonZero> noz3{9}; const asl::option<NonZero> noz3{9};
asl::option<NonZero> noz4 = asl::nullopt; 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(noz2));
ASL_TEST_EXPECT(asl::hash_value(noz1) != asl::hash_value(noz3)); ASL_TEST_EXPECT(asl::hash_value(noz1) != asl::hash_value(noz3));
@ -208,17 +208,18 @@ ASL_TEST(option)
static_assert(asl::hashable<asl::status>); static_assert(asl::hashable<asl::status>);
// NOLINTNEXTLINE(*-cognitive-complexity)
ASL_TEST(status) ASL_TEST(status)
{ {
asl::status s1 = asl::ok(); const asl::status s1 = asl::ok();
asl::status s2 = asl::ok(); const asl::status s2 = asl::ok();
asl::status s3 = asl::internal_error(); const asl::status s3 = asl::internal_error();
asl::status s4 = asl::internal_error(); const asl::status s4 = asl::internal_error();
asl::status s5 = asl::runtime_error(); const asl::status s5 = asl::runtime_error();
asl::status s6 = asl::internal_error("Oh, no!"); const asl::status s6 = asl::internal_error("Oh, no!");
asl::status s7 = asl::internal_error("Oh, no!"); const asl::status s7 = asl::internal_error("Oh, no!");
asl::status s8 = asl::internal_error("Oh, no"); const asl::status s8 = asl::internal_error("Oh, no");
asl::status s9 = asl::runtime_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(s1) == asl::hash_value(s2));
ASL_TEST_EXPECT(asl::hash_value(s3) == asl::hash_value(s4)); 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_TEST(status_or)
{ {
asl::status_or<int> s1 = 42; const asl::status_or<int> s1 = 42;
asl::status_or<int> s2 = 42; const asl::status_or<int> s2 = 42;
asl::status_or<int> s3 = 43; const asl::status_or<int> s3 = 43;
asl::status_or<int> s4 = asl::runtime_error(); const asl::status_or<int> s4 = asl::runtime_error();
asl::status_or<int> s5 = asl::runtime_error(); const asl::status_or<int> s5 = asl::runtime_error();
asl::status_or<int> s6 = asl::runtime_error("Hello"); const asl::status_or<int> s6 = asl::runtime_error("Hello");
asl::status_or<int> s7 = 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(s1) == asl::hash_value(s2));
ASL_TEST_EXPECT(asl::hash_value(s4) == asl::hash_value(s5)); 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 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 // SPDX-License-Identifier: BSD-3-Clause
#include "asl/logging/logging.hpp" #include "asl/logging/logging.hpp"
#include "asl/containers/intrusive_list.hpp"
#include "asl/formatting/format.hpp"
#include "asl/io/print.hpp" #include "asl/io/print.hpp"
#include "asl/io/writer.hpp"
#include "asl/strings/string_builder.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 // @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()}; static asl::log::DefaultLogger<asl::Writer*> g_default_logger{asl::print_internals::get_stdout_writer()};
// @Todo Protect the loggers list being a mutex // @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); static asl::IntrusiveList<asl::log::Logger> g_loggers(&g_default_logger);
void asl::log::register_logger(Logger* logger) void asl::log::register_logger(Logger* logger)
@ -56,7 +64,7 @@ void asl::log::log_inner(
StringWriter msg_writer{}; StringWriter msg_writer{};
asl::format_internals::format(&msg_writer, fmt, args); asl::format_internals::format(&msg_writer, fmt, args);
message m{ const message m{
.level = l, .level = l,
.message = msg_writer.as_string_view(), .message = msg_writer.as_string_view(),
.location = sl, .location = sl,

View File

@ -48,7 +48,11 @@ class DefaultLogger : public DefaultLoggerBase
W m_writer; W m_writer;
public: 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 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 else
{ {
format_internals::type_erased_arg type_erased_args[] = { const format_internals::type_erased_arg type_erased_args[] = {
format_internals::type_erased_arg(args)... format_internals::type_erased_arg(args)...
}; };
log_inner(l, fmt, type_erased_args, sl); 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 } // 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__) #define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 3
#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__) #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__) #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__) #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 // 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/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) ASL_TEST(log)
{ {
@ -27,6 +28,6 @@ ASL_TEST(custom_writer)
ASL_LOG_INFO("Hello"); ASL_LOG_INFO("Hello");
auto sv = string_writer.as_string_view(); 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( cc_library(
name = "memory", name = "memory",
hdrs = [ hdrs = [
"allocator.hpp",
"layout.hpp", "layout.hpp",
"memory.hpp", "memory.hpp",
], ],
srcs = [
"allocator.cpp",
],
deps = [ deps = [
"//asl/base", "//asl/base",
], ],
visibility = ["//visibility:public"], 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/memory/allocator.hpp"
#include "asl/base/assert.hpp" #include "asl/base/assert.hpp"
#include "asl/base/numeric.hpp"
#include "asl/memory/layout.hpp" #include "asl/memory/layout.hpp"
#include <cstdlib> #include <cstdlib>

View File

@ -37,7 +37,22 @@ template<typename T>
T* alloc_new(allocator auto& a, auto&&... args) T* alloc_new(allocator auto& a, auto&&... args)
{ {
void* ptr = a.alloc(layout::of<T>()); 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> template<typename T>
@ -50,13 +65,15 @@ void alloc_delete(allocator auto& a, T* ptr)
template<typename T> template<typename T>
constexpr T* alloc_new_default(auto&&... args) 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> template<typename T>
void alloc_delete_default(T* ptr) void alloc_delete_default(T* ptr)
{ {
alloc_delete(DefaultAllocator{}, ptr); DefaultAllocator allocator{};
alloc_delete(allocator, ptr);
} }
} // namespace asl } // namespace asl

View File

@ -9,7 +9,7 @@
#include "asl/base/utility.hpp" #include "asl/base/utility.hpp"
#include "asl/memory/layout.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; return ptr;
} }
@ -17,6 +17,17 @@ constexpr void* operator new(size_t, void* ptr)
namespace asl 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) constexpr isize_t memcmp(const void* a, const void* b, isize_t size)
{ {
return __builtin_memcmp(a, b, static_cast<size_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) constexpr T* construct_at(void* ptr, Args&&... args)
requires constructible_from<T, 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> 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) for (isize_t i = 0; i < n; ++i)
{ {
// NOLINTNEXTLINE(*-pointer-arithmetic) // 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); 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) for (isize_t i = 0; i < n; ++i)
{ {
// NOLINTNEXTLINE(*-pointer-arithmetic) // NOLINTNEXTLINE(*-pointer-arithmetic)
to[i] = ASL_MOVE(from[i]); to[i] = std::move(from[i]);
} }
destroy_n(from, n); destroy_n(from, n);
} }

View File

@ -45,6 +45,24 @@ cc_library(
visibility = ["//visibility:public"], 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( [cc_test(
name = "%s_tests" % name, name = "%s_tests" % name,
srcs = [ srcs = [
@ -54,6 +72,7 @@ cc_library(
":string", ":string",
":string_builder", ":string_builder",
":string_view", ":string_view",
":parse_number",
"//asl/tests:utils", "//asl/tests:utils",
"//asl/testing", "//asl/testing",
], ],
@ -61,4 +80,5 @@ cc_library(
"string", "string",
"string_view", "string_view",
"string_builder", "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> 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) : explicit constexpr string(buffer<char, Allocator>&& b) :
m_buffer{ASL_MOVE(buffer)} Base{std::move(b)}
{} {}
template<allocator A> template<allocator A>
@ -24,38 +24,36 @@ class string
public: public:
constexpr string() requires default_constructible<Allocator> = default; 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) constexpr string(string_view sv)
requires default_constructible<Allocator> requires default_constructible<Allocator>
: m_buffer{sv.as_span()} : Base{sv.as_span()}
{} {}
constexpr string(string_view sv, Allocator allocator) 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; using Base::is_empty;
constexpr string(string&&) = default;
constexpr string& operator=(const string&) requires copy_assignable<Allocator> = default; [[nodiscard]] constexpr const char* data() const { return Base::data(); }
constexpr string& operator=(string&&) = default;
constexpr isize_t size() const { return m_buffer.size(); } // NOLINTNEXTLINE(*explicit*)
constexpr const char* data() const { return m_buffer.data(); }
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr operator string_view() const constexpr operator string_view() const
{ {
return as_string_view(); 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()}; return string_view{span.data(), span.size()};
} }

View File

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

View File

@ -14,13 +14,13 @@ ASL_TEST(string_builder)
ASL_TEST_EXPECT(b.as_string_view() == "abcdefg"); 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_EXPECT(s == "abcdefg");
} }
ASL_TEST(string_builder_rvalue) 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"); ASL_TEST_EXPECT(s == "abcdefg");
} }

View File

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

View File

@ -12,6 +12,7 @@
namespace asl namespace asl
{ {
// NOLINTBEGIN(*-convert-member-functions-to-static)
class string_view class string_view
{ {
const char* m_data{}; const char* m_data{};
@ -20,17 +21,24 @@ class string_view
public: public:
constexpr string_view() = default; 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) constexpr string_view(const char* data, isize_t size)
: m_data{data} : m_data{data}
, m_size{size} , 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> 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) requires (kSize >= 1)
: m_data{str} : m_data{static_cast<const char*>(str)}
, m_size{kSize - 1} , m_size{kSize - 1}
{ {
ASL_ASSERT(m_data[kSize - 1] == '\0'); // NOLINT(*-pointer-arithmetic) ASL_ASSERT(m_data[kSize - 1] == '\0'); // NOLINT(*-pointer-arithmetic)
@ -38,7 +46,7 @@ public:
static constexpr string_view from_zstr(const char* str) 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; constexpr string_view(const string_view&) = default;
@ -49,51 +57,69 @@ public:
~string_view() = default; ~string_view() = default;
constexpr isize_t size() const { return m_size; } [[nodiscard]] constexpr isize_t size(this string_view self)
{
return self.m_size;
}
constexpr bool is_empty() const { return m_size == 0; } [[nodiscard]] constexpr bool is_empty(this string_view self)
{
return self.m_size == 0;
}
constexpr const char* data() const { return m_data; } [[nodiscard]] constexpr const char* data(this string_view self)
{
return self.m_data;
}
constexpr contiguous_iterator<const char> begin() const { return contiguous_iterator{m_data}; } [[nodiscard]] constexpr contiguous_iterator<const char> begin(this string_view self)
{
return contiguous_iterator{self.m_data};
}
[[nodiscard]] constexpr contiguous_iterator<const char> end(this string_view self)
{
// NOLINTNEXTLINE(*-pointer-arithmetic) // NOLINTNEXTLINE(*-pointer-arithmetic)
constexpr contiguous_iterator<const char> end() const { return contiguous_iterator{m_data + m_size}; } return contiguous_iterator{self.m_data + self.m_size};
constexpr span<const char> as_span() const { return span<const char>(m_data, m_size); }
constexpr char operator[](isize_t i) const
{
ASL_ASSERT(i >= 0 && i < m_size);
return m_data[i]; // NOLINT(*-pointer-arithmetic)
} }
constexpr string_view substr(isize_t offset, isize_t size) const [[nodiscard]] constexpr span<const char> as_span(this string_view self)
{ {
ASL_ASSERT(offset >= 0 && size >= 0 && offset + size <= m_size); return {self.m_data, self.m_size};
return string_view{m_data + offset, size}; // NOLINT(*-pointer-arithmetic)
} }
constexpr string_view substr(isize_t offset) const [[nodiscard]] constexpr char operator[](this string_view self, isize_t i)
{ {
ASL_ASSERT(offset >= 0 && offset <= m_size); ASL_ASSERT(i >= 0 && i < self.m_size);
return string_view{m_data + offset, m_size - offset}; // NOLINT(*-pointer-arithmetic) return self.m_data[i]; // NOLINT(*-pointer-arithmetic)
} }
constexpr string_view first(isize_t size) const [[nodiscard]] constexpr string_view substr(this string_view self, isize_t offset, isize_t size)
{ {
return substr(0, size); ASL_ASSERT(offset >= 0 && size >= 0 && offset + size <= self.m_size);
return string_view{self.m_data + offset, size}; // NOLINT(*-pointer-arithmetic)
} }
constexpr string_view last(isize_t size) const [[nodiscard]] constexpr string_view substr(this string_view self, isize_t offset)
{ {
return substr(m_size - size); ASL_ASSERT(offset >= 0 && offset <= self.m_size);
return string_view{self.m_data + offset, self.m_size - offset}; // NOLINT(*-pointer-arithmetic)
} }
constexpr bool operator==(string_view other) const [[nodiscard]] constexpr string_view first(this string_view self, isize_t size)
{ {
if (m_size != other.m_size) { return false; } return self.substr(0, size);
return memcmp(m_data, other.m_data, m_size) == 0; }
[[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> template<typename H>
@ -102,10 +128,11 @@ public:
return H::combine(H::combine_contiguous(h, as_bytes(sv.as_span())), sv.size()); return H::combine(H::combine_contiguous(h, as_bytes(sv.as_span())), sv.size());
} }
}; };
// NOLINTEND(*-convert-member-functions-to-static)
} // namespace asl } // namespace asl
constexpr asl::string_view operator ""_sv(const char* s, size_t len) 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_TEST(default)
{ {
asl::string_view s1; const asl::string_view s1;
ASL_TEST_EXPECT(s1.is_empty()); 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_EXPECT(s2.is_empty());
} }
ASL_TEST(from_literal) 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_ASSERT(s1.size() == 5);
ASL_TEST_EXPECT(asl::memcmp(s1.data(), "Hello", 5) == 0); 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()); ASL_TEST_EXPECT(s2.is_empty());
} }
@ -47,7 +47,7 @@ ASL_TEST(from_zstr)
ASL_TEST(substr1) ASL_TEST(substr1)
{ {
asl::string_view s1 = "abcd"; const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.substr(0); asl::string_view s2 = s1.substr(0);
ASL_TEST_ASSERT(s2.size() == 4); ASL_TEST_ASSERT(s2.size() == 4);
@ -63,7 +63,7 @@ ASL_TEST(substr1)
ASL_TEST(substr2) ASL_TEST(substr2)
{ {
asl::string_view s1 = "abcd"; const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.substr(0, 4); asl::string_view s2 = s1.substr(0, 4);
ASL_TEST_ASSERT(s2.size() == 4); ASL_TEST_ASSERT(s2.size() == 4);
@ -82,7 +82,7 @@ ASL_TEST(substr2)
ASL_TEST(first) ASL_TEST(first)
{ {
asl::string_view s1 = "abcd"; const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.first(0); asl::string_view s2 = s1.first(0);
ASL_TEST_ASSERT(s2.size() == 0); ASL_TEST_ASSERT(s2.size() == 0);
@ -98,7 +98,7 @@ ASL_TEST(first)
ASL_TEST(last) ASL_TEST(last)
{ {
asl::string_view s1 = "abcd"; const asl::string_view s1 = "abcd";
asl::string_view s2 = s1.last(0); asl::string_view s2 = s1.last(0);
ASL_TEST_ASSERT(s2.size() == 0); ASL_TEST_ASSERT(s2.size() == 0);

View File

@ -4,37 +4,51 @@
#include "asl/testing/testing.hpp" #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" #include "asl/io/print.hpp"
static asl::testing::Test* g_head = nullptr; namespace
static asl::testing::Test* g_tail = nullptr; {
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) 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_state.head = test;
g_tail = test; g_state.tail = test;
} }
else else
{ {
g_tail->m_next = test; g_state.tail->m_next = test;
test->m_prev = asl::exchange(g_tail, 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) void asl::testing::report_failure(const char* msg, const asl::source_location& sl)
{ {
asl::eprint("--------------------------------------------------------------\n"); asl::eprint("--------------------------------------------------------------\n");
asl::eprint("Test assertion failed at {}, line {}:\n", sl.file, sl.line); asl::eprint("Test assertion failed at {}, line {}:\n", sl.file, sl.line);
asl::eprint(" {}\n", msg); asl::eprint(" {}\n", msg);
asl::eprint("--------------------------------------------------------------\n"); 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("------------------------------------------------------------\n");
asl::eprint("Assertion failure at {}, line {}:\n", sl.file, sl.line); 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; 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); asl::eprint(GREEN("[ RUN ]") " {}\n", it->m_case_name);
g_current_test_fail = false; g_state.current_test_fail = false;
it->m_fn(); it->m_fn();
if (!g_current_test_fail) if (!g_state.current_test_fail)
{ {
asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name); asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name);
pass += 1; pass += 1;
it = it->m_next;
} }
else else
{ {
asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name); asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name);
fail += 1; 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 #pragma once
#include "asl/base/utility.hpp" #include "asl/base/meta.hpp"
namespace asl::testing namespace asl::testing
{ {
@ -35,7 +35,8 @@ struct Test
} // namespace asl::testing } // namespace asl::testing
#define ASL_TEST(CASE) \ #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( \ static ::asl::testing::Test asl_test_##CASE( \
#CASE, \ #CASE, \
asl_test_fn_##CASE); \ asl_test_fn_##CASE); \
@ -45,6 +46,6 @@ struct Test
if (EXPR) {} \ if (EXPR) {} \
else { ::asl::testing::report_failure(#EXPR); return; } else { ::asl::testing::report_failure(#EXPR); return; }
#define ASL_TEST_EXPECT(EXPR) \ #define ASL_TEST_EXPECT(...) \
if (EXPR) {} \ if (__VA_ARGS__) {} \
else { ::asl::testing::report_failure(#EXPR); } else { ::asl::testing::report_failure(#__VA_ARGS__); }

View File

@ -9,10 +9,12 @@ package(
cc_library( cc_library(
name = "utils", name = "utils",
hdrs = [ hdrs = [
"counting_allocator.hpp",
"types.hpp", "types.hpp",
], ],
deps = [ deps = [
"//asl/base", "//asl/base",
"//asl/memory:allocator",
], ],
visibility = ["//asl:__subpackages__"], 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) if (this != &other)
{ {
destroyed = asl::exchange(other.destroyed, nullptr); asl::swap(destroyed, other.destroyed);
} }
return *this; return *this;
} }

View File

@ -6,6 +6,18 @@ package(
default_applicable_licenses = ["//:license"], default_applicable_licenses = ["//:license"],
) )
cc_library(
name = "array",
hdrs = [
"array.hpp",
],
deps = [
"//asl/base",
"//asl/types:span",
],
visibility = ["//visibility:public"],
)
cc_library( cc_library(
name = "box", name = "box",
hdrs = [ hdrs = [
@ -14,6 +26,7 @@ cc_library(
deps = [ deps = [
"//asl/base", "//asl/base",
"//asl/memory", "//asl/memory",
"//asl/memory:allocator",
"//asl/hashing", "//asl/hashing",
], ],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
@ -78,6 +91,61 @@ cc_library(
visibility = ["//visibility:public"], 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( cc_test(
name = "box_tests", name = "box_tests",
srcs = ["box_tests.cpp"], 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) constexpr box(T* ptr, Allocator alloc)
: m_ptr{ptr} : m_ptr{ptr}
, m_alloc{ASL_MOVE(alloc)} , m_alloc{std::move(alloc)}
{ {
ASL_ASSERT(m_ptr != nullptr); ASL_ASSERT(m_ptr != nullptr);
} }
constexpr box(box&& other) constexpr box(box&& other)
: m_ptr{exchange(other.m_ptr, nullptr)} : 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> template<is_object U>
requires convertible_from<T*, U*> requires convertible_to<U*, T*>
constexpr box(box<U, Allocator>&& other) // NOLINT(*-explicit-conversions) constexpr box(box<U, Allocator>&& other) // NOLINT(*explicit*,*-not-moved)
: m_ptr{exchange(other.m_ptr, nullptr)} : 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) constexpr box& operator=(box&& other)
@ -53,7 +53,7 @@ public:
if (m_ptr != nullptr) { reset(); } if (m_ptr != nullptr) { reset(); }
m_ptr = exchange(other.m_ptr, nullptr); m_ptr = exchange(other.m_ptr, nullptr);
m_alloc = ASL_MOVE(other.m_alloc); m_alloc = std::move(other.m_alloc);
return *this; return *this;
} }
@ -76,18 +76,21 @@ public:
} }
} }
constexpr T* get() const { return m_ptr; } constexpr auto* get(this auto&& self)
constexpr T& operator*() const
{ {
ASL_ASSERT(m_ptr != nullptr); return self.m_ptr;
return *m_ptr;
} }
constexpr T* operator->() const constexpr auto&& operator*(this auto&& self)
{ {
ASL_ASSERT(m_ptr != nullptr); ASL_ASSERT(self.m_ptr != nullptr);
return m_ptr; 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 constexpr bool operator==(niche_t) const
@ -99,7 +102,7 @@ public:
requires hashable<T> requires hashable<T>
friend H AslHashValue(H h, const box& b) 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> 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&&...> requires constructible_from<T, Args&&...>
{ {
void* raw_ptr = allocator.alloc(layout::of<T>()); void* raw_ptr = allocator.alloc(layout::of<T>());
auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...); auto* ptr = construct_at<T>(raw_ptr, std::forward<Args>(args)...);
return box(ptr, ASL_MOVE(allocator)); return box(ptr, std::move(allocator));
} }
template<is_object T, allocator Allocator = DefaultAllocator, typename... Args> template<is_object T, allocator Allocator = DefaultAllocator, typename... Args>
@ -124,14 +127,14 @@ constexpr box<T, Allocator> make_box(Args&&... args)
{ {
Allocator allocator{}; Allocator allocator{};
void* raw_ptr = allocator.alloc(layout::of<T>()); void* raw_ptr = allocator.alloc(layout::of<T>());
auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...); auto* ptr = construct_at<T>(raw_ptr, std::forward<Args>(args)...);
return box<T>(ptr, ASL_MOVE(allocator)); return box<T>(ptr, std::move(allocator));
} }
template<is_object T, allocator A> template<is_object T, allocator A>
constexpr T* leak(box<T, A>&& b) constexpr T* leak(box<T, A>&& b)
{ {
return exchange(b.m_ptr, nullptr); return exchange(std::move(b).m_ptr, nullptr);
} }
} // namespace asl } // namespace asl

View File

@ -23,7 +23,7 @@ ASL_TEST(destructor)
ASL_TEST_ASSERT(!d); ASL_TEST_ASSERT(!d);
auto box3 = ASL_MOVE(box); auto box3 = std::move(box);
ASL_TEST_ASSERT(!d); ASL_TEST_ASSERT(!d);
} }
@ -35,7 +35,7 @@ ASL_TEST(value)
auto b = asl::make_box<int>(24); auto b = asl::make_box<int>(24);
ASL_TEST_EXPECT(*b == 24); ASL_TEST_EXPECT(*b == 24);
auto b2 = ASL_MOVE(b); auto b2 = std::move(b);
ASL_TEST_EXPECT(*b2 == 24); ASL_TEST_EXPECT(*b2 == 24);
} }
@ -44,7 +44,7 @@ ASL_TEST(ptr)
auto b = asl::make_box<int>(24); auto b = asl::make_box<int>(24);
auto* ptr1 = b.get(); auto* ptr1 = b.get();
auto b2 = ASL_MOVE(b); auto b2 = std::move(b);
auto* ptr2 = b2.get(); auto* ptr2 = b2.get();
ASL_TEST_EXPECT(ptr1 == ptr2); ASL_TEST_EXPECT(ptr1 == ptr2);
} }
@ -81,7 +81,7 @@ ASL_TEST(niche)
ASL_TEST_EXPECT(destroyed); ASL_TEST_EXPECT(destroyed);
} }
class Base class Base // NOLINT
{ {
public: public:
virtual ~Base() = default; virtual ~Base() = default;
@ -94,10 +94,10 @@ public:
int number() override { return 2; } int number() override { return 2; }
}; };
static_assert(asl::convertible_from<asl::box<Base>, asl::box<Derived>>); static_assert(asl::convertible_to<asl::box<Derived>, asl::box<Base>>);
static_assert(asl::convertible_from<asl::box<Base>, asl::box<Base>>); static_assert(asl::convertible_to<asl::box<Base>, asl::box<Base>>);
static_assert(!asl::convertible_from<asl::box<Derived>, asl::box<Base>>); static_assert(!asl::convertible_to<asl::box<Base>, asl::box<Derived>>);
static_assert(!asl::convertible_from<asl::box<int>, asl::box<float>>); static_assert(!asl::convertible_to<asl::box<float>, asl::box<int>>);
ASL_TEST(derived) 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) explicit constexpr maybe_uninit(in_place_t, auto&&... args)
requires constructible_from<T, decltype(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; 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(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> = 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> = 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> = default;
constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {} // NOLINT constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {} // NOLINT
@ -45,14 +45,14 @@ public:
constexpr void construct_unsafe(auto&&... args) constexpr void construct_unsafe(auto&&... args)
requires constructible_from<T, decltype(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 // @Safety Value must have been initialized
constexpr void assign_unsafe(auto&& value) constexpr void assign_unsafe(auto&& value)
requires assignable_from<T&, decltype(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 // @Safety Value must have been initialized
@ -67,7 +67,7 @@ public:
// @Safety Value must have been initialized // @Safety Value must have been initialized
constexpr auto&& as_init_unsafe(this auto&& self) 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>>); static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
#define TEST_TYPE_PROPERTIES(PRP) \ #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<TrivialType>> == asl::PRP<TrivialType>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \ static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \
static_assert(asl::PRP<asl::maybe_uninit<WithDestructor>> == asl::PRP<WithDestructor>); \ 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<MoveableOnly>> == asl::PRP<MoveableOnly>); \
static_assert(asl::PRP<asl::maybe_uninit<Pinned>> == asl::PRP<Pinned>); 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_default_constructible);
TEST_TYPE_PROPERTIES(trivially_copy_constructible); TEST_TYPE_PROPERTIES(trivially_copy_constructible);
TEST_TYPE_PROPERTIES(trivially_move_constructible); TEST_TYPE_PROPERTIES(trivially_move_constructible);

View File

@ -74,19 +74,19 @@ class option
if constexpr (!kHasNiche) if constexpr (!kHasNiche)
{ {
m_payload.construct_unsafe(ASL_FWD(args)...); m_payload.construct_unsafe(std::forward<Args>(args)...);
m_has_value = true; m_has_value = true;
} }
else else
{ {
if constexpr (move_assignable<T>) 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 else
{ {
m_payload.destroy_unsafe(); 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) constexpr void assign(U&& arg)
{ {
ASL_ASSERT(has_value()); ASL_ASSERT(has_value());
m_payload.assign_unsafe(ASL_FWD(arg)); m_payload.assign_unsafe(std::forward<U>(arg));
} }
public: public:
@ -103,32 +103,32 @@ public:
constexpr option() : option{nullopt} {} constexpr option() : option{nullopt} {}
// NOLINTNEXTLINE(*-explicit-conversions) // NOLINTNEXTLINE(*explicit*)
constexpr option(nullopt_t) requires (!kHasNiche) {} constexpr option(nullopt_t) requires (!kHasNiche) {}
// NOLINTNEXTLINE(*-explicit-conversions) // NOLINTNEXTLINE(*explicit*)
constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {} constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {}
template<typename U = T> template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>) constexpr explicit (!convertible_to<U&&, T>)
option(U&& value) option(U&& value)
requires ( requires (
kHasNiche && kHasNiche &&
constructible_from<T, U&&> && constructible_from<T, U&&> &&
!same_as<un_cvref_t<U>, option> !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> template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>) constexpr explicit (!convertible_to<U&&, T>)
option(U&& value) option(U&& value)
requires ( requires (
!kHasNiche && !kHasNiche &&
constructible_from<T, U&&> && constructible_from<T, U&&> &&
!is_option<U> !is_option<U>
) )
: m_payload{in_place, ASL_FWD(value)} : m_payload{in_place, std::forward<U>(value)}
, m_has_value{true} , m_has_value{true}
{} {}
@ -154,12 +154,12 @@ public:
{ {
if (other.has_value()) 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> template<typename U>
constexpr explicit (!convertible_from<T, const U&>) constexpr explicit (!convertible_to<const U&, T>)
option(const option<U>& other) option(const option<U>& other)
requires ( requires (
constructible_from<T, const U&> && constructible_from<T, const U&> &&
@ -174,7 +174,7 @@ public:
} }
template<typename U> template<typename U>
constexpr explicit (!convertible_from<T, U&&>) constexpr explicit (!convertible_to<U&&, T>)
option(option<U>&& other) option(option<U>&& other)
requires ( requires (
constructible_from<T, U&&> && constructible_from<T, U&&> &&
@ -184,7 +184,7 @@ public:
{ {
if (other.has_value()) 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()) if (has_value())
{ {
assign(ASL_FWD(value)); assign(std::forward<U>(value));
} }
else else
{ {
construct(ASL_FWD(value)); construct(std::forward<U>(value));
} }
return *this; return *this;
@ -259,11 +259,11 @@ public:
{ {
if (has_value()) if (has_value())
{ {
assign(ASL_MOVE(other.m_payload.as_init_unsafe())); assign(std::move(other.m_payload.as_init_unsafe()));
} }
else else
{ {
construct(ASL_MOVE(other.m_payload.as_init_unsafe())); construct(std::move(other.m_payload.as_init_unsafe()));
} }
} }
else if (has_value()) else if (has_value())
@ -313,11 +313,11 @@ public:
{ {
if (has_value()) if (has_value())
{ {
assign(ASL_MOVE(other.m_payload.as_init_unsafe())); assign(std::move(other).m_payload.as_init_unsafe());
} }
else else
{ {
construct(ASL_MOVE(other.m_payload.as_init_unsafe())); construct(std::move(other).m_payload.as_init_unsafe());
} }
} }
else if (has_value()) else if (has_value())
@ -342,7 +342,7 @@ public:
{ {
if constexpr (move_assignable<T>) if constexpr (move_assignable<T>)
{ {
m_payload.assign_unsafe(ASL_MOVE(T{niche_t{}})); m_payload.assign_unsafe(std::move(T{niche_t{}}));
} }
else else
{ {
@ -357,7 +357,7 @@ public:
} }
} }
constexpr bool has_value() const [[nodiscard]] constexpr bool has_value() const
{ {
if constexpr (kHasNiche) if constexpr (kHasNiche)
{ {
@ -372,28 +372,28 @@ public:
constexpr auto&& value(this auto&& self) constexpr auto&& value(this auto&& self)
{ {
ASL_ASSERT_RELEASE(self.has_value()); 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> template<typename U>
constexpr T value_or(U&& other_value) const& 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> template<typename U>
constexpr T value_or(U&& other_value) && 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) & constexpr T& emplace(auto&&... args) &
requires constructible_from<T, decltype(args)...> requires constructible_from<T, decltype(args)...>
{ {
if (has_value()) { reset(); } if (has_value()) { reset(); }
construct(ASL_FWD(args)...); construct(std::forward<decltype(args)>(args)...);
return value(); return value();
} }
@ -405,7 +405,7 @@ public:
if (self.has_value()) 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 }; return Result{ asl::nullopt };
} }
@ -417,7 +417,7 @@ public:
if (self.has_value()) if (self.has_value())
{ {
return option<un_cvref_t<Result>>{ 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 }; return option<un_cvref_t<Result>>{ asl::nullopt };
@ -427,14 +427,14 @@ public:
constexpr option or_else(F&& f) const& constexpr option or_else(F&& f) const&
requires same_as<un_cvref_t<invoke_result_t<F>>, option> 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> template<typename F>
constexpr option or_else(F&& f) && constexpr option or_else(F&& f) &&
requires same_as<un_cvref_t<invoke_result_t<F>>, option> 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> template<typename H>
@ -443,9 +443,9 @@ public:
{ {
if (!opt.has_value()) 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<Base*>&, asl::option<Derived*>>);
static_assert(!asl::assignable_from<asl::option<Derived*>&, asl::option<Base*>>); 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_to<asl::option<Derived*>, asl::option<Base*>>);
static_assert(!asl::convertible_from<asl::option<Derived*>, asl::option<Base*>>); static_assert(!asl::convertible_to<asl::option<Base*>, asl::option<Derived*>>);
class ExplicitConversion { public: explicit ExplicitConversion(int) {} }; class ExplicitConversion { public: explicit ExplicitConversion(int) {} };
class ImplicitConversion { public: ImplicitConversion(int) {} }; // NOLINT class ImplicitConversion { public: ImplicitConversion(int) {} }; // NOLINT
static_assert(!asl::convertible_from<ExplicitConversion, int>); static_assert(!asl::convertible_to<int, ExplicitConversion>);
static_assert(asl::convertible_from<ImplicitConversion, int>); static_assert(asl::convertible_to<int, ImplicitConversion>);
static_assert(!asl::convertible_from<asl::option<ExplicitConversion>, int>); static_assert(!asl::convertible_to<int, asl::option<ExplicitConversion>>);
static_assert(asl::convertible_from<asl::option<ImplicitConversion>, int>); static_assert(asl::convertible_to<int, asl::option<ImplicitConversion>>);
static_assert(!asl::convertible_from<asl::option<ExplicitConversion>, asl::option<int>>); static_assert(!asl::convertible_to<asl::option<int>, asl::option<ExplicitConversion>>);
static_assert(asl::convertible_from<asl::option<ImplicitConversion>, asl::option<int>>); 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<int>>);
static_assert(asl::trivially_copy_constructible<asl::option<TrivialType>>); 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_TEST(make_null)
{ {
asl::option<int> a; const asl::option<int> a;
asl::option<int> b = asl::nullopt; const asl::option<int> b = asl::nullopt;
ASL_TEST_EXPECT(!a.has_value()); ASL_TEST_EXPECT(!a.has_value());
ASL_TEST_EXPECT(!b.has_value()); ASL_TEST_EXPECT(!b.has_value());
@ -108,7 +108,7 @@ ASL_TEST(make_null)
ASL_TEST(make_value) ASL_TEST(make_value)
{ {
asl::option<int> a = 48; const asl::option<int> a = 48;
ASL_TEST_EXPECT(a.has_value()); ASL_TEST_EXPECT(a.has_value());
} }
@ -129,10 +129,10 @@ ASL_TEST(call_destructor)
{ {
DestructorObserver obs(&destroyed); DestructorObserver obs(&destroyed);
asl::option<DestructorObserver> opt(ASL_MOVE(obs)); asl::option<DestructorObserver> opt(std::move(obs));
ASL_TEST_EXPECT(!destroyed); ASL_TEST_EXPECT(!destroyed);
asl::option<DestructorObserver> opt2 = ASL_MOVE(opt); const asl::option<DestructorObserver> opt2 = std::move(opt);
ASL_TEST_EXPECT(!destroyed); ASL_TEST_EXPECT(!destroyed);
} }
@ -172,7 +172,7 @@ ASL_TEST(value_move)
ASL_TEST_EXPECT(!destroyed); ASL_TEST_EXPECT(!destroyed);
{ {
auto x = ASL_MOVE(opt).value(); auto x = std::move(opt).value();
ASL_TEST_EXPECT(!destroyed); ASL_TEST_EXPECT(!destroyed);
} }
@ -208,27 +208,27 @@ ASL_TEST(convert_copy)
ASL_TEST(convert_move) ASL_TEST(convert_move)
{ {
asl::option<uint8_t> opt8 = uint8_t{8}; 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_ASSERT(opt16.has_value());
ASL_TEST_EXPECT(opt16.value() == 8); ASL_TEST_EXPECT(opt16.value() == 8);
opt8 = ASL_MOVE(uint8_t{10}); opt8 = uint8_t{10};
ASL_TEST_ASSERT(opt8.has_value()); ASL_TEST_ASSERT(opt8.has_value());
ASL_TEST_EXPECT(opt8.value() == 10); ASL_TEST_EXPECT(opt8.value() == 10);
opt16 = asl::nullopt; opt16 = asl::nullopt;
ASL_TEST_EXPECT(!opt16.has_value()); ASL_TEST_EXPECT(!opt16.has_value());
opt16 = ASL_MOVE(opt8); opt16 = std::move(opt8);
ASL_TEST_ASSERT(opt16.has_value()); ASL_TEST_ASSERT(opt16.has_value());
ASL_TEST_EXPECT(opt16.value() == 10); ASL_TEST_EXPECT(opt16.value() == 10);
} }
ASL_TEST(value_or) ASL_TEST(value_or)
{ {
asl::option<int> a = asl::nullopt; const asl::option<int> a = asl::nullopt;
asl::option<int> b = 2; const asl::option<int> b = 2;
ASL_TEST_EXPECT(a.value_or(5) == 5); ASL_TEST_EXPECT(a.value_or(5) == 5);
ASL_TEST_EXPECT(b.value_or(5) == 2); ASL_TEST_EXPECT(b.value_or(5) == 2);
@ -296,8 +296,8 @@ ASL_TEST(transform)
ASL_TEST(or_else) ASL_TEST(or_else)
{ {
asl::option<int> a = 5; const asl::option<int> a = 5;
asl::option<int> b; const asl::option<int> b;
auto fn = []() -> asl::option<int> { return 12; }; auto fn = []() -> asl::option<int> { return 12; };

View File

@ -14,6 +14,7 @@ namespace asl
static constexpr isize_t dynamic_size = -1; static constexpr isize_t dynamic_size = -1;
// NOLINTBEGIN(*-convert-member-functions-to-static)
template<typename T> template<typename T>
class contiguous_iterator class contiguous_iterator
{ {
@ -22,7 +23,7 @@ class contiguous_iterator
public: public:
constexpr explicit contiguous_iterator(T* ptr) : m_ptr{ptr} {} 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++() constexpr contiguous_iterator& operator++()
{ {
@ -35,9 +36,9 @@ public:
return contiguous_iterator{ exchange(m_ptr, m_ptr + 1) }; 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> template<typename T>
@ -67,7 +68,7 @@ public:
, m_size{size} , m_size{size}
{} {}
constexpr explicit span(T* data, isize_t size) constexpr explicit span(T* data, [[maybe_unused]] isize_t size)
requires (!kIsDynamic) requires (!kIsDynamic)
: m_data{data} : m_data{data}
{ {
@ -75,27 +76,27 @@ public:
} }
template<isize_t N> template<isize_t N>
constexpr span(T (&array)[N]) // NOLINT(*-explicit-conversions) constexpr span(T (&array)[N]) // NOLINT(*explicit*)
requires (kIsDynamic) requires (kIsDynamic)
: m_data{array} : m_data{static_cast<T*>(array)}
, m_size{N} , m_size{N}
{} {}
template<isize_t N> template<isize_t N>
constexpr span(T (&array)[N]) // NOLINT(*-explicit-conversions) constexpr span(T (&array)[N]) // NOLINT(*explicit*)
requires (!kIsDynamic) && (N == kSize) requires (!kIsDynamic) && (N == kSize)
: m_data{array} : m_data{static_cast<T*>(array)}
{} {}
template<is_object U, isize_t kOtherSize> template<is_object U, isize_t kOtherSize>
constexpr explicit(!kIsDynamic) constexpr explicit(!kIsDynamic)
span(const span<U, kOtherSize>& other) // NOLINT(*-explicit-conversions) span(const span<U, kOtherSize>& other)
requires ( requires (
( (
kIsDynamic || kIsDynamic ||
is_dynamic(kOtherSize) || is_dynamic(kOtherSize) ||
kOtherSize == kSize kOtherSize == kSize
) && convertible_from<T(&)[], U(&)[]> ) && convertible_to<U(&)[], T(&)[]>
) )
: span{static_cast<U*>(other.data()), other.size()} : span{static_cast<U*>(other.data()), other.size()}
{ {
@ -109,115 +110,116 @@ public:
~span() = default; ~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; } 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()); ASL_ASSERT(i >= 0 && i < self.size());
return m_data[i]; // NOLINT(*-pointer-arithmetic) return self.m_data[i]; // NOLINT(*-pointer-arithmetic)
} }
template<isize_t kOffset, isize_t kSubSize = dynamic_size> template<isize_t kOffset, isize_t kSubSize = dynamic_size>
constexpr auto subspan() const [[nodiscard]] constexpr auto subspan(this span self)
requires ( requires (
kOffset >= 0 && kOffset >= 0 &&
(kIsDynamic || kOffset <= kSize) && (kIsDynamic || kOffset <= kSize) &&
(kIsDynamic || is_dynamic(kSubSize) || kSubSize <= kSize - kOffset) (kIsDynamic || is_dynamic(kSubSize) || kSubSize <= kSize - kOffset)
) )
{ {
ASL_ASSERT(kOffset <= size()); ASL_ASSERT(kOffset <= self.size());
if constexpr (is_dynamic(kSubSize)) if constexpr (is_dynamic(kSubSize))
{ {
if constexpr (kIsDynamic) if constexpr (kIsDynamic)
{ {
return span<T>(data() + kOffset, size() - kOffset); return span<T>(self.data() + kOffset, self.size() - kOffset);
} }
else else
{ {
return span<T, kSize - kOffset>(data() + kOffset, size() - kOffset); return span<T, kSize - kOffset>(self.data() + kOffset, self.size() - kOffset);
} }
} }
else else
{ {
ASL_ASSERT(kSubSize <= size() - kOffset); ASL_ASSERT(kSubSize <= self.size() - kOffset);
return span<T, kSubSize>(data() + kOffset, kSubSize); 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()); ASL_ASSERT(offset <= self.size());
return span<T>{ data() + offset, size() - offset }; 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(offset <= self.size() && !is_dynamic(sub_size));
ASL_ASSERT(sub_size <= size() - offset); ASL_ASSERT(sub_size <= self.size() - offset);
return span<T>{ data() + offset, sub_size }; return span<T>{ self.data() + offset, sub_size };
} }
template<isize_t kSubSize> template<isize_t kSubSize>
constexpr auto first() const [[nodiscard]] constexpr auto first(this span self)
requires ( requires (
kSubSize >= 0 && kSubSize >= 0 &&
(kIsDynamic || kSubSize <= kSize) (kIsDynamic || kSubSize <= kSize)
) )
{ {
ASL_ASSERT(kSubSize <= size()); ASL_ASSERT(kSubSize <= self.size());
return span<T, kSubSize>{ data(), kSubSize }; 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()); ASL_ASSERT(sub_size >= 0 && sub_size <= self.size());
return span<T>{ data(), sub_size }; return span<T>{ self.data(), sub_size };
} }
template<isize_t kSubSize> template<isize_t kSubSize>
constexpr auto last() const [[nodiscard]] constexpr auto last(this span self)
requires ( requires (
kSubSize >= 0 && kSubSize >= 0 &&
(kIsDynamic || kSubSize <= kSize) (kIsDynamic || kSubSize <= kSize)
) )
{ {
ASL_ASSERT(kSubSize <= size()); ASL_ASSERT(kSubSize <= self.size());
return span<T, kSubSize>{ data() + size() - kSubSize, kSubSize }; 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()); ASL_ASSERT(sub_size >= 0 && sub_size <= self.size());
return span<T>{ data() + size() - sub_size, sub_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> template<is_object T, isize_t kSize>
inline span<const byte> as_bytes(span<T, kSize> s) inline span<const byte> as_bytes(span<T, kSize> s)
{ {
return span<const byte>( return span<const byte>(
reinterpret_cast<const byte*>(s.data()), reinterpret_cast<const byte*>(s.data()), // NOLINT(*-reinterpret-cast)
s.size_bytes()); s.size_bytes());
} }
@ -226,7 +228,7 @@ inline span<byte> as_mutable_bytes(span<T, kSize> s)
requires (!is_const<T>) requires (!is_const<T>)
{ {
return span<byte>( return span<byte>(
reinterpret_cast<byte*>(s.data()), reinterpret_cast<byte*>(s.data()), // NOLINT(*-reinterpret-cast)
s.size_bytes()); 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_TEST(empty_dynamic)
{ {
asl::span<int> s; const asl::span<int> s;
ASL_TEST_EXPECT(s.size() == 0); ASL_TEST_EXPECT(s.size() == 0);
ASL_TEST_EXPECT(s.size_bytes() == 0); ASL_TEST_EXPECT(s.size_bytes() == 0);
ASL_TEST_EXPECT(s.is_empty()); ASL_TEST_EXPECT(s.is_empty());
@ -26,7 +26,7 @@ ASL_TEST(empty_dynamic)
ASL_TEST(empty_static) 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() == 0);
ASL_TEST_EXPECT(s.size_bytes() == 0); ASL_TEST_EXPECT(s.size_bytes() == 0);
ASL_TEST_EXPECT(s.is_empty()); ASL_TEST_EXPECT(s.is_empty());
@ -35,7 +35,7 @@ ASL_TEST(empty_static)
ASL_TEST(from_array_dynamic) ASL_TEST(from_array_dynamic)
{ {
int array[] = {1, 2, 3}; int array[] = {1, 2, 3};
asl::span<int> span = array; const asl::span<int> span = array;
ASL_TEST_ASSERT(span.size() == 3); ASL_TEST_ASSERT(span.size() == 3);
ASL_TEST_EXPECT(span[0] == 1); ASL_TEST_EXPECT(span[0] == 1);
ASL_TEST_EXPECT(span[1] == 2); 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) ASL_TEST(from_array_static)
{ {
int array[] = {1, 2, 3}; 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_ASSERT(span.size() == 3);
ASL_TEST_EXPECT(span[0] == 1); ASL_TEST_EXPECT(span[0] == 1);
ASL_TEST_EXPECT(span[1] == 2); ASL_TEST_EXPECT(span[1] == 2);
@ -77,21 +77,21 @@ ASL_TEST(conversion)
{ {
int array[] = {1, 2, 3}; 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_ASSERT(span2.size() == 3);
ASL_TEST_EXPECT(span2[0] == 1); ASL_TEST_EXPECT(span2[0] == 1);
ASL_TEST_EXPECT(span2[1] == 2); ASL_TEST_EXPECT(span2[1] == 2);
ASL_TEST_EXPECT(span2[2] == 3); 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_ASSERT(span3.size() == 3);
ASL_TEST_EXPECT(span3[0] == 1); ASL_TEST_EXPECT(span3[0] == 1);
ASL_TEST_EXPECT(span3[1] == 2); ASL_TEST_EXPECT(span3[1] == 2);
ASL_TEST_EXPECT(span3[2] == 3); 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_ASSERT(span4.size() == 3);
ASL_TEST_EXPECT(span4[0] == 1); ASL_TEST_EXPECT(span4[0] == 1);
ASL_TEST_EXPECT(span4[1] == 2); 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) ASL_TEST(subspan_static_from_static)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array}; const asl::span<int, 4> span{array};
auto s1 = span.subspan<0>(); auto s1 = span.subspan<0>();
ASL_TEST_ASSERT(s1.size() == 4); ASL_TEST_ASSERT(s1.size() == 4);
@ -164,7 +164,7 @@ ASL_TEST(subspan_static_from_static)
ASL_TEST(subspan_static_from_dynamic) ASL_TEST(subspan_static_from_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.subspan<0>(); auto s1 = span.subspan<0>();
ASL_TEST_ASSERT(s1.size() == 4); ASL_TEST_ASSERT(s1.size() == 4);
@ -190,7 +190,7 @@ ASL_TEST(subspan_static_from_dynamic)
ASL_TEST(subspan_dynamic) ASL_TEST(subspan_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.subspan(0); auto s1 = span.subspan(0);
ASL_TEST_ASSERT(s1.size() == 4); 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) ASL_TEST(first_static_from_static)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array}; const asl::span<int, 4> span{array};
auto s1 = span.first<0>(); auto s1 = span.first<0>();
ASL_TEST_ASSERT(s1.size() == 0); ASL_TEST_ASSERT(s1.size() == 0);
@ -270,7 +270,7 @@ ASL_TEST(first_static_from_static)
ASL_TEST(first_static_from_dynamic) ASL_TEST(first_static_from_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.first<0>(); auto s1 = span.first<0>();
ASL_TEST_ASSERT(s1.size() == 0); ASL_TEST_ASSERT(s1.size() == 0);
@ -291,7 +291,7 @@ ASL_TEST(first_static_from_dynamic)
ASL_TEST(first_dynamic) ASL_TEST(first_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.first(0); auto s1 = span.first(0);
ASL_TEST_ASSERT(s1.size() == 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) ASL_TEST(last_static_from_static)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int, 4> span{array}; const asl::span<int, 4> span{array};
auto s1 = span.last<0>(); auto s1 = span.last<0>();
ASL_TEST_ASSERT(s1.size() == 0); ASL_TEST_ASSERT(s1.size() == 0);
@ -366,7 +366,7 @@ ASL_TEST(last_static_from_static)
ASL_TEST(last_static_from_dynamic) ASL_TEST(last_static_from_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.last<0>(); auto s1 = span.last<0>();
ASL_TEST_ASSERT(s1.size() == 0); ASL_TEST_ASSERT(s1.size() == 0);
@ -387,7 +387,7 @@ ASL_TEST(last_static_from_dynamic)
ASL_TEST(last_dynamic) ASL_TEST(last_dynamic)
{ {
int array[] = {1, 2, 3, 4}; int array[] = {1, 2, 3, 4};
asl::span<int> span{array}; const asl::span<int> span{array};
auto s1 = span.last(0); auto s1 = span.last(0);
ASL_TEST_ASSERT(s1.size() == 0); ASL_TEST_ASSERT(s1.size() == 0);
@ -416,8 +416,8 @@ static_assert(HasAsMutableBytes<const int*>);
ASL_TEST(as_bytes) ASL_TEST(as_bytes)
{ {
uint32_t data[] = {0x01020304, 0x05060708}; uint32_t data[] = {0x01020304, 0x05060708};
asl::span s1(data); const asl::span s1(data);
asl::span s2 = asl::as_bytes(s1); const asl::span s2 = asl::as_bytes(s1);
ASL_TEST_ASSERT(s2.size() == 8); ASL_TEST_ASSERT(s2.size() == 8);
ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04); ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04);
@ -433,8 +433,8 @@ ASL_TEST(as_bytes)
ASL_TEST(as_mutable_bytes) ASL_TEST(as_mutable_bytes)
{ {
uint32_t data[] = {0x01020304, 0x05060708}; uint32_t data[] = {0x01020304, 0x05060708};
asl::span s1(data); const asl::span s1(data);
asl::span s2 = asl::as_mutable_bytes(s1); const asl::span s2 = asl::as_mutable_bytes(s1);
ASL_TEST_ASSERT(s2.size() == 8); ASL_TEST_ASSERT(s2.size() == 8);
ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04); ASL_TEST_ASSERT(static_cast<int>(s2[0]) == 0x04);

View File

@ -11,9 +11,11 @@
// @Todo Use custom allocator // @Todo Use custom allocator
using Allocator = asl::DefaultAllocator; using Allocator = asl::DefaultAllocator;
// NOLINTNEXTLINE(*-non-const-global-variables)
static Allocator g_allocator{}; static Allocator g_allocator{};
namespace namespace asl
{ {
struct StatusInternal struct StatusInternal
@ -23,7 +25,7 @@ struct StatusInternal
asl::atomic<int32_t> ref_count; asl::atomic<int32_t> ref_count;
constexpr StatusInternal(asl::string<Allocator>&& msg_, asl::status_code code_) constexpr StatusInternal(asl::string<Allocator>&& msg_, asl::status_code code_)
: msg{ASL_MOVE(msg_)} : msg{std::move(msg_)}
, code{code_} , code{code_}
{ {
ASL_ASSERT(code != asl::status_code::ok); 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) asl::status::status(status_code code, string_view msg)
: m_payload{alloc_new<StatusInternal>(g_allocator, msg, code)} : 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}; StringWriter<Allocator> sink{g_allocator};
format_internals::format(&sink, fmt, args); 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::status_code asl::status::code_internal() const
{ {
ASL_ASSERT(!is_inline()); 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::string_view asl::status::message_internal() const
{ {
ASL_ASSERT(!is_inline()); ASL_ASSERT(!is_inline());
return reinterpret_cast<const StatusInternal*>(m_payload)->msg; return m_payload->msg;
} }
void asl::status::ref() void asl::status::ref()
{ {
ASL_ASSERT(!is_inline()); ASL_ASSERT(!is_inline());
auto* internal = reinterpret_cast<StatusInternal*>(m_payload); atomic_fetch_increment(&m_payload->ref_count, memory_order::relaxed);
atomic_fetch_increment(&internal->ref_count, memory_order::relaxed);
} }
void asl::status::unref() void asl::status::unref()
{ {
ASL_ASSERT(!is_inline()); ASL_ASSERT(!is_inline());
auto* internal = reinterpret_cast<StatusInternal*>(m_payload); if (atomic_fetch_decrement(&m_payload->ref_count, memory_order::release) == 1)
if (atomic_fetch_decrement(&internal->ref_count, memory_order::release) == 1)
{ {
atomic_fence(memory_order::acquire); 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, invalid_argument = 4,
}; };
struct StatusInternal;
class status 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 return code == status_code::ok
? nullptr ? 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) 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); 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; 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()); ASL_ASSERT(is_inline());
if (m_payload == nullptr) if (m_payload == nullptr)
@ -53,8 +55,8 @@ class status
return payload_to_status(m_payload); return payload_to_status(m_payload);
} }
status_code code_internal() const; [[nodiscard]] status_code code_internal() const;
string_view message_internal() const; [[nodiscard]] string_view message_internal() const;
void ref(); void ref();
void unref(); void unref();
@ -103,17 +105,17 @@ public:
return *this; return *this;
} }
constexpr bool ok() const [[nodiscard]] constexpr bool ok() const
{ {
return m_payload == nullptr; return m_payload == nullptr;
} }
constexpr status_code code() const [[nodiscard]] constexpr status_code code() const
{ {
return is_inline() ? code_inline() : code_internal(); return is_inline() ? code_inline() : code_internal();
} }
constexpr string_view message() const [[nodiscard]] constexpr string_view message() const
{ {
if (!is_inline()) if (!is_inline())
{ {
@ -122,7 +124,7 @@ public:
return {}; return {};
} }
constexpr status&& throw_status() && { return ASL_MOVE(*this); } constexpr status&& throw_status() && { return std::move(*this); }
friend void AslFormat(Formatter& f, const status&); friend void AslFormat(Formatter& f, const status&);
@ -131,9 +133,9 @@ public:
{ {
if (s.is_inline()) 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_(runtime)
ASL_DEFINE_ERROR_(invalid_argument) 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 } // namespace asl

View File

@ -33,11 +33,11 @@ public:
constexpr status_or(status_or&& other) constexpr status_or(status_or&& other)
requires move_constructible<T> requires move_constructible<T>
: m_status{ASL_MOVE(other.m_status)} : m_status{std::move(other.m_status)}
{ {
if (other.ok()) 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()) 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 else
{ {
@ -84,9 +84,9 @@ public:
} }
else if (other.ok()) 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; return *this;
} }
@ -99,14 +99,14 @@ public:
} }
} }
// NOLINTNEXTLINE(*-explicit-conversions) // NOLINTNEXTLINE(*explicit*)
constexpr status_or(const status& status) : m_status{status} constexpr status_or(const status& status) : m_status{status}
{ {
ASL_ASSERT_RELEASE(!m_status.ok()); ASL_ASSERT_RELEASE(!m_status.ok());
} }
// NOLINTNEXTLINE(*-explicit-conversions) // NOLINTNEXTLINE(*explicit*)
constexpr status_or(status&& status) : m_status{ASL_MOVE(status)} constexpr status_or(status&& status) : m_status{std::move(status)}
{ {
ASL_ASSERT_RELEASE(!m_status.ok()); ASL_ASSERT_RELEASE(!m_status.ok());
} }
@ -114,7 +114,7 @@ public:
status_or& operator=(status status) = delete; status_or& operator=(status status) = delete;
template<typename U = T> template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>) constexpr explicit (!convertible_to<U&&, T>)
status_or(U&& value) status_or(U&& value)
requires ( requires (
constructible_from<T, U&&> && constructible_from<T, U&&> &&
@ -122,35 +122,35 @@ public:
!same_as<un_cvref_t<U>, status> !same_as<un_cvref_t<U>, status>
) )
: m_status{status_code::ok} : 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) constexpr auto&& value(this auto&& self)
{ {
ASL_ASSERT_RELEASE(self.ok()); 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> template<typename U>
constexpr T value_or(U&& other_value) const& 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> template<typename U>
constexpr T value_or(U&& other_value) && 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) friend void AslFormat(Formatter& f, const status_or& status)
@ -164,9 +164,9 @@ public:
{ {
if (s.ok()) 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_TEST(ok)
{ {
asl::status_or<int> s = 6; const asl::status_or<int> s = 6;
ASL_TEST_EXPECT(s.ok()); ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(s.code() == asl::status_code::ok); ASL_TEST_EXPECT(s.code() == asl::status_code::ok);
} }
ASL_TEST(from_status) 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.ok());
ASL_TEST_EXPECT(s.code() == asl::status_code::internal); ASL_TEST_EXPECT(s.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s.message() == ""_sv); 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.ok());
ASL_TEST_EXPECT(s2.code() == asl::status_code::internal); ASL_TEST_EXPECT(s2.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s2.message() == "oh no"_sv); 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.ok());
ASL_TEST_EXPECT(s3.code() == asl::status_code::internal); ASL_TEST_EXPECT(s3.code() == asl::status_code::internal);
ASL_TEST_EXPECT(s3.message() == "1 2"_sv); ASL_TEST_EXPECT(s3.message() == "1 2"_sv);
@ -52,11 +52,11 @@ ASL_TEST(destructor)
ASL_TEST_EXPECT(s.ok()); ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(!d); ASL_TEST_EXPECT(!d);
asl::status_or s2 = ASL_MOVE(s); asl::status_or s2 = std::move(s);
ASL_TEST_EXPECT(s.ok()); ASL_TEST_EXPECT(s2.ok());
ASL_TEST_EXPECT(!d); ASL_TEST_EXPECT(!d);
s = ASL_MOVE(s2); s = std::move(s2);
ASL_TEST_EXPECT(s.ok()); ASL_TEST_EXPECT(s.ok());
ASL_TEST_EXPECT(!d); ASL_TEST_EXPECT(!d);
} }
@ -79,8 +79,8 @@ ASL_TEST(copy)
ASL_TEST(value_or) ASL_TEST(value_or)
{ {
asl::status_or<int> s = 7; const asl::status_or<int> s = 7;
asl::status_or<int> s2 = asl::internal_error(); const asl::status_or<int> s2 = asl::internal_error();
ASL_TEST_EXPECT(s.value_or(45) == 7); ASL_TEST_EXPECT(s.value_or(45) == 7);
ASL_TEST_EXPECT(s2.value_or(45) == 45); ASL_TEST_EXPECT(s2.value_or(45) == 45);

View File

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

View File

@ -1 +1 @@
C++26: constexpr cast from void* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2738r1.pdf

View File

@ -12,6 +12,7 @@ import time
TO_FIX = [ TO_FIX = [
".bazelrc", ".bazelrc",
".clang-tidy", ".clang-tidy",
".clangd",
".gitignore", ".gitignore",
"**/*.hpp", "**/*.hpp",
"**/*.h", "**/*.h",

View File

@ -18,7 +18,7 @@ cc_library(
name = "dragonbox", name = "dragonbox",
hdrs = ["dragonbox.h"], hdrs = ["dragonbox.h"],
includes = ["."], includes = ["."],
visibility = ["//:__subpackages__"], visibility = ["//asl:__subpackages__"],
applicable_licenses = [ applicable_licenses = [
":license", ":license",
], ],

25
vendor/fast_float/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,25 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load("@rules_license//rules:license.bzl", "license")
license(
name = "license",
license_kinds = [
"@rules_license//licenses/spdx:MIT",
],
license_text = "LICENSE.txt",
package_name = "fast_float",
package_url = "https://github.com/fastfloat/fast_float",
)
cc_library(
name = "fast_float",
hdrs = ["fast_float.h"],
includes = ["."],
visibility = ["//asl:__subpackages__"],
applicable_licenses = [
":license",
],
)

27
vendor/fast_float/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,27 @@
MIT License
Copyright (c) 2021 The fast_float authors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

4443
vendor/fast_float/fast_float.h vendored Normal file

File diff suppressed because it is too large Load Diff