Compare commits
301 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64f60542ac | |||
| a757f3737b | |||
| 26ab9ff584 | |||
| 1e92d9f545 | |||
| 2cf72093b1 | |||
| 88d4d64edf | |||
| 7f7e577e77 | |||
| 80d7203d78 | |||
| 5dfed4deec | |||
| e08f9f11a1 | |||
| c304284773 | |||
| 0e8affcc07 | |||
| ba00dbd65a | |||
| c214fb44a3 | |||
| c704788718 | |||
| a91efeb237 | |||
| 7812b26d3a | |||
| 8bd07e17c3 | |||
| b3c0368ab5 | |||
| 37edc392a5 | |||
| fb403bc233 | |||
| 910b8de697 | |||
| 3aff995ae0 | |||
| 68c018fda6 | |||
| a9ad06139e | |||
| 9a0e5e9ea8 | |||
| b1a52d0236 | |||
| 1723358298 | |||
| 6bf0e93a41 | |||
| 0e5ef25a69 | |||
| ab84a922cf | |||
| d8bc38f154 | |||
| ecd4a82ed5 | |||
| 0898219edc | |||
| d9b62d24dc | |||
| b771278813 | |||
| 2226a7238d | |||
| 00c6a93a56 | |||
| 8ea1cbbd64 | |||
| 81a47ca88d | |||
| c54151769f | |||
| e73d5ecdd1 | |||
| 8a4e987030 | |||
| 8544d9f3c8 | |||
| ed80cb14e2 | |||
| 91eb2ddbd1 | |||
| 3bd28dc3d7 | |||
| e4d2da40f2 | |||
| a004e71dd0 | |||
| ddcab1c171 | |||
| e3cbf03f47 | |||
| f8fbbc3b76 | |||
| 52bf2bf7a2 | |||
| cf39fc4c38 | |||
| 1309361a02 | |||
| 946491489e | |||
| 19202d2a8d | |||
| 3be466966d | |||
| d22787c218 | |||
| b86dee9a18 | |||
| 863984a7c8 | |||
| e9b3ad4a5f | |||
| e00d700457 | |||
| 53e20a2a1b | |||
| af4cdf8fab | |||
| 7003e91d44 | |||
| e86bb5cce2 | |||
| 6a4728add0 | |||
| f02afbc815 | |||
| 6399dfd1e4 | |||
| f468fb77e0 | |||
| 5e316ebc88 | |||
| 0c90585511 | |||
| 13b4fd7133 | |||
| 27b4c62bd2 | |||
| 846de7ca82 | |||
| 62cf58d1ca | |||
| 4482031329 | |||
| 7b1af37b9c | |||
| 1c48f665ea | |||
| e4072747bb | |||
| ce674c8007 | |||
| 45ebc1e555 | |||
| 02c3bd4072 | |||
| 5f4172c13f | |||
| 40c5c6907a | |||
| 8dbe380d6c | |||
| 37ff9de0db | |||
| ae9f35073b | |||
| a85df27d4c | |||
| 88c70b885d | |||
| 240f5f94b1 | |||
| 7415721f7d | |||
| 8b5539756b | |||
| 49ac1035bb | |||
| d085c40541 | |||
| 99d5b9b1bb | |||
| ad86f9211b | |||
| d9515fdc2f | |||
| eedbc6bcf7 | |||
| 297fb2e8c4 | |||
| 99eab2dfb1 | |||
| 1070c9b69e | |||
| 9e6b5a7e43 | |||
| e1f11e840c | |||
| f2d4f1968b | |||
| 4a7116b996 | |||
| 41584e73d9 | |||
| 52453cb5a4 | |||
| 44b918896d | |||
| ff356ac083 | |||
| 3064801a89 | |||
| e9d9f19e76 | |||
| 9b85c38e37 | |||
| 0b2829c793 | |||
| a9c2d1daa5 | |||
| d29815b3a8 | |||
| 7ed7878278 | |||
| 4c927cd2f2 | |||
| 2fae05ed31 | |||
| 41abb96d83 | |||
| 63f51e7c41 | |||
| ba4f321934 | |||
| 68563fab72 | |||
| d8e9503869 | |||
| 86e998cbc7 | |||
| ce722e4d56 | |||
| 77eaab4803 | |||
| 96562f4c3b | |||
| d66ceaf7fe | |||
| 2e44769247 | |||
| e64fd45760 | |||
| 454fc7501e | |||
| a89c62728a | |||
| 1d8c0f91f8 | |||
| 25ba8e4dfc | |||
| fb4437219f | |||
| 0d1fdae84c | |||
| c5debf11cf | |||
| 9e4f89aa43 | |||
| 12935eb695 | |||
| 2386188d69 | |||
| e4b32f7f5e | |||
| 3df435d4d3 | |||
| ed076facc4 | |||
| b09c5bdcf0 | |||
| 6abb94bb68 | |||
| d2b1f095c8 | |||
| 3d34571654 | |||
| d683b3ac3e | |||
| e16a8737f4 | |||
| 8f348aea26 | |||
| 9a8ba2bdff | |||
| 3aaefdcc78 | |||
| ca59ed8c50 | |||
| ff78ef25c0 | |||
| ec487b5c6c | |||
| 0a03ed8170 | |||
| 5881dcc2b2 | |||
| 2070cbe2cc | |||
| 8198b9c57c | |||
| c35e9fc631 | |||
| f244ba55de | |||
| 5e225fe46c | |||
| 06535e518e | |||
| 81584c22cf | |||
| 83040211e8 | |||
| 4ed301df97 | |||
| 8010f506a6 | |||
| 840ea1ce5c | |||
| 447306bfb9 | |||
| 4569c1bec0 | |||
| 37ad547e18 | |||
| e037ee8d82 | |||
| 9bbadfdb34 | |||
| c1b63f68c8 | |||
| 0590e3bf8c | |||
| 8f17f5f548 | |||
| 3a8bea9e1d | |||
| b33b354818 | |||
| b08837b93e | |||
| b3b840df30 | |||
| 2ef47f0813 | |||
| 27f5513911 | |||
| a5100a9d35 | |||
| ccbc639677 | |||
| 18725a682d | |||
| dac1135199 | |||
| ead55a8938 | |||
| 8f39001ff0 | |||
| 8b056b902e | |||
| 5724adbbe9 | |||
| 1c5640cfbe | |||
| 4d982e133e | |||
| 1bf193b8fc | |||
| 7faec20f26 | |||
| ced8b45add | |||
| 15c9bc40f7 | |||
| 9aedb95296 | |||
| e56cb4418c | |||
| 1ae21c6d1a | |||
| 82acad3794 | |||
| c50461528c | |||
| bd08e49255 | |||
| dbba3cec7b | |||
| 6c5b5b3186 | |||
| 47dd02a4b3 | |||
| 8429d1a6aa | |||
| 86d3f9f632 | |||
| 48e92afd92 | |||
| 5a5c31de35 | |||
| 088941dc7b | |||
| 5f6f1691e5 | |||
| 45d6c07be6 | |||
| 5c63cd2bdb | |||
| 86318c1e88 | |||
| 0ff1fa0f6e | |||
| d90bf0c438 | |||
| dc63438e22 | |||
| 0a3ac26f00 | |||
| c64ed1caa3 | |||
| efc33b18be | |||
| c189d8d5e8 | |||
| 886227db3b | |||
| 719eb90d03 | |||
| fadb686bc5 | |||
| aebefaef0d | |||
| 63a564e8ff | |||
| 16c1593368 | |||
| 53580a3070 | |||
| d26aeed68f | |||
| 73eefd2b5d | |||
| 3df600d069 | |||
| a1adacdfe2 | |||
| f405fa9118 | |||
| 7b5d6e7c29 | |||
| 7d0ba7178e | |||
| df5e2c3aca | |||
| 343e344582 | |||
| 34ed463bea | |||
| 5986f75d16 | |||
| b08810c0c4 | |||
| 7c608ae7bc | |||
| d51cee35d5 | |||
| d0289b4c7b | |||
| bc7ba9cb3b | |||
| 6b10c8a4d3 | |||
| b9805c7309 | |||
| 844460fd22 | |||
| a600278d27 | |||
| 4398ffe8c9 | |||
| eed1eeedeb | |||
| 8c0c230f78 | |||
| e96ec1e79a | |||
| be7dd51fab | |||
| 4dec95840b | |||
| d44fb36581 | |||
| 9d3bbe27db | |||
| 13ed4afc95 | |||
| b3f54e74a7 | |||
| d6374e2718 | |||
| 74156b6b06 | |||
| 965048d3e6 | |||
| 36dfe6b98b | |||
| e4e89bdbde | |||
| 85a9d8d81d | |||
| b21ad13bbb | |||
| f0d10308e3 | |||
| f240e587cb | |||
| 2a7dc9e40e | |||
| 99b8d0063b | |||
| c6aff9278a | |||
| 129497133e | |||
| eaa9ff0cec | |||
| fa735864a3 | |||
| 0ce180e19b | |||
| e578b7eee3 | |||
| 1844695b0c | |||
| 54b9adbdbc | |||
| 8b71656cd7 | |||
| a858ef5688 | |||
| 18395c8d21 | |||
| e68ec5c9a9 | |||
| d3b264345d | |||
| f1e1733c06 | |||
| 66ffbdb881 | |||
| f7f5466ed2 | |||
| 83410fcabe | |||
| 1ee91d494d | |||
| e8fe550d6c | |||
| 5b0fe7f655 | |||
| ba439437bf | |||
| aa6eef135e | |||
| 1dd085daaa | |||
| 2a8f50bcb7 | |||
| 866c58a879 | |||
| c9a75a56c2 | |||
| e1861aa3d9 | |||
| 242c7cb93a | |||
| 0597463902 | |||
| 14d80725eb |
@@ -6,11 +6,12 @@ local library = "unordered";
|
||||
|
||||
local triggers =
|
||||
{
|
||||
branch: [ "master", "develop", "feature/*", "bugfix/*" ]
|
||||
branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ]
|
||||
};
|
||||
|
||||
local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' };
|
||||
local asan = { ASAN: '1' };
|
||||
local tsan = { TSAN: '1' };
|
||||
|
||||
local linux_pipeline(name, image, environment, packages = "", sources = [], arch = "amd64") =
|
||||
{
|
||||
@@ -224,6 +225,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 64 TSAN (11,14,17,20,2b)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '11,14,17,20,2b', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 16.04 Clang 3.5",
|
||||
"cppalliance/droneubuntu1604:1",
|
||||
@@ -343,6 +351,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
"clang-14",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 14 libc++ 64 TSAN",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests', CXXSTD: '11,14,17,20', STDLIB: 'libc++' } + tsan,
|
||||
"clang-14 libc++-14-dev libc++abi-14-dev",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 15",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
@@ -362,6 +377,12 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64",
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 12.4 Xcode 13.4.1 TSAN",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '11,14,1z', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan,
|
||||
xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64",
|
||||
),
|
||||
|
||||
windows_pipeline(
|
||||
"Windows VS2015 msvc-14.0",
|
||||
"cppalliance/dronevs2015",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
set -ex
|
||||
export PATH=~/.local/bin:/usr/local/bin:$PATH
|
||||
|
||||
: ${TARGET:="libs/$LIBRARY/test"}
|
||||
|
||||
DRONE_BUILD_DIR=$(pwd)
|
||||
|
||||
BOOST_BRANCH=develop
|
||||
@@ -22,4 +24,4 @@ python tools/boostdep/depinst/depinst.py $LIBRARY
|
||||
./b2 -d0 headers
|
||||
|
||||
echo "using $TOOLSET : : $COMPILER ;" > ~/user-config.jam
|
||||
./b2 -j3 libs/$LIBRARY/test toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS}
|
||||
./b2 -j3 $TARGET toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${STDLIB:+stdlib=$STDLIB} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${TSAN:+thread-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS}
|
||||
|
||||
@@ -19,9 +19,9 @@ on:
|
||||
- fix/**
|
||||
- pr/**
|
||||
|
||||
concurrency:
|
||||
group: ${{format('{0}:{1}', github.repository, github.ref)}}
|
||||
cancel-in-progress: true
|
||||
#concurrency:
|
||||
# group: ${{format('{0}:{1}', github.repository, github.ref)}}
|
||||
# cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GIT_FETCH_JOBS: 8
|
||||
@@ -42,40 +42,52 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# Linux, gcc
|
||||
- { compiler: gcc-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-7' }
|
||||
- { compiler: gcc-8, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-8' }
|
||||
- { compiler: gcc-9, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-9' }
|
||||
- { compiler: gcc-10, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, install: 'g++-10' }
|
||||
- { compiler: gcc-11, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, install: 'g++-11' }
|
||||
- { name: "gcc-12 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
compiler: gcc-12, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" }
|
||||
- { name: "gcc-12 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
compiler: gcc-12, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
|
||||
- { name: Collect coverage, coverage: yes,
|
||||
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" }
|
||||
# - { compiler: gcc-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-7' }
|
||||
# - { compiler: gcc-8, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-8' }
|
||||
# - { compiler: gcc-9, cxxstd: '03,11,14,17', os: ubuntu-20.04, install: 'g++-9' }
|
||||
# - { compiler: gcc-10, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, install: 'g++-10' }
|
||||
# - { compiler: gcc-11, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, install: 'g++-11' }
|
||||
# - { name: "gcc-12 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
# compiler: gcc-12, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" }
|
||||
# - { name: "gcc-12 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
# compiler: gcc-12, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
|
||||
# - { name: Collect coverage, coverage: yes,
|
||||
# compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" }
|
||||
|
||||
- { name: "cfoa tsan (gcc)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: gcc-12,
|
||||
targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes, ccache_key: "tsan-gcc" }
|
||||
|
||||
# Linux, clang, libc++
|
||||
- { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, stdlib: libc++, install: 'clang-7 libc++-7-dev libc++abi-7-dev' }
|
||||
- { compiler: clang-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-10 libc++-10-dev libc++abi-10-dev' }
|
||||
- { compiler: clang-11, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, stdlib: libc++, install: 'clang-11 libc++-11-dev libc++abi-11-dev' }
|
||||
# - { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, stdlib: libc++, install: 'clang-7 libc++-7-dev libc++abi-7-dev' }
|
||||
# - { compiler: clang-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-10 libc++-10-dev libc++abi-10-dev' }
|
||||
# - { compiler: clang-11, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, stdlib: libc++, install: 'clang-11 libc++-11-dev libc++abi-11-dev' }
|
||||
# clang-12 doesn't work on ubuntu-22.04, the linker can't find -lunwind for some reason
|
||||
- { name: "clang-12 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
compiler: clang-12, cxxstd: '03,11,14', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san1" }
|
||||
- { name: "clang-12 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
compiler: clang-12, cxxstd: '17,20,2b', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san2" }
|
||||
- { compiler: clang-13, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-13 libc++-13-dev libc++abi-13-dev' }
|
||||
- { compiler: clang-14, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' }
|
||||
# - { name: "clang-12 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
# compiler: clang-12, cxxstd: '03,11,14', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san1" }
|
||||
# - { name: "clang-12 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
# compiler: clang-12, cxxstd: '17,20,2b', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san2" }
|
||||
# - { compiler: clang-13, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-13 libc++-13-dev libc++abi-13-dev' }
|
||||
# - { compiler: clang-14, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' }
|
||||
|
||||
# not using libc++ because of https://github.com/llvm/llvm-project/issues/52771
|
||||
- { name: "clang-14 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
compiler: clang-14, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" }
|
||||
- { name: "clang-14 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
|
||||
# - { name: "clang-14 w/ sanitizers (03,11,14)", sanitize: yes,
|
||||
# compiler: clang-14, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" }
|
||||
# - { name: "clang-14 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
# compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
|
||||
|
||||
- { name: "cfoa tsan (clang)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: clang-14,
|
||||
targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes,
|
||||
stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev', ccache_key: "tsan-clang" }
|
||||
|
||||
# OSX, clang
|
||||
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, }
|
||||
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes }
|
||||
- { compiler: clang, cxxstd: '11,14', os: macos-11, ccache_key: "11-14" }
|
||||
- { compiler: clang, cxxstd: '17,2a', os: macos-11, ccache_key: "17-2a" }
|
||||
- { compiler: clang, cxxstd: '11,14', os: macos-12, sanitize: yes, ccache_key: "11-14" }
|
||||
- { compiler: clang, cxxstd: '17,2a', os: macos-12, sanitize: yes, ccache_key: "17-2a" }
|
||||
- { compiler: clang, cxxstd: '11,14', os: macos-12, thread-sanitize: yes, targets: 'libs/unordered/test//cfoa_tests', ccache_key: "tsan-macos-1" }
|
||||
- { compiler: clang, cxxstd: '17,2a', os: macos-12, thread-sanitize: yes, targets: 'libs/unordered/test//cfoa_tests', ccache_key: "tsan-macos-2" }
|
||||
|
||||
timeout-minutes: 120
|
||||
timeout-minutes: 240
|
||||
runs-on: ${{matrix.os}}
|
||||
container: ${{matrix.container}}
|
||||
env: {B2_USE_CCACHE: 1}
|
||||
@@ -184,6 +196,8 @@ jobs:
|
||||
B2_COMPILER: ${{matrix.compiler}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_SANITIZE: ${{matrix.sanitize}}
|
||||
B2_TSAN: ${{matrix.thread-sanitize}}
|
||||
B2_TARGETS: ${{matrix.targets}}
|
||||
B2_STDLIB: ${{matrix.stdlib}}
|
||||
# More entries can be added in the same way, see the B2_ARGS assignment in ci/enforce.sh for the possible keys.
|
||||
# B2_DEFINES: ${{matrix.defines}}
|
||||
@@ -197,7 +211,7 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
if: '!matrix.coverity'
|
||||
run: ci/build.sh
|
||||
run: B2_TARGETS=${{matrix.targets}} B2_FLAGS='-l 3600' ci/build.sh
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.coverage
|
||||
@@ -209,187 +223,3 @@ jobs:
|
||||
env:
|
||||
COVERITY_SCAN_NOTIFICATION_EMAIL: ${{ secrets.COVERITY_SCAN_NOTIFICATION_EMAIL }}
|
||||
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
|
||||
windows:
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { toolset: msvc-14.0, cxxstd: '14,latest', addrmd: '32,64', os: windows-2019, variant: 'debug,release' }
|
||||
- { toolset: msvc-14.2, cxxstd: '14,17,20,latest', addrmd: '32,64', os: windows-2019, variant: 'debug,release' }
|
||||
- { toolset: msvc-14.3, cxxstd: '14,17,20,latest', addrmd: '32,64', os: windows-2022, variant: 'debug,release' }
|
||||
- { toolset: msvc-14.3, cxxstd: '14', addrmd: '64', os: windows-2022, variant: 'debug', defines: '_ALLOW_RTCc_IN_STL', cxxflags: '/RTCc' }
|
||||
- { toolset: msvc-14.3, cxxstd: '14', addrmd: '32', os: windows-2022, variant: 'debug', defines: '_ALLOW_RTCc_IN_STL', cxxflags: '"/RTCc /arch:IA32"' }
|
||||
- { toolset: clang-win, cxxstd: '14,17,latest', addrmd: '32,64', os: windows-2022, variant: 'debug,release' }
|
||||
- { toolset: gcc, cxxstd: '03,11,14,17,2a', addrmd: '64', os: windows-2019, variant: 'debug,release' }
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
REM Copy ci folder if not testing Boost.CI
|
||||
if "%GITHUB_REPOSITORY%" == "%GITHUB_REPOSITORY:boost-ci=%" xcopy /s /e /q /i /y boost-ci-cloned\ci .\ci
|
||||
rmdir /s /q boost-ci-cloned
|
||||
|
||||
- name: Setup Boost
|
||||
run: ci\github\install.bat
|
||||
|
||||
- name: Run tests
|
||||
if: '!matrix.coverage'
|
||||
run: ci\build.bat
|
||||
env:
|
||||
B2_TOOLSET: ${{matrix.toolset}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_ADDRESS_MODEL: ${{matrix.addrmd}}
|
||||
B2_DEFINES: ${{matrix.defines}}
|
||||
B2_VARIANT: ${{matrix.variant}}
|
||||
B2_CXXFLAGS: ${{matrix.cxxflags}}
|
||||
|
||||
- name: Collect coverage
|
||||
shell: powershell
|
||||
if: matrix.coverage
|
||||
run: ci\opencppcoverage.ps1
|
||||
env:
|
||||
B2_TOOLSET: ${{matrix.toolset}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_ADDRESS_MODEL: ${{matrix.addrmd}}
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
files: __out/cobertura.xml
|
||||
|
||||
MSYS2:
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { sys: MINGW32, compiler: gcc, cxxstd: '03,11,17,20' }
|
||||
- { sys: MINGW64, compiler: gcc, cxxstd: '03,11,17,20' }
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup MSYS2 environment
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{matrix.sys}}
|
||||
update: true
|
||||
install: git python
|
||||
pacboy: gcc:p cmake:p ninja:p
|
||||
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
[[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci .
|
||||
rm -rf boost-ci-cloned
|
||||
|
||||
- name: Setup Boost
|
||||
env:
|
||||
B2_COMPILER: ${{matrix.compiler}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_SANITIZE: ${{matrix.sanitize}}
|
||||
B2_STDLIB: ${{matrix.stdlib}}
|
||||
run: ci/github/install.sh
|
||||
|
||||
- name: Run tests
|
||||
run: ci/build.sh
|
||||
|
||||
# Run also the CMake tests to avoid having to setup another matrix for CMake on MSYS
|
||||
- name: Run CMake tests
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_test__ && cd __build_cmake_test__
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=ON -DBoost_VERBOSE=ON ..
|
||||
cmake --build . --target tests --config Debug -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config Debug
|
||||
|
||||
CMake:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-20.04, build_shared: ON, build_type: Debug, generator: 'Unix Makefiles' }
|
||||
- { os: windows-2019, build_shared: ON, build_type: Debug, generator: 'Visual Studio 16 2019' }
|
||||
|
||||
timeout-minutes: 120
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
[[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci .
|
||||
rm -rf boost-ci-cloned
|
||||
- name: Setup Boost
|
||||
env: {B2_DONT_BOOTSTRAP: 1}
|
||||
run: source ci/github/install.sh
|
||||
|
||||
- name: Run CMake tests
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_test__ && cd __build_cmake_test__
|
||||
cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DBUILD_TESTING=ON -DBoost_VERBOSE=ON ..
|
||||
cmake --build . --target tests --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
||||
|
||||
- name: Run CMake subdir tests
|
||||
run: |
|
||||
cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_test" # New unified folder
|
||||
[ -d "$cmake_test_folder" ] || cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_subdir_test"
|
||||
cd "$cmake_test_folder"
|
||||
mkdir __build_cmake_subdir_test__ && cd __build_cmake_subdir_test__
|
||||
cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=OFF -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} ..
|
||||
cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
||||
|
||||
- name: Install Library
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__
|
||||
cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_INSTALL_PREFIX=~/.local -DBoost_VERBOSE=ON -DBoost_DEBUG=ON ..
|
||||
cmake --build . --target install --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
- name: Run CMake install tests
|
||||
run: |
|
||||
cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_test" # New unified folder
|
||||
[ -d "$cmake_test_folder" ] || cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_install_test"
|
||||
cd "$cmake_test_folder"
|
||||
mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__
|
||||
cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=ON -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_PREFIX_PATH=~/.local ..
|
||||
cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
@@ -272,6 +274,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -345,6 +350,9 @@ std::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
template<class K, class V> using boost_unordered_map_fnv1a =
|
||||
boost::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map_fnv1a =
|
||||
boost::unordered_node_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map_fnv1a =
|
||||
boost::unordered_flat_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -373,6 +381,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
@@ -390,6 +399,7 @@ int main()
|
||||
|
||||
test<std_unordered_map_fnv1a>( "std::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_map_fnv1a>( "boost::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_node_map_fnv1a>( "boost::unordered_node_map, FNV-1a" );
|
||||
test<boost_unordered_flat_map_fnv1a>( "boost::unordered_flat_map, FNV-1a" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
@@ -273,6 +275,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -346,6 +351,9 @@ std::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
template<class K, class V> using boost_unordered_map_fnv1a =
|
||||
boost::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map_fnv1a =
|
||||
boost::unordered_node_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map_fnv1a =
|
||||
boost::unordered_flat_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -374,6 +382,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
@@ -391,6 +400,7 @@ int main()
|
||||
|
||||
test<std_unordered_map_fnv1a>( "std::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_map_fnv1a>( "boost::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_node_map_fnv1a>( "boost::unordered_node_map, FNV-1a" );
|
||||
test<boost_unordered_flat_map_fnv1a>( "boost::unordered_flat_map, FNV-1a" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
@@ -289,6 +291,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -315,6 +320,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
@@ -289,6 +291,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -325,6 +330,7 @@ int main()
|
||||
#endif
|
||||
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021, 2022 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
@@ -340,6 +342,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -366,6 +371,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021, 2022 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
@@ -227,6 +229,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -300,6 +305,9 @@ std::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
template<class K, class V> using boost_unordered_map_fnv1a =
|
||||
boost::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map_fnv1a =
|
||||
boost::unordered_node_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map_fnv1a =
|
||||
boost::unordered_flat_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -328,6 +336,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
@@ -345,6 +354,7 @@ int main()
|
||||
|
||||
test<std_unordered_map_fnv1a>( "std::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_map_fnv1a>( "boost::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_node_map_fnv1a>( "boost::unordered_node_map, FNV-1a" );
|
||||
test<boost_unordered_flat_map_fnv1a>( "boost::unordered_flat_map, FNV-1a" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021, 2022 Peter Dimov.
|
||||
// Copyright 2023 Joaquin M Lopez Munoz.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
#define _SILENCE_CXX20_CISO646_REMOVED_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
@@ -181,6 +183,9 @@ template<class K, class V> using std_unordered_map =
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_node_map =
|
||||
boost::unordered_node_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_flat_map =
|
||||
boost::unordered_flat_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
@@ -207,6 +212,7 @@ int main()
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<boost_unordered_node_map>( "boost::unordered_node_map" );
|
||||
test<boost_unordered_flat_map>( "boost::unordered_flat_map" );
|
||||
|
||||
#ifdef HAVE_ANKERL_UNORDERED_DENSE
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 51 KiB |
@@ -278,13 +278,14 @@ max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
== boost::unordered_flat_map
|
||||
== boost::unordered_(flat|node)_map
|
||||
|
||||
All benchmarks were created using:
|
||||
|
||||
* `https://abseil.io/docs/cpp/guides/container[absl::flat_hash_map^]<uint64_t, uint64_t>`
|
||||
* `boost::unordered_flat_map<uint64_t, uint64_t>`
|
||||
* `boost::unordered_map<uint64_t, uint64_t>`
|
||||
* `boost::unordered_flat_map<uint64_t, uint64_t>`
|
||||
* `boost::unordered_node_map<uint64_t, uint64_t>`
|
||||
|
||||
The source code can be https://github.com/boostorg/boost_unordered_benchmarks/tree/boost_unordered_flat_map[found here^].
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ h|*Method* h|*Description*
|
||||
|Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor.
|
||||
|
||||
2+^h| *Open-addressing containers only* +
|
||||
`boost::unordered_flat_set`, `boost::unordered_flat_map`
|
||||
`boost::unordered_flat_set`, `boost::unordered_flat_map` +
|
||||
`boost::unordered_node_set`, `boost::unordered_node_map` +
|
||||
h|*Method* h|*Description*
|
||||
|
||||
|`size_type max_load() const`
|
||||
@@ -160,8 +161,9 @@ change the number of buckets when this happens. Iterators can be
|
||||
invalidated by calls to `insert`, `rehash` and `reserve`.
|
||||
|
||||
As for pointers and references,
|
||||
they are never invalidated for closed-addressing containers (`boost::unordered_[multi]set`, `boost::unordered_[multi]map`),
|
||||
but they will when rehashing occurs for open-addressing
|
||||
they are never invalidated for node-based containers
|
||||
(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`),
|
||||
but they will when rehashing occurs for
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because
|
||||
these containers store elements directly into their holding buckets, so
|
||||
when allocating a new bucket array the elements must be transferred by means of move construction.
|
||||
@@ -252,15 +254,16 @@ xref:#rationale_boostunordered_multiset_and_boostunordered_multimap[correspondin
|
||||
|
||||
== Open Addressing Implementation
|
||||
|
||||
The diagram shows the basic internal layout of `boost::unordered_flat_map` and
|
||||
`boost:unordered_flat_set`.
|
||||
The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and
|
||||
`boost:unordered_flat_set`/`unordered_node_set`.
|
||||
|
||||
|
||||
[#img-foa-layout]
|
||||
.Open-addressing layout used by Boost.Unordered.
|
||||
image::foa.png[align=center]
|
||||
|
||||
As with all open-addressing containers, elements are stored directly in the bucket array.
|
||||
As with all open-addressing containers, elements (or pointers to the element nodes in the case of
|
||||
`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array.
|
||||
This array is logically divided into 2^_n_^ _groups_ of 15 elements each.
|
||||
In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^
|
||||
16-byte words.
|
||||
|
||||
@@ -6,10 +6,23 @@
|
||||
:github-pr-url: https://github.com/boostorg/unordered/pull
|
||||
:cpp: C++
|
||||
|
||||
== Release 1.82.0
|
||||
== Release 1.83.0
|
||||
|
||||
* Sped up iteration of open-addressing containers.
|
||||
|
||||
== Release 1.82.0 - Major update
|
||||
|
||||
* {cpp}03 support is planned for deprecation. Boost 1.84.0 will no longer support
|
||||
{cpp}03 mode and {cpp}11 will become the new minimum for using the library.
|
||||
* Added node-based, open-addressing containers
|
||||
`boost::unordered_node_map` and `boost::unordered_node_set`.
|
||||
* Extended heterogeneous lookup to more member functions as specified in
|
||||
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2363r3.html[P2363].
|
||||
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2363r5.html[P2363].
|
||||
* Replaced the previous post-mixing process for open-addressing containers with
|
||||
a new algorithm based on extended multiplication by a constant.
|
||||
* Fixed bug in internal emplace() impl where stack-local types were not properly
|
||||
constructed using the Allocator of the container which breaks uses-allocator
|
||||
construction.
|
||||
|
||||
== Release 1.81.0 - Major update
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
|
||||
|Iterators, pointers and references to the container's elements are never invalidated.
|
||||
|<<buckets_iterator_invalidation,Iterators can be invalidated by calls to insert or rehash>>. +
|
||||
**Closed-addressing containers:** Pointers and references to the container's elements are never invalidated. +
|
||||
**Open-addressing containers:** Pointers and references to the container's elements are invalidated when rehashing occurs.
|
||||
**Node-based containers:** Pointers and references to the container's elements are never invalidated. +
|
||||
**Flat containers:** Pointers and references to the container's elements are invalidated when rehashing occurs.
|
||||
|
||||
|Iterators iterate through the container in the order defined by the comparison object.
|
||||
|Iterators iterate through the container in an arbitrary order, that can change as elements are inserted, although equivalent elements are always adjacent.
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
:cpp: C++
|
||||
|
||||
== Closed-addressing containers: unordered_[multi]set, unordered_[multi]map
|
||||
== Closed-addressing containers
|
||||
|
||||
The intent of Boost.Unordered is to provide a conformant
|
||||
`unordered_[multi]set` and `unordered_[multi]map` are intended to provide a conformant
|
||||
implementation of the {cpp}20 standard that will work with {cpp}98 upwards.
|
||||
This wide compatibility does mean some compromises have to be made.
|
||||
With a compiler and library that fully support {cpp}11, the differences should
|
||||
@@ -117,27 +117,31 @@ Variadic constructor arguments for `emplace` are only used when both
|
||||
rvalue references and variadic template parameters are available.
|
||||
Otherwise `emplace` can only take up to 10 constructors arguments.
|
||||
|
||||
== Open-addressing containers: unordered_flat_set, unordered_flat_map
|
||||
== Open-addressing containers
|
||||
|
||||
The C++ standard does not currently provide any open-addressing container
|
||||
specification to adhere to, so `boost::unordered_flat_set` and
|
||||
`boost::unordered_flat_map` take inspiration from `std::unordered_set` and
|
||||
specification to adhere to, so `boost::unordered_flat_set`/`unordered_node_set` and
|
||||
`boost::unordered_flat_map`/`unordered_node_map` take inspiration from `std::unordered_set` and
|
||||
`std::unordered_map`, respectively, and depart from their interface where
|
||||
convenient or as dictated by their internal data structure, which is
|
||||
radically different from that imposed by the standard (closed addressing, node based).
|
||||
radically different from that imposed by the standard (closed addressing).
|
||||
|
||||
`unordered_flat_set` and `unordered_flat_map` only work with reasonably
|
||||
Open-addressing containers provided by Boost.Unordered only work with reasonably
|
||||
compliant C++11 (or later) compilers. Language-level features such as move semantics
|
||||
and variadic template parameters are then not emulated.
|
||||
`unordered_flat_set` and `unordered_flat_map` are fully https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^].
|
||||
The containers are fully https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^].
|
||||
|
||||
The main differences with C++ unordered associative containers are:
|
||||
|
||||
* `value_type` must be move-constructible.
|
||||
* Pointer stability is not kept under rehashing.
|
||||
* `begin()` is not constant-time.
|
||||
* `erase(iterator)` returns `void` instead of an iterator to the following element.
|
||||
* There is no API for bucket handling (except `bucket_count`) or node extraction/insertion.
|
||||
* The maximum load factor of the container is managed internally and can't be set by the user. The maximum load,
|
||||
exposed through the public function `max_load`, may decrease on erasure under high-load conditions.
|
||||
* In general:
|
||||
** `begin()` is not constant-time.
|
||||
** `erase(iterator)` returns `void` instead of an iterator to the following element.
|
||||
** There is no API for bucket handling (except `bucket_count`).
|
||||
** The maximum load factor of the container is managed internally and can't be set by the user. The maximum load,
|
||||
exposed through the public function `max_load`, may decrease on erasure under high-load conditions.
|
||||
* Flat containers (`boost::unordered_flat_set` and `boost::unordered_flat_map`):
|
||||
** `value_type` must be move-constructible.
|
||||
** Pointer stability is not kept under rehashing.
|
||||
** There is no API for node extraction/insertion.
|
||||
|
||||
//-
|
||||
|
||||
@@ -9,10 +9,10 @@ Copyright (C) 2003, 2004 Jeremy B. Maitin-Shepard
|
||||
|
||||
Copyright (C) 2005-2008 Daniel James
|
||||
|
||||
Copyright (C) 2022 Christian Mazakas
|
||||
Copyright (C) 2022-2023 Christian Mazakas
|
||||
|
||||
Copyright (C) 2022 Joaquín M López Muñoz
|
||||
Copyright (C) 2022-2023 Joaquín M López Muñoz
|
||||
|
||||
Copyright (C) 2022 Peter Dimov
|
||||
Copyright (C) 2022-2023 Peter Dimov
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
@@ -106,8 +106,69 @@ namespace boost {
|
||||
}
|
||||
----
|
||||
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map` require a
|
||||
reasonably compliant C++11 compiler.
|
||||
Starting in Boost 1.82, the containers `boost::unordered_node_set` and `boost::unordered_node_map`
|
||||
are introduced: they use open addressing like `boost::unordered_flat_set` and `boost::unordered_flat_map`,
|
||||
but internally store element _nodes_, like `boost::unordered_set` and `boost::unordered_map`,
|
||||
which provide stability of pointers and references to the elements:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_node_set.hpp>
|
||||
//
|
||||
// Note: no multiset version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_node_set;
|
||||
}
|
||||
----
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_node_map.hpp>
|
||||
//
|
||||
// Note: no multimap version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class unordered_node_map;
|
||||
}
|
||||
----
|
||||
|
||||
These are all the containers provided by Boost.Unordered:
|
||||
|
||||
[caption=, title='Table {counter:table-counter}. Boost.Unordered containers']
|
||||
[cols="1,1,.^1", frame=all, grid=rows]
|
||||
|===
|
||||
^h|
|
||||
^h|*Node-based*
|
||||
^h|*Flat*
|
||||
|
||||
^.^h|*Closed addressing*
|
||||
^| `boost::unordered_set` +
|
||||
`boost::unordered_map` +
|
||||
`boost::unordered_multiset` +
|
||||
`boost::unordered_multimap`
|
||||
^|
|
||||
|
||||
^.^h|*Open addressing*
|
||||
^| `boost::unordered_node_set` +
|
||||
`boost::unordered_node_map`
|
||||
^| `boost::unordered_flat_set` +
|
||||
`boost::unordered_flat_map`
|
||||
|
||||
|===
|
||||
|
||||
Closed-addressing containers are pass:[C++]98-compatible. Open-addressing containers require a
|
||||
reasonably compliant pass:[C++]11 compiler.
|
||||
|
||||
Boost.Unordered containers are used in a similar manner to the normal associative
|
||||
containers:
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
= Implementation Rationale
|
||||
|
||||
== boost::unordered_[multi]set and boost::unordered_[multi]map
|
||||
== Closed-addressing containers
|
||||
|
||||
These containers adhere to the standard requirements for unordered associative
|
||||
`boost::unordered_[multi]set` and `boost::unordered_[multi]map`
|
||||
adhere to the standard requirements for unordered associative
|
||||
containers, so the interface was fixed. But there are
|
||||
still some implementation decisions to make. The priorities are
|
||||
conformance to the standard and portability.
|
||||
@@ -64,8 +65,8 @@ of bits in the hash value, so it was only used when `size_t` was 64 bit.
|
||||
|
||||
Since release 1.79.0, https://en.wikipedia.org/wiki/Hash_function#Fibonacci_hashing[Fibonacci hashing]
|
||||
is used instead. With this implementation, the bucket number is determined
|
||||
by using `(h * m) >> (w - k)`, where `h` is the hash value, `m` is the golden
|
||||
ratio multiplied by `2^w`, `w` is the word size (32 or 64), and `2^k` is the
|
||||
by using `(h * m) >> (w - k)`, where `h` is the hash value, `m` is `2^w` divided
|
||||
by the golden ratio, `w` is the word size (32 or 64), and `2^k` is the
|
||||
number of buckets. This provides a good compromise between speed and
|
||||
distribution.
|
||||
|
||||
@@ -73,7 +74,7 @@ Since release 1.80.0, prime numbers are chosen for the number of buckets in
|
||||
tandem with sophisticated modulo arithmetic. This removes the need for "mixing"
|
||||
the result of the user's hash function as was used for release 1.79.0.
|
||||
|
||||
== boost::unordered_flat_set and boost::unordered_flat_map
|
||||
== Open-addresing containers
|
||||
|
||||
The C++ standard specification of unordered associative containers impose
|
||||
severe limitations on permissible implementations, the most important being
|
||||
@@ -81,29 +82,32 @@ that closed addressing is implicitly assumed. Slightly relaxing this specificati
|
||||
opens up the possibility of providing container variations taking full
|
||||
advantage of open-addressing techniques.
|
||||
|
||||
The design of `boost::unordered_flat_set` and `boost::unordered_flat_map` has been
|
||||
The design of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` has been
|
||||
guided by Peter Dimov's https://pdimov.github.io/articles/unordered_dev_plan.html[Development Plan for Boost.Unordered^].
|
||||
We discuss here the most relevant principles.
|
||||
|
||||
=== Hash function
|
||||
|
||||
Given its rich functionality and cross-platform interoperability,
|
||||
`boost::hash` remains the default hash function of `boost::unordered_flat_set` and `boost::unordered_flat_map`.
|
||||
`boost::hash` remains the default hash function of open-addressing containers.
|
||||
As it happens, `boost::hash` for integral and other basic types does not possess
|
||||
the statistical properties required by open addressing; to cope with this,
|
||||
we implement a post-mixing stage:
|
||||
|
||||
* 64-bit architectures: we use the `xmx` function defined in
|
||||
Jon Maiga's http://jonkagstrom.com/bit-mixer-construction/index.html[The construct of a bit mixer^].
|
||||
* 32-bit architectures: the mixer used was selected from a set generated with https://github.com/skeeto/hash-prospector[Hash Function Prospector^]
|
||||
as the best overall performer in our internal benchmarks. Score assigned by Hash Prospector is 333.7934929677524.
|
||||
{nbsp}{nbsp}{nbsp}{nbsp} _a_ <- _h_ *mulx* _C_, +
|
||||
{nbsp}{nbsp}{nbsp}{nbsp} _h_ <- *high*(_a_) *xor* *low*(_a_),
|
||||
|
||||
where *mulx* is an _extended multiplication_ (128 bits in 64-bit architectures, 64 bits in 32-bit environments),
|
||||
and *high* and *low* are the upper and lower halves of an extended word, respectively.
|
||||
In 64-bit architectures, _C_ is the integer part of 2^64^∕https://en.wikipedia.org/wiki/Golden_ratio[_φ_],
|
||||
whereas in 32 bits _C_ = 0xE817FB2Du has been obtained from https://arxiv.org/abs/2001.05304[Steele and Vigna (2021)^].
|
||||
|
||||
When using a hash function directly suitable for open addressing, post-mixing can be opted out by via a dedicated <<hash_traits_hash_is_avalanching,`hash_is_avalanching`>>trait.
|
||||
`boost::hash` specializations for string types are marked as avalanching.
|
||||
|
||||
=== Platform interoperability
|
||||
|
||||
The observable behavior of `boost::unordered_flat_set` and `boost::unordered_flat_map` is deterministically
|
||||
The observable behavior of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` is deterministically
|
||||
identical across different compilers as long as their ``std::size_type``s are the same size and the user-provided
|
||||
hash function and equality predicate are also interoperable
|
||||
—this includes elements being ordered in exactly the same way for the same sequence of
|
||||
|
||||
@@ -8,3 +8,5 @@ include::unordered_multiset.adoc[]
|
||||
include::hash_traits.adoc[]
|
||||
include::unordered_flat_map.adoc[]
|
||||
include::unordered_flat_set.adoc[]
|
||||
include::unordered_node_map.adoc[]
|
||||
include::unordered_node_set.adoc[]
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
/* Fast open-addressing concurrent hash table.
|
||||
*
|
||||
* Copyright 2023 Christian Mazakas.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
/* Reference:
|
||||
* https://github.com/joaquintides/concurrent_hashmap_api#proposed-synopsis
|
||||
*/
|
||||
|
||||
#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
|
||||
#include <boost/unordered/detail/foa/concurrent_table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
#include <boost/core/allocator_access.hpp>
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/type_traits/type_identity.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) \
|
||||
static_assert(boost::unordered::detail::is_invocable<F, value_type&>::value, \
|
||||
"The provided Callable must be invocable with `value_type&`");
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) \
|
||||
static_assert( \
|
||||
boost::unordered::detail::is_invocable<F, value_type const&>::value, \
|
||||
"The provided Callable must be invocable with `value_type const&`");
|
||||
|
||||
#define BOOST_UNORDERED_COMMA ,
|
||||
|
||||
#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \
|
||||
mp11::mp_back<mp11::mp_list<Arg BOOST_UNORDERED_COMMA Args> >
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args))
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \
|
||||
BOOST_UNORDERED_LAST_ARG(Arg, Args))
|
||||
|
||||
namespace boost {
|
||||
namespace unordered {
|
||||
namespace detail {
|
||||
|
||||
template <class F, class... Args>
|
||||
struct is_invocable
|
||||
: std::is_constructible<std::function<void(Args...)>,
|
||||
std::reference_wrapper<typename std::remove_reference<F>::type> >
|
||||
{
|
||||
};
|
||||
|
||||
template <class Key, class T> struct concurrent_map_types
|
||||
{
|
||||
using key_type = Key;
|
||||
using raw_key_type = typename std::remove_const<Key>::type;
|
||||
using raw_mapped_type = typename std::remove_const<T>::type;
|
||||
|
||||
using init_type = std::pair<raw_key_type, raw_mapped_type>;
|
||||
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
|
||||
using value_type = std::pair<Key const, T>;
|
||||
|
||||
using element_type = value_type;
|
||||
|
||||
static value_type& value_from(element_type& x) { return x; }
|
||||
|
||||
template <class K, class V>
|
||||
static raw_key_type const& extract(std::pair<K, V> const& kv)
|
||||
{
|
||||
return kv.first;
|
||||
}
|
||||
|
||||
static moved_type move(init_type& x)
|
||||
{
|
||||
return {std::move(x.first), std::move(x.second)};
|
||||
}
|
||||
|
||||
static moved_type move(element_type& x)
|
||||
{
|
||||
// TODO: we probably need to launder here
|
||||
return {std::move(const_cast<raw_key_type&>(x.first)),
|
||||
std::move(const_cast<raw_mapped_type&>(x.second))};
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, init_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, value_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, init_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, value_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <class Key, class T, class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Allocator = std::allocator<std::pair<Key const, T> > >
|
||||
class concurrent_flat_map
|
||||
{
|
||||
private:
|
||||
using type_policy = detail::concurrent_map_types<Key, T>;
|
||||
|
||||
detail::foa::concurrent_table<type_policy, Hash, Pred, Allocator> table_;
|
||||
|
||||
public:
|
||||
using key_type = Key;
|
||||
using mapped_type = T;
|
||||
using value_type = typename type_policy::value_type;
|
||||
using init_type = typename type_policy::init_type;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using hasher = typename boost::type_identity<Hash>::type;
|
||||
using key_equal = typename boost::type_identity<Pred>::type;
|
||||
using allocator_type = typename boost::type_identity<Allocator>::type;
|
||||
using reference = value_type&;
|
||||
using const_reference = value_type const&;
|
||||
using pointer = typename boost::allocator_pointer<allocator_type>::type;
|
||||
using const_pointer =
|
||||
typename boost::allocator_const_pointer<allocator_type>::type;
|
||||
|
||||
concurrent_flat_map()
|
||||
: concurrent_flat_map(detail::foa::default_bucket_count)
|
||||
{
|
||||
}
|
||||
|
||||
explicit concurrent_flat_map(size_type n, const hasher& hf = hasher(),
|
||||
const key_equal& eql = key_equal(),
|
||||
const allocator_type& a = allocator_type())
|
||||
: table_(n, hf, eql, a)
|
||||
{
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
concurrent_flat_map(InputIterator f, InputIterator l,
|
||||
size_type n = detail::foa::default_bucket_count,
|
||||
const hasher& hf = hasher(), const key_equal& eql = key_equal(),
|
||||
const allocator_type& a = allocator_type())
|
||||
: table_(n, hf, eql, a)
|
||||
{
|
||||
this->insert(f, l);
|
||||
}
|
||||
|
||||
concurrent_flat_map(concurrent_flat_map const& rhs)
|
||||
: table_(rhs.table_,
|
||||
boost::allocator_select_on_container_copy_construction(
|
||||
rhs.get_allocator()))
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(concurrent_flat_map&& rhs)
|
||||
: table_(std::move(rhs.table_))
|
||||
{
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
concurrent_flat_map(InputIterator f, InputIterator l, allocator_type a)
|
||||
: concurrent_flat_map(f, l, 0, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
explicit concurrent_flat_map(allocator_type a)
|
||||
: table_(detail::foa::default_bucket_count, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(concurrent_flat_map const& rhs, allocator_type a)
|
||||
: table_(rhs.table_, a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(concurrent_flat_map&& rhs, allocator_type a)
|
||||
: table_(std::move(rhs.table_), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(std::initializer_list<value_type> il,
|
||||
size_type n = detail::foa::default_bucket_count,
|
||||
const hasher& hf = hasher(), const key_equal& eql = key_equal(),
|
||||
const allocator_type& a = allocator_type())
|
||||
: concurrent_flat_map(n, hf, eql, a)
|
||||
{
|
||||
this->insert(il.begin(), il.end());
|
||||
}
|
||||
|
||||
concurrent_flat_map(size_type n, const allocator_type& a)
|
||||
: concurrent_flat_map(n, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(
|
||||
size_type n, const hasher& hf, const allocator_type& a)
|
||||
: concurrent_flat_map(n, hf, key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
concurrent_flat_map(
|
||||
InputIterator f, InputIterator l, size_type n, const allocator_type& a)
|
||||
: concurrent_flat_map(f, l, n, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
concurrent_flat_map(InputIterator f, InputIterator l, size_type n,
|
||||
const hasher& hf, const allocator_type& a)
|
||||
: concurrent_flat_map(f, l, n, hf, key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(
|
||||
std::initializer_list<value_type> il, const allocator_type& a)
|
||||
: concurrent_flat_map(
|
||||
il, detail::foa::default_bucket_count, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(std::initializer_list<value_type> il, size_type n,
|
||||
const allocator_type& a)
|
||||
: concurrent_flat_map(il, n, hasher(), key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
concurrent_flat_map(std::initializer_list<value_type> il, size_type n,
|
||||
const hasher& hf, const allocator_type& a)
|
||||
: concurrent_flat_map(il, n, hf, key_equal(), a)
|
||||
{
|
||||
}
|
||||
|
||||
~concurrent_flat_map() = default;
|
||||
|
||||
concurrent_flat_map& operator=(concurrent_flat_map const& rhs)
|
||||
{
|
||||
table_ = rhs.table_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Capacity
|
||||
///
|
||||
|
||||
size_type size() const noexcept { return table_.size(); }
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD bool empty() const noexcept
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t cvisit(key_type const& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
}
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
visit(K&& k, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.visit(std::forward<K>(k), f);
|
||||
}
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
visit(K&& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(std::forward<K>(k), f);
|
||||
}
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
cvisit(K&& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(std::forward<K>(k), f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t visit_all(F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.visit_all(f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t visit_all(F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit_all(f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t cvisit_all(F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.cvisit_all(f);
|
||||
}
|
||||
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy p, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
table_.visit_all(p, f);
|
||||
}
|
||||
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy p, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
table_.visit_all(p, f);
|
||||
}
|
||||
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
cvisit_all(ExecPolicy p, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
table_.cvisit_all(p, f);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Modifiers
|
||||
///
|
||||
|
||||
BOOST_FORCEINLINE bool insert(value_type const& obj)
|
||||
{
|
||||
return table_.insert(obj);
|
||||
}
|
||||
BOOST_FORCEINLINE bool insert(value_type&& obj)
|
||||
{
|
||||
return table_.insert(std::move(obj));
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE bool insert(init_type const& obj)
|
||||
{
|
||||
return table_.insert(obj);
|
||||
}
|
||||
BOOST_FORCEINLINE bool insert(init_type&& obj)
|
||||
{
|
||||
return table_.insert(std::move(obj));
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
BOOST_FORCEINLINE void insert(InputIterator begin, InputIterator end)
|
||||
{
|
||||
for (auto pos = begin; pos != end; ++pos) {
|
||||
table_.insert(*pos);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE void insert(std::initializer_list<value_type> ilist)
|
||||
{
|
||||
this->insert(ilist.begin(), ilist.end());
|
||||
}
|
||||
|
||||
template <class M>
|
||||
BOOST_FORCEINLINE bool insert_or_assign(key_type const& k, M&& obj)
|
||||
{
|
||||
return table_.try_emplace_or_visit(k, std::forward<M>(obj),
|
||||
[&](value_type& m) { m.second = std::forward<M>(obj); });
|
||||
}
|
||||
|
||||
template <class M>
|
||||
BOOST_FORCEINLINE bool insert_or_assign(key_type&& k, M&& obj)
|
||||
{
|
||||
return table_.try_emplace_or_visit(std::move(k), std::forward<M>(obj),
|
||||
[&](value_type& m) { m.second = std::forward<M>(obj); });
|
||||
}
|
||||
|
||||
template <class K, class M>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
|
||||
insert_or_assign(K&& k, M&& obj)
|
||||
{
|
||||
return table_.try_emplace_or_visit(std::forward<K>(k),
|
||||
std::forward<M>(obj),
|
||||
[&](value_type& m) { m.second = std::forward<M>(obj); });
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(value_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(value_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(init_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(init_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class InputIterator, class F>
|
||||
BOOST_FORCEINLINE void insert_or_visit(
|
||||
InputIterator first, InputIterator last, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
for (; first != last; ++first) {
|
||||
table_.insert_or_visit(*first, f);
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE void insert_or_visit(
|
||||
std::initializer_list<value_type> ilist, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
this->insert_or_visit(ilist.begin(), ilist.end(), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(value_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(value_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(init_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class InputIterator, class F>
|
||||
BOOST_FORCEINLINE void insert_or_cvisit(
|
||||
InputIterator first, InputIterator last, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
for (; first != last; ++first) {
|
||||
table_.insert_or_cvisit(*first, f);
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE void insert_or_cvisit(
|
||||
std::initializer_list<value_type> ilist, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
this->insert_or_visit(ilist.begin(), ilist.end(), f);
|
||||
}
|
||||
|
||||
template <class... Args> BOOST_FORCEINLINE bool emplace(Args&&... args)
|
||||
{
|
||||
return table_.emplace(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool emplace_or_visit(Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
|
||||
return table_.emplace_or_visit(
|
||||
std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool emplace_or_cvisit(Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
|
||||
return table_.emplace_or_cvisit(
|
||||
std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace(key_type const& k, Args&&... args)
|
||||
{
|
||||
return table_.try_emplace(k, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace(key_type&& k, Args&&... args)
|
||||
{
|
||||
return table_.try_emplace(std::move(k), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class K, class... Args>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
|
||||
try_emplace(K&& k, Args&&... args)
|
||||
{
|
||||
return table_.try_emplace(
|
||||
std::forward<K>(k), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_visit(
|
||||
key_type const& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_visit(
|
||||
k, std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
|
||||
key_type const& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_cvisit(
|
||||
k, std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_visit(
|
||||
key_type&& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_visit(
|
||||
std::move(k), std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
|
||||
key_type&& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_cvisit(
|
||||
std::move(k), std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class K, class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_visit(
|
||||
K&& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_visit(std::forward<K>(k),
|
||||
std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class K, class Arg, class... Args>
|
||||
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
|
||||
K&& k, Arg&& arg, Args&&... args)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
|
||||
return table_.try_emplace_or_cvisit(std::forward<K>(k),
|
||||
std::forward<Arg>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE size_type erase(key_type const& k)
|
||||
{
|
||||
return table_.erase(k);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
|
||||
erase(K&& k)
|
||||
{
|
||||
return table_.erase(std::forward<K>(k));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE size_type erase_if(key_type const& k, F f)
|
||||
{
|
||||
return table_.erase_if(k, f);
|
||||
}
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value &&
|
||||
!detail::is_execution_policy<K>::value,
|
||||
size_type>::type
|
||||
erase_if(K&& k, F f)
|
||||
{
|
||||
return table_.erase_if(std::forward<K>(k), f);
|
||||
}
|
||||
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
erase_if(ExecPolicy p, F f)
|
||||
{
|
||||
table_.erase_if(p, f);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class F> BOOST_FORCEINLINE size_type erase_if(F f)
|
||||
{
|
||||
return table_.erase_if(f);
|
||||
}
|
||||
|
||||
/// Hash Policy
|
||||
///
|
||||
void rehash(size_type n) { table_.rehash(n); }
|
||||
void reserve(size_type n) { table_.reserve(n); }
|
||||
|
||||
/// Observers
|
||||
///
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return table_.get_allocator();
|
||||
}
|
||||
|
||||
hasher hash_function() const { return table_.hash_function(); }
|
||||
key_equal key_eq() const { return table_.key_eq(); }
|
||||
};
|
||||
} // namespace unordered
|
||||
} // namespace boost
|
||||
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE
|
||||
#undef BOOST_UNORDERED_COMMA
|
||||
#undef BOOST_UNORDERED_LAST_ARG
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE
|
||||
|
||||
#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Copyright 2023 Christian Mazakas.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
#ifndef BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
|
||||
#define BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
|
||||
|
||||
namespace boost{
|
||||
namespace unordered{
|
||||
namespace detail{
|
||||
namespace foa{
|
||||
|
||||
template<class T>
|
||||
struct element_type
|
||||
{
|
||||
using value_type=T;
|
||||
value_type* p;
|
||||
|
||||
/*
|
||||
* we use a deleted copy constructor here so the type is no longer
|
||||
* trivially copy-constructible which inhibits our memcpy
|
||||
* optimizations when copying the tables
|
||||
*/
|
||||
element_type() = default;
|
||||
element_type(value_type* p_):p(p_){}
|
||||
element_type(element_type const&) = delete;
|
||||
element_type(element_type&& rhs) noexcept
|
||||
{
|
||||
p = rhs.p;
|
||||
rhs.p = nullptr;
|
||||
}
|
||||
|
||||
element_type& operator=(element_type const&)=delete;
|
||||
element_type& operator=(element_type&& rhs)noexcept
|
||||
{
|
||||
if (this!=&rhs){
|
||||
p=rhs.p;
|
||||
rhs.p=nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(element_type& rhs)noexcept
|
||||
{
|
||||
auto tmp=p;
|
||||
p=rhs.p;
|
||||
rhs.p=tmp;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Copyright 2023 Joaquin M Lopez Munoz.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#if defined(BOOST_GCC)
|
||||
#if !defined(BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW)
|
||||
/* GCC's -Wshadow triggers at scenarios like this:
|
||||
*
|
||||
* struct foo{};
|
||||
* template<typename Base>
|
||||
* struct derived:Base
|
||||
* {
|
||||
* void f(){int foo;}
|
||||
* };
|
||||
*
|
||||
* derived<foo>x;
|
||||
* x.f(); // declaration of "foo" in derived::f shadows base type "foo"
|
||||
*
|
||||
* This makes shadowing warnings unavoidable in general when a class template
|
||||
* derives from user-provided classes, as is the case with foa::table_core
|
||||
* deriving from empty_value.
|
||||
*/
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#else
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
@@ -45,20 +45,30 @@ struct node_handle_base
|
||||
|
||||
private:
|
||||
using node_value_type=typename type_policy::value_type;
|
||||
node_value_type* p_=nullptr;
|
||||
element_type p_;
|
||||
BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage<Allocator> a_;
|
||||
|
||||
protected:
|
||||
node_value_type& element()noexcept
|
||||
node_value_type& data()noexcept
|
||||
{
|
||||
BOOST_ASSERT(!empty());
|
||||
return *p_;
|
||||
return *(p_.p);
|
||||
}
|
||||
|
||||
node_value_type const& element()const noexcept
|
||||
node_value_type const& data()const noexcept
|
||||
{
|
||||
return *(p_.p);
|
||||
}
|
||||
|
||||
element_type& element()noexcept
|
||||
{
|
||||
BOOST_ASSERT(!empty());
|
||||
return *p_;
|
||||
return p_;
|
||||
}
|
||||
|
||||
element_type const& element()const noexcept
|
||||
{
|
||||
BOOST_ASSERT(!empty());
|
||||
return p_;
|
||||
}
|
||||
|
||||
Allocator& al()noexcept
|
||||
@@ -73,110 +83,114 @@ struct node_handle_base
|
||||
return a_.t_;
|
||||
}
|
||||
|
||||
void emplace(node_value_type* p,Allocator a)
|
||||
{
|
||||
BOOST_ASSERT(empty());
|
||||
p_=p;
|
||||
new(&a_.t_)Allocator(a);
|
||||
}
|
||||
|
||||
void emplace(element_type&& x,Allocator a)
|
||||
{
|
||||
emplace(x.p,a);
|
||||
BOOST_ASSERT(empty());
|
||||
auto* p=x.p;
|
||||
p_.p=p;
|
||||
new(&a_.t_)Allocator(a);
|
||||
x.p=nullptr;
|
||||
}
|
||||
|
||||
void clear()
|
||||
void reset()
|
||||
{
|
||||
al().~Allocator();
|
||||
p_=nullptr;
|
||||
a_.t_.~Allocator();
|
||||
p_.p=nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr node_handle_base()noexcept{}
|
||||
constexpr node_handle_base()noexcept:p_{nullptr}{}
|
||||
|
||||
node_handle_base(node_handle_base&& nh) noexcept
|
||||
{
|
||||
p_.p = nullptr;
|
||||
if (!nh.empty()){
|
||||
emplace(nh.p_,nh.al());
|
||||
nh.clear();
|
||||
emplace(std::move(nh.p_),nh.al());
|
||||
nh.reset();
|
||||
}
|
||||
}
|
||||
|
||||
node_handle_base& operator=(node_handle_base&& nh)noexcept
|
||||
{
|
||||
bool const pocma=
|
||||
boost::allocator_propagate_on_container_move_assignment<
|
||||
Allocator>::type::value;
|
||||
if(this!=&nh){
|
||||
if(empty()){
|
||||
if(nh.empty()){ /* empty(), nh.empty() */
|
||||
/* nothing to do */
|
||||
}else{ /* empty(), !nh.empty() */
|
||||
emplace(std::move(nh.p_),std::move(nh.al()));
|
||||
nh.reset();
|
||||
}
|
||||
}else{
|
||||
if(nh.empty()){ /* !empty(), nh.empty() */
|
||||
type_policy::destroy(al(),&p_);
|
||||
reset();
|
||||
}else{ /* !empty(), !nh.empty() */
|
||||
bool const pocma=
|
||||
boost::allocator_propagate_on_container_move_assignment<
|
||||
Allocator>::type::value;
|
||||
|
||||
BOOST_ASSERT(
|
||||
pocma
|
||||
||empty()
|
||||
||nh.empty()
|
||||
||(al()==nh.al()));
|
||||
BOOST_ASSERT(pocma||al()==nh.al());
|
||||
|
||||
if(!empty()){
|
||||
type_policy::destroy(al(),p_);
|
||||
if (pocma&&!nh.empty()){al()=std::move(nh.al());}
|
||||
type_policy::destroy(al(),&p_);
|
||||
if(pocma){
|
||||
al()=std::move(nh.al());
|
||||
}
|
||||
|
||||
p_=std::move(nh.p_);
|
||||
nh.reset();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(empty()){ /* empty(), nh.empty() */
|
||||
/* nothing to do */
|
||||
}else{ /* !empty(), !nh.empty() */
|
||||
type_policy::destroy(al(),&p_);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
if(!nh.empty()){
|
||||
if(empty()){new(&a_.t_)Allocator(std::move(nh.al()));}
|
||||
p_=nh.p_;
|
||||
|
||||
nh.p_=nullptr;
|
||||
nh.a_.t_.~Allocator();
|
||||
}else if (!empty()){
|
||||
a_.t_.~Allocator();
|
||||
p_=nullptr;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~node_handle_base()
|
||||
{
|
||||
if(!empty()){
|
||||
type_policy::destroy(al(),p_);
|
||||
a_.t_.~Allocator();
|
||||
type_policy::destroy(al(),&p_);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
allocator_type get_allocator()const noexcept{return al();}
|
||||
explicit operator bool()const noexcept{ return !empty();}
|
||||
BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_==nullptr;}
|
||||
BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_.p==nullptr;}
|
||||
|
||||
void swap(node_handle_base& nh) noexcept(
|
||||
boost::allocator_is_always_equal<Allocator>::type::value||
|
||||
boost::allocator_propagate_on_container_swap<Allocator>::type::value)
|
||||
{
|
||||
using std::swap;
|
||||
if(this!=&nh){
|
||||
if(empty()){
|
||||
if(nh.empty()) {
|
||||
/* nothing to do here */
|
||||
} else {
|
||||
emplace(std::move(nh.p_), nh.al());
|
||||
nh.reset();
|
||||
}
|
||||
}else{
|
||||
if(nh.empty()){
|
||||
nh.emplace(std::move(p_),al());
|
||||
reset();
|
||||
}else{
|
||||
bool const pocs=
|
||||
boost::allocator_propagate_on_container_swap<
|
||||
Allocator>::type::value;
|
||||
|
||||
bool const pocs=
|
||||
boost::allocator_propagate_on_container_swap<Allocator>::type::value;
|
||||
BOOST_ASSERT(pocs || al()==nh.al());
|
||||
|
||||
if (!empty()&&!nh.empty()){
|
||||
BOOST_ASSERT(pocs || al()==nh.al());
|
||||
|
||||
node_value_type *p=p_;
|
||||
p_=nh.p_;
|
||||
nh.p_=p;
|
||||
|
||||
if(pocs){
|
||||
swap(al(),nh.al());
|
||||
using std::swap;
|
||||
p_.swap(nh.p_);
|
||||
if(pocs)swap(al(),nh.al());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty()&&nh.empty()){return;}
|
||||
|
||||
if (empty()){
|
||||
emplace(nh.p_,nh.al());
|
||||
nh.clear();
|
||||
}else{
|
||||
nh.emplace(p_,al());
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/* Copyright 2023 Joaquin M Lopez Munoz.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
#define BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW
|
||||
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
|
||||
#undef BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW
|
||||
@@ -0,0 +1,188 @@
|
||||
#ifndef BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
|
||||
#define BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
|
||||
|
||||
// Copyright 2023 Peter Dimov
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#include <boost/smart_ptr/detail/sp_thread_pause.hpp>
|
||||
#include <boost/smart_ptr/detail/sp_thread_sleep.hpp>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace boost{
|
||||
namespace unordered{
|
||||
namespace detail{
|
||||
namespace foa{
|
||||
|
||||
class rw_spinlock
|
||||
{
|
||||
private:
|
||||
|
||||
// bit 31: locked exclusive
|
||||
// bit 30: writer pending
|
||||
// bit 29..0: reader lock count
|
||||
|
||||
static constexpr std::uint32_t locked_exclusive_mask = 1u << 31; // 0x8000'0000
|
||||
static constexpr std::uint32_t writer_pending_mask = 1u << 30; // 0x4000'0000
|
||||
static constexpr std::uint32_t reader_lock_count_mask = writer_pending_mask - 1; // 0x3FFF'FFFF
|
||||
|
||||
std::atomic<std::uint32_t> state_ = {};
|
||||
|
||||
private:
|
||||
|
||||
// number of times to spin before sleeping
|
||||
static constexpr int spin_count = 24576;
|
||||
|
||||
public:
|
||||
|
||||
bool try_lock_shared() noexcept
|
||||
{
|
||||
std::uint32_t st = state_.load( std::memory_order_relaxed );
|
||||
|
||||
if( st >= reader_lock_count_mask )
|
||||
{
|
||||
// either bit 31 set, bit 30 set, or reader count is max
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t newst = st + 1;
|
||||
return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
void lock_shared() noexcept
|
||||
{
|
||||
for( ;; )
|
||||
{
|
||||
for( int k = 0; k < spin_count; ++k )
|
||||
{
|
||||
std::uint32_t st = state_.load( std::memory_order_relaxed );
|
||||
|
||||
if( st < reader_lock_count_mask )
|
||||
{
|
||||
std::uint32_t newst = st + 1;
|
||||
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_pause();
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared() noexcept
|
||||
{
|
||||
// pre: locked shared, not locked exclusive
|
||||
|
||||
state_.fetch_sub( 1, std::memory_order_release );
|
||||
|
||||
// if the writer pending bit is set, there's a writer waiting
|
||||
// let it acquire the lock; it will clear the bit on unlock
|
||||
}
|
||||
|
||||
bool try_lock() noexcept
|
||||
{
|
||||
std::uint32_t st = state_.load( std::memory_order_relaxed );
|
||||
|
||||
if( st & locked_exclusive_mask )
|
||||
{
|
||||
// locked exclusive
|
||||
return false;
|
||||
}
|
||||
|
||||
if( st & reader_lock_count_mask )
|
||||
{
|
||||
// locked shared
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t newst = locked_exclusive_mask;
|
||||
return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
void lock() noexcept
|
||||
{
|
||||
for( ;; )
|
||||
{
|
||||
for( int k = 0; k < spin_count; ++k )
|
||||
{
|
||||
std::uint32_t st = state_.load( std::memory_order_relaxed );
|
||||
|
||||
if( st & locked_exclusive_mask )
|
||||
{
|
||||
// locked exclusive, spin
|
||||
}
|
||||
else if( ( st & reader_lock_count_mask ) == 0 )
|
||||
{
|
||||
// not locked exclusive, not locked shared, try to lock
|
||||
|
||||
std::uint32_t newst = locked_exclusive_mask;
|
||||
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
|
||||
}
|
||||
else if( st & writer_pending_mask )
|
||||
{
|
||||
// writer pending bit already set, nothing to do
|
||||
}
|
||||
else
|
||||
{
|
||||
// locked shared, set writer pending bit
|
||||
|
||||
std::uint32_t newst = st | writer_pending_mask;
|
||||
state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_pause();
|
||||
}
|
||||
|
||||
// clear writer pending bit before going to sleep
|
||||
|
||||
{
|
||||
std::uint32_t st = state_.load( std::memory_order_relaxed );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
if( st & locked_exclusive_mask )
|
||||
{
|
||||
// locked exclusive, nothing to do
|
||||
break;
|
||||
}
|
||||
else if( ( st & reader_lock_count_mask ) == 0 )
|
||||
{
|
||||
// lock free, try to take it
|
||||
|
||||
std::uint32_t newst = locked_exclusive_mask;
|
||||
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
|
||||
}
|
||||
else if( ( st & writer_pending_mask ) == 0 )
|
||||
{
|
||||
// writer pending bit already clear, nothing to do
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// clear writer pending bit
|
||||
|
||||
std::uint32_t newst = st & ~writer_pending_mask;
|
||||
if( state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ) ) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() noexcept
|
||||
{
|
||||
// pre: locked exclusive, not locked shared
|
||||
state_.store( 0, std::memory_order_release );
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace foa */
|
||||
} /* namespace detail */
|
||||
} /* namespace unordered */
|
||||
} /* namespace boost */
|
||||
|
||||
#endif // BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
|
||||
@@ -0,0 +1,540 @@
|
||||
/* Fast open-addressing hash table.
|
||||
*
|
||||
* Copyright 2022-2023 Joaquin M Lopez Munoz.
|
||||
* Copyright 2023 Christian Mazakas.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
#ifndef BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
|
||||
#define BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/config/workaround.hpp>
|
||||
#include <boost/unordered/detail/foa/core.hpp>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost{
|
||||
namespace unordered{
|
||||
namespace detail{
|
||||
namespace foa{
|
||||
|
||||
/* use plain integrals for group metadata storage */
|
||||
|
||||
template<typename Integral>
|
||||
struct plain_integral
|
||||
{
|
||||
operator Integral()const{return n;}
|
||||
void operator=(Integral m){n=m;}
|
||||
|
||||
#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000)
|
||||
void operator|=(Integral m){n=static_cast<Integral>(n|m);}
|
||||
void operator&=(Integral m){n=static_cast<Integral>(n&m);}
|
||||
#else
|
||||
void operator|=(Integral m){n|=m;}
|
||||
void operator&=(Integral m){n&=m;}
|
||||
#endif
|
||||
|
||||
Integral n;
|
||||
};
|
||||
|
||||
template<typename,typename,typename,typename>
|
||||
class table;
|
||||
|
||||
/* table_iterator keeps two pointers:
|
||||
*
|
||||
* - A pointer p to the element slot.
|
||||
* - A pointer pc to the n-th byte of the associated group metadata, where n
|
||||
* is the position of the element in the group.
|
||||
*
|
||||
* A simpler solution would have been to keep a pointer p to the element, a
|
||||
* pointer pg to the group, and the position n, but that would increase
|
||||
* sizeof(table_iterator) by 4/8 bytes. In order to make this compact
|
||||
* representation feasible, it is required that group objects are aligned
|
||||
* to their size, so that we can recover pg and n as
|
||||
*
|
||||
* - n = pc%sizeof(group)
|
||||
* - pg = pc-n
|
||||
*
|
||||
* (for explanatory purposes pg and pc are treated above as if they were memory
|
||||
* addresses rather than pointers).
|
||||
*
|
||||
* p = nullptr is conventionally used to mark end() iterators.
|
||||
*/
|
||||
|
||||
/* internal conversion from const_iterator to iterator */
|
||||
struct const_iterator_cast_tag{};
|
||||
|
||||
template<typename TypePolicy,typename Group,bool Const>
|
||||
class table_iterator
|
||||
{
|
||||
using type_policy=TypePolicy;
|
||||
using table_element_type=typename type_policy::element_type;
|
||||
using group_type=Group;
|
||||
static constexpr auto N=group_type::N;
|
||||
static constexpr auto regular_layout=group_type::regular_layout;
|
||||
|
||||
public:
|
||||
using difference_type=std::ptrdiff_t;
|
||||
using value_type=typename type_policy::value_type;
|
||||
using pointer=
|
||||
typename std::conditional<Const,value_type const*,value_type*>::type;
|
||||
using reference=
|
||||
typename std::conditional<Const,value_type const&,value_type&>::type;
|
||||
using iterator_category=std::forward_iterator_tag;
|
||||
using element_type=
|
||||
typename std::conditional<Const,value_type const,value_type>::type;
|
||||
|
||||
table_iterator()=default;
|
||||
template<bool Const2,typename std::enable_if<!Const2>::type* =nullptr>
|
||||
table_iterator(const table_iterator<TypePolicy,Group,Const2>& x):
|
||||
pc{x.pc},p{x.p}{}
|
||||
table_iterator(
|
||||
const_iterator_cast_tag, const table_iterator<TypePolicy,Group,true>& x):
|
||||
pc{x.pc},p{x.p}{}
|
||||
|
||||
inline reference operator*()const noexcept
|
||||
{return type_policy::value_from(*p);}
|
||||
inline pointer operator->()const noexcept
|
||||
{return std::addressof(type_policy::value_from(*p));}
|
||||
inline table_iterator& operator++()noexcept{increment();return *this;}
|
||||
inline table_iterator operator++(int)noexcept
|
||||
{auto x=*this;increment();return x;}
|
||||
friend inline bool operator==(
|
||||
const table_iterator& x,const table_iterator& y)
|
||||
{return x.p==y.p;}
|
||||
friend inline bool operator!=(
|
||||
const table_iterator& x,const table_iterator& y)
|
||||
{return !(x==y);}
|
||||
|
||||
private:
|
||||
template<typename,typename,bool> friend class table_iterator;
|
||||
template<typename,typename,typename,typename> friend class table;
|
||||
|
||||
table_iterator(Group* pg,std::size_t n,const table_element_type* p_):
|
||||
pc{reinterpret_cast<unsigned char*>(const_cast<group_type*>(pg))+n},
|
||||
p{const_cast<table_element_type*>(p_)}
|
||||
{}
|
||||
|
||||
inline void increment()noexcept
|
||||
{
|
||||
BOOST_ASSERT(p!=nullptr);
|
||||
increment(std::integral_constant<bool,regular_layout>{});
|
||||
}
|
||||
|
||||
inline void increment(std::true_type /* regular layout */)noexcept
|
||||
{
|
||||
for(;;){
|
||||
++p;
|
||||
if(reinterpret_cast<uintptr_t>(pc)%sizeof(group_type)==N-1){
|
||||
pc+=sizeof(group_type)-(N-1);
|
||||
break;
|
||||
}
|
||||
++pc;
|
||||
if(!group_type::is_occupied(pc))continue;
|
||||
if(BOOST_UNLIKELY(group_type::is_sentinel(pc)))p=nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
for(;;){
|
||||
int mask=reinterpret_cast<group_type*>(pc)->match_occupied();
|
||||
if(mask!=0){
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc)->is_sentinel(n))){
|
||||
p=nullptr;
|
||||
}
|
||||
else{
|
||||
pc+=n;
|
||||
p+=n;
|
||||
}
|
||||
return;
|
||||
}
|
||||
pc+=sizeof(group_type);
|
||||
p+=N;
|
||||
}
|
||||
}
|
||||
|
||||
inline void increment(std::false_type /* interleaved */)noexcept
|
||||
{
|
||||
std::size_t n0=reinterpret_cast<uintptr_t>(pc)%sizeof(group_type);
|
||||
pc-=n0;
|
||||
|
||||
int mask=(
|
||||
reinterpret_cast<group_type*>(pc)->match_occupied()>>(n0+1))<<(n0+1);
|
||||
if(!mask){
|
||||
do{
|
||||
pc+=sizeof(group_type);
|
||||
p+=N;
|
||||
}
|
||||
while((mask=reinterpret_cast<group_type*>(pc)->match_occupied())==0);
|
||||
}
|
||||
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc)->is_sentinel(n))){
|
||||
p=nullptr;
|
||||
}
|
||||
else{
|
||||
pc+=n;
|
||||
p-=n0;
|
||||
p+=n;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char *pc=nullptr;
|
||||
table_element_type *p=nullptr;
|
||||
};
|
||||
|
||||
/* foa::table interface departs in a number of ways from that of C++ unordered
|
||||
* associative containers because it's not for end-user consumption
|
||||
* (boost::unordered_[flat|node]_[map|set] wrappers complete it as
|
||||
* appropriate).
|
||||
*
|
||||
* The table supports two main modes of operation: flat and node-based. In the
|
||||
* flat case, buckets directly store elements. For node-based, buckets store
|
||||
* pointers to individually heap-allocated elements.
|
||||
*
|
||||
* For both flat and node-based:
|
||||
*
|
||||
* - begin() is not O(1).
|
||||
* - No bucket API.
|
||||
* - Load factor is fixed and can't be set by the user.
|
||||
*
|
||||
* For flat only:
|
||||
*
|
||||
* - value_type must be moveable.
|
||||
* - Pointer stability is not kept under rehashing.
|
||||
* - No extract API.
|
||||
*
|
||||
* try_emplace, erase and find support heterogeneous lookup by default,
|
||||
* that is, without checking for any ::is_transparent typedefs --the
|
||||
* checking is done by boost::unordered_[flat|node]_[map|set].
|
||||
*/
|
||||
|
||||
template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
|
||||
using table_core_impl=
|
||||
table_core<TypePolicy,group15<plain_integral>,table_arrays,
|
||||
std::size_t,Hash,Pred,Allocator>;
|
||||
|
||||
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4714) /* marked as __forceinline not inlined */
|
||||
#endif
|
||||
|
||||
template<typename TypePolicy,typename Hash,typename Pred,typename Allocator>
|
||||
class table:table_core_impl<TypePolicy,Hash,Pred,Allocator>
|
||||
{
|
||||
using super=table_core_impl<TypePolicy,Hash,Pred,Allocator>;
|
||||
using type_policy=typename super::type_policy;
|
||||
using group_type=typename super::group_type;
|
||||
using super::N;
|
||||
using prober=typename super::prober;
|
||||
using locator=typename super::locator;
|
||||
|
||||
public:
|
||||
using key_type=typename super::key_type;
|
||||
using init_type=typename super::init_type;
|
||||
using value_type=typename super::value_type;
|
||||
using element_type=typename super::element_type;
|
||||
|
||||
private:
|
||||
static constexpr bool has_mutable_iterator=
|
||||
!std::is_same<key_type,value_type>::value;
|
||||
|
||||
public:
|
||||
using hasher=typename super::hasher;
|
||||
using key_equal=typename super::key_equal;
|
||||
using allocator_type=typename super::allocator_type;
|
||||
using pointer=typename super::pointer;
|
||||
using const_pointer=typename super::const_pointer;
|
||||
using reference=typename super::reference;
|
||||
using const_reference=typename super::const_reference;
|
||||
using size_type=typename super::size_type;
|
||||
using difference_type=typename super::difference_type;
|
||||
using const_iterator=table_iterator<type_policy,group_type,true>;
|
||||
using iterator=typename std::conditional<
|
||||
has_mutable_iterator,
|
||||
table_iterator<type_policy,group_type,false>,
|
||||
const_iterator>::type;
|
||||
|
||||
table(
|
||||
std::size_t n=default_bucket_count,const Hash& h_=Hash(),
|
||||
const Pred& pred_=Pred(),const Allocator& al_=Allocator()):
|
||||
super{n,h_,pred_,al_}
|
||||
{}
|
||||
|
||||
table(const table& x)=default;
|
||||
table(table&& x)=default;
|
||||
table(const table& x,const Allocator& al_):super{x,al_}{}
|
||||
table(table&& x,const Allocator& al_):super{std::move(x),al_}{}
|
||||
~table()=default;
|
||||
|
||||
table& operator=(const table& x)=default;
|
||||
table& operator=(table&& x)=default;
|
||||
|
||||
using super::get_allocator;
|
||||
|
||||
iterator begin()noexcept
|
||||
{
|
||||
iterator it{this->arrays.groups,0,this->arrays.elements};
|
||||
if(this->arrays.elements&&
|
||||
!(this->arrays.groups[0].match_occupied()&0x1))++it;
|
||||
return it;
|
||||
}
|
||||
|
||||
const_iterator begin()const noexcept
|
||||
{return const_cast<table*>(this)->begin();}
|
||||
iterator end()noexcept{return {};}
|
||||
const_iterator end()const noexcept{return const_cast<table*>(this)->end();}
|
||||
const_iterator cbegin()const noexcept{return begin();}
|
||||
const_iterator cend()const noexcept{return end();}
|
||||
|
||||
using super::empty;
|
||||
using super::size;
|
||||
using super::max_size;
|
||||
|
||||
template<typename... Args>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)
|
||||
{
|
||||
auto x=alloc_make_insert_type<type_policy>(
|
||||
this->al(),std::forward<Args>(args)...);
|
||||
return emplace_impl(type_policy::move(x.value()));
|
||||
}
|
||||
|
||||
template<typename Key,typename... Args>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool> try_emplace(
|
||||
Key&& x,Args&&... args)
|
||||
{
|
||||
return emplace_impl(
|
||||
try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool>
|
||||
insert(const init_type& x){return emplace_impl(x);}
|
||||
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool>
|
||||
insert(init_type&& x){return emplace_impl(std::move(x));}
|
||||
|
||||
/* template<typename=void> tilts call ambiguities in favor of init_type */
|
||||
|
||||
template<typename=void>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool>
|
||||
insert(const value_type& x){return emplace_impl(x);}
|
||||
|
||||
template<typename=void>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool>
|
||||
insert(value_type&& x){return emplace_impl(std::move(x));}
|
||||
|
||||
template<typename T=element_type>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<
|
||||
!std::is_same<T,value_type>::value,
|
||||
std::pair<iterator,bool>
|
||||
>::type
|
||||
insert(element_type&& x){return emplace_impl(std::move(x));}
|
||||
|
||||
template<
|
||||
bool dependent_value=false,
|
||||
typename std::enable_if<
|
||||
has_mutable_iterator||dependent_value>::type* =nullptr
|
||||
>
|
||||
void erase(iterator pos)noexcept{return erase(const_iterator(pos));}
|
||||
|
||||
BOOST_FORCEINLINE
|
||||
void erase(const_iterator pos)noexcept
|
||||
{
|
||||
super::erase(pos.pc,pos.p);
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE
|
||||
auto erase(Key&& x) -> typename std::enable_if<
|
||||
!std::is_convertible<Key,iterator>::value&&
|
||||
!std::is_convertible<Key,const_iterator>::value, std::size_t>::type
|
||||
{
|
||||
auto it=find(x);
|
||||
if(it!=end()){
|
||||
erase(it);
|
||||
return 1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
void swap(table& x)
|
||||
noexcept(noexcept(std::declval<super&>().swap(std::declval<super&>())))
|
||||
{
|
||||
super::swap(x);
|
||||
}
|
||||
|
||||
using super::clear;
|
||||
|
||||
element_type extract(const_iterator pos)
|
||||
{
|
||||
BOOST_ASSERT(pos!=end());
|
||||
erase_on_exit e{*this,pos};
|
||||
(void)e;
|
||||
return std::move(*pos.p);
|
||||
}
|
||||
|
||||
// TODO: should we accept different allocator too?
|
||||
template<typename Hash2,typename Pred2>
|
||||
void merge(table<TypePolicy,Hash2,Pred2,Allocator>& x)
|
||||
{
|
||||
x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){
|
||||
erase_on_exit e{x,{pg,n,p}};
|
||||
if(!emplace_impl(type_policy::move(*p)).second)e.rollback();
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Hash2,typename Pred2>
|
||||
void merge(table<TypePolicy,Hash2,Pred2,Allocator>&& x){merge(x);}
|
||||
|
||||
using super::hash_function;
|
||||
using super::key_eq;
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE iterator find(const Key& x)
|
||||
{
|
||||
auto hash=this->hash_for(x);
|
||||
return find_impl(x,this->position_for(hash),hash);
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE const_iterator find(const Key& x)const
|
||||
{
|
||||
return const_cast<table*>(this)->find(x);
|
||||
}
|
||||
|
||||
using super::capacity;
|
||||
using super::load_factor;
|
||||
using super::max_load_factor;
|
||||
using super::max_load;
|
||||
using super::rehash;
|
||||
using super::reserve;
|
||||
|
||||
template<typename Predicate>
|
||||
friend std::size_t erase_if(table& x,Predicate& pr)
|
||||
{
|
||||
using value_reference=typename std::conditional<
|
||||
std::is_same<key_type,value_type>::value,
|
||||
const_reference,
|
||||
reference
|
||||
>::type;
|
||||
|
||||
std::size_t s=x.size();
|
||||
x.for_all_elements(
|
||||
[&](group_type* pg,unsigned int n,element_type* p){
|
||||
if(pr(const_cast<value_reference>(type_policy::value_from(*p)))){
|
||||
x.super::erase(pg,n,p);
|
||||
}
|
||||
});
|
||||
return std::size_t(s-x.size());
|
||||
}
|
||||
|
||||
private:
|
||||
struct erase_on_exit
|
||||
{
|
||||
erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{}
|
||||
~erase_on_exit(){if(!rollback_)x.erase(it);}
|
||||
|
||||
void rollback(){rollback_=true;}
|
||||
|
||||
table& x;
|
||||
const_iterator it;
|
||||
bool rollback_=false;
|
||||
};
|
||||
|
||||
static inline iterator make_iterator(const locator& l)noexcept
|
||||
{
|
||||
return {l.pg,l.n,l.p};
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4800)
|
||||
#endif
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE iterator find_impl(
|
||||
const Key& x,std::size_t pos0,std::size_t hash)const
|
||||
{
|
||||
prober pb(pos0);
|
||||
do{
|
||||
auto pos=pb.get();
|
||||
auto pg=this->arrays.groups+pos;
|
||||
auto mask=pg->match(hash);
|
||||
if(mask){
|
||||
BOOST_UNORDERED_ASSUME(this->arrays.elements!=nullptr);
|
||||
auto p=this->arrays.elements+pos*N;
|
||||
this->prefetch_elements(p);
|
||||
do{
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(BOOST_LIKELY(bool(this->pred()(x,this->key_from(p[n]))))){
|
||||
return {pg,n,p+n};
|
||||
}
|
||||
mask&=mask-1;
|
||||
}while(mask);
|
||||
}
|
||||
if(BOOST_LIKELY(pg->is_not_overflowed(hash))){
|
||||
return {}; /* end() */
|
||||
}
|
||||
}
|
||||
while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask)));
|
||||
return {}; /* end() */
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(pop) /* C4800 */
|
||||
#endif
|
||||
|
||||
template<typename... Args>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
|
||||
{
|
||||
const auto &k=this->key_from(std::forward<Args>(args)...);
|
||||
auto hash=this->hash_for(k);
|
||||
auto pos0=this->position_for(hash);
|
||||
auto it=find_impl(k,pos0,hash);
|
||||
|
||||
if(it!=end()){
|
||||
return {it,false};
|
||||
}
|
||||
if(BOOST_LIKELY(this->size_<this->ml)){
|
||||
return {
|
||||
make_iterator(
|
||||
this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...)),
|
||||
true
|
||||
};
|
||||
}
|
||||
else{
|
||||
return {
|
||||
make_iterator(
|
||||
this->unchecked_emplace_with_rehash(
|
||||
hash,std::forward<Args>(args)...)),
|
||||
true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(pop) /* C4714 */
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/foa/restore_wshadow.hpp>
|
||||
|
||||
} /* namespace foa */
|
||||
} /* namespace detail */
|
||||
} /* namespace unordered */
|
||||
} /* namespace boost */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright 2023 Joaquin M Lopez Munoz.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
#ifndef BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP
|
||||
#define BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP
|
||||
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace boost{
|
||||
namespace unordered{
|
||||
namespace detail{
|
||||
namespace foa{
|
||||
|
||||
template<typename Tuple>
|
||||
using tuple_rotate_right_return_type=mp11::mp_rotate_right_c<
|
||||
typename std::remove_cv<typename std::remove_reference<Tuple>::type>::type,
|
||||
1
|
||||
>;
|
||||
|
||||
template<std::size_t... Is,typename Tuple>
|
||||
tuple_rotate_right_return_type<Tuple>
|
||||
tuple_rotate_right_aux(mp11::index_sequence<Is...>,Tuple&& x)
|
||||
{
|
||||
return tuple_rotate_right_return_type<Tuple>{
|
||||
std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward<Tuple>(x))...};
|
||||
}
|
||||
|
||||
template<typename Tuple>
|
||||
tuple_rotate_right_return_type<Tuple> tuple_rotate_right(Tuple&& x)
|
||||
{
|
||||
using RawTuple=typename std::remove_cv<
|
||||
typename std::remove_reference<Tuple>::type>::type;
|
||||
|
||||
return tuple_rotate_right_aux(
|
||||
mp11::make_index_sequence<std::tuple_size<RawTuple>::value>{},
|
||||
std::forward<Tuple>(x));
|
||||
}
|
||||
|
||||
} /* namespace foa */
|
||||
} /* namespace detail */
|
||||
} /* namespace unordered */
|
||||
} /* namespace boost */
|
||||
|
||||
#endif
|
||||
@@ -61,4 +61,90 @@ namespace boost {
|
||||
}
|
||||
}
|
||||
|
||||
// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in
|
||||
// emplace (not including things like hints). Don't set it to a lower value, as
|
||||
// that might break something.
|
||||
|
||||
#if !defined BOOST_UNORDERED_EMPLACE_LIMIT
|
||||
#define BOOST_UNORDERED_EMPLACE_LIMIT 10
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Configuration
|
||||
//
|
||||
// Unless documented elsewhere these configuration macros should be considered
|
||||
// an implementation detail, I'll try not to break them, but you never know.
|
||||
|
||||
// Use Sun C++ workarounds
|
||||
// I'm not sure which versions of the compiler require these workarounds, so
|
||||
// I'm just using them of everything older than the current test compilers
|
||||
// (as of May 2017).
|
||||
|
||||
#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1)
|
||||
#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0)
|
||||
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1
|
||||
#else
|
||||
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_TUPLE_ARGS
|
||||
//
|
||||
// Maximum number of std::tuple members to support, or 0 if std::tuple
|
||||
// isn't avaiable. More are supported when full C++11 is used.
|
||||
|
||||
// Already defined, so do nothing
|
||||
#if defined(BOOST_UNORDERED_TUPLE_ARGS)
|
||||
|
||||
// Assume if we have C++11 tuple it's properly variadic,
|
||||
// and just use a max number of 10 arguments.
|
||||
#elif !defined(BOOST_NO_CXX11_HDR_TUPLE)
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 10
|
||||
|
||||
// Visual C++ has a decent enough tuple for piecewise construction,
|
||||
// so use that if available, using _VARIADIC_MAX for the maximum
|
||||
// number of parameters. Note that this comes after the check
|
||||
// for a full C++11 tuple.
|
||||
#elif defined(BOOST_MSVC)
|
||||
#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 0
|
||||
#elif defined(_VARIADIC_MAX)
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX
|
||||
#else
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 5
|
||||
#endif
|
||||
|
||||
// Assume that we don't have std::tuple
|
||||
#else
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 0
|
||||
#endif
|
||||
|
||||
#if BOOST_UNORDERED_TUPLE_ARGS
|
||||
#include <tuple>
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_CXX11_CONSTRUCTION
|
||||
//
|
||||
// Use C++11 construction, requires variadic arguments, good construct support
|
||||
// in allocator_traits and piecewise construction of std::pair
|
||||
// Otherwise allocators aren't used for construction/destruction
|
||||
|
||||
#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \
|
||||
!defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS
|
||||
#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU
|
||||
// Sun C++ std::pair piecewise construction doesn't seem to be exception safe.
|
||||
// (At least for Sun C++ 12.5 using libstdc++).
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0)
|
||||
// Piecewise construction in GCC 4.6 doesn't work for uncopyable types.
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#elif !defined(BOOST_NO_CXX11_ALLOCATOR)
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION)
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -60,92 +60,6 @@
|
||||
#include <type_traits>
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Configuration
|
||||
//
|
||||
// Unless documented elsewhere these configuration macros should be considered
|
||||
// an implementation detail, I'll try not to break them, but you never know.
|
||||
|
||||
// Use Sun C++ workarounds
|
||||
// I'm not sure which versions of the compiler require these workarounds, so
|
||||
// I'm just using them of everything older than the current test compilers
|
||||
// (as of May 2017).
|
||||
|
||||
#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1)
|
||||
#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0)
|
||||
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1
|
||||
#else
|
||||
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in
|
||||
// emplace (not including things like hints). Don't set it to a lower value, as
|
||||
// that might break something.
|
||||
|
||||
#if !defined BOOST_UNORDERED_EMPLACE_LIMIT
|
||||
#define BOOST_UNORDERED_EMPLACE_LIMIT 10
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_TUPLE_ARGS
|
||||
//
|
||||
// Maximum number of std::tuple members to support, or 0 if std::tuple
|
||||
// isn't avaiable. More are supported when full C++11 is used.
|
||||
|
||||
// Already defined, so do nothing
|
||||
#if defined(BOOST_UNORDERED_TUPLE_ARGS)
|
||||
|
||||
// Assume if we have C++11 tuple it's properly variadic,
|
||||
// and just use a max number of 10 arguments.
|
||||
#elif !defined(BOOST_NO_CXX11_HDR_TUPLE)
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 10
|
||||
|
||||
// Visual C++ has a decent enough tuple for piecewise construction,
|
||||
// so use that if available, using _VARIADIC_MAX for the maximum
|
||||
// number of parameters. Note that this comes after the check
|
||||
// for a full C++11 tuple.
|
||||
#elif defined(BOOST_MSVC)
|
||||
#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 0
|
||||
#elif defined(_VARIADIC_MAX)
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX
|
||||
#else
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 5
|
||||
#endif
|
||||
|
||||
// Assume that we don't have std::tuple
|
||||
#else
|
||||
#define BOOST_UNORDERED_TUPLE_ARGS 0
|
||||
#endif
|
||||
|
||||
#if BOOST_UNORDERED_TUPLE_ARGS
|
||||
#include <tuple>
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_CXX11_CONSTRUCTION
|
||||
//
|
||||
// Use C++11 construction, requires variadic arguments, good construct support
|
||||
// in allocator_traits and piecewise construction of std::pair
|
||||
// Otherwise allocators aren't used for construction/destruction
|
||||
|
||||
#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \
|
||||
!defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS
|
||||
#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU
|
||||
// Sun C++ std::pair piecewise construction doesn't seem to be exception safe.
|
||||
// (At least for Sun C++ 12.5 using libstdc++).
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0)
|
||||
// Piecewise construction in GCC 4.6 doesn't work for uncopyable types.
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#elif !defined(BOOST_NO_CXX11_ALLOCATOR)
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION)
|
||||
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
|
||||
#endif
|
||||
|
||||
#if BOOST_UNORDERED_CXX11_CONSTRUCTION
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED
|
||||
#define BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED
|
||||
|
||||
// Copyright 2023 Peter Dimov
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/config/pragma_message.hpp>
|
||||
|
||||
#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || \
|
||||
defined(BOOST_NO_CXX11_RVALUE_REFERENCES) || \
|
||||
defined(BOOST_NO_CXX11_DECLTYPE) || \
|
||||
defined(BOOST_NO_CXX11_CONSTEXPR) || \
|
||||
defined(BOOST_NO_CXX11_NOEXCEPT)
|
||||
|
||||
BOOST_PRAGMA_MESSAGE("C++03 support is deprecated in Boost.Unordered 1.82 and will be removed in Boost.Unordered 1.84.")
|
||||
|
||||
#endif
|
||||
|
||||
#endif // #ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED
|
||||
@@ -10,7 +10,7 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/foa.hpp>
|
||||
#include <boost/unordered/detail/foa/table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
#include <boost/unordered/unordered_flat_map_fwd.hpp>
|
||||
|
||||
@@ -53,6 +53,11 @@ namespace boost {
|
||||
return kv.first;
|
||||
}
|
||||
|
||||
static moved_type move(init_type& x)
|
||||
{
|
||||
return {std::move(x.first), std::move(x.second)};
|
||||
}
|
||||
|
||||
static moved_type move(element_type& x)
|
||||
{
|
||||
// TODO: we probably need to launder here
|
||||
@@ -60,19 +65,24 @@ namespace boost {
|
||||
std::move(const_cast<raw_mapped_type&>(x.second))};
|
||||
}
|
||||
|
||||
template <class A>
|
||||
static void construct(A& al, element_type* p, moved_type&& x)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::move(x));
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, element_type* p, Args&&... args)
|
||||
static void construct(A& al, init_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, element_type* p) noexcept
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, value_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, init_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, value_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/foa.hpp>
|
||||
#include <boost/unordered/detail/foa/table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
#include <boost/unordered/unordered_flat_set_fwd.hpp>
|
||||
|
||||
@@ -45,25 +45,13 @@ namespace boost {
|
||||
|
||||
static element_type&& move(element_type& x) { return std::move(x); }
|
||||
|
||||
template <class A>
|
||||
static void construct(A& al, element_type* p, element_type const& copy)
|
||||
{
|
||||
boost::allocator_construct(al, p, copy);
|
||||
}
|
||||
|
||||
template <class A>
|
||||
static void construct(A& al, element_type* p, Key&& x)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::move(x));
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, element_type* p, Args&&... args)
|
||||
static void construct(A& al, value_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, element_type* p) noexcept
|
||||
template <class A> static void destroy(A& al, value_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/requires_cxx11.hpp>
|
||||
#include <boost/core/explicit_operator_bool.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/move/move.hpp>
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/foa.hpp>
|
||||
#include <boost/unordered/detail/foa/element_type.hpp>
|
||||
#include <boost/unordered/detail/foa/node_handle.hpp>
|
||||
#include <boost/unordered/detail/foa/table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
#include <boost/unordered/unordered_node_map_fwd.hpp>
|
||||
|
||||
@@ -45,23 +46,7 @@ namespace boost {
|
||||
using value_type = std::pair<Key const, T>;
|
||||
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
|
||||
|
||||
struct element_type
|
||||
{
|
||||
value_type* p;
|
||||
|
||||
/*
|
||||
* we use a deleted copy constructor here so the type is no longer
|
||||
* trivially copy-constructible which inhibits our memcpy
|
||||
* optimizations when copying the tables
|
||||
*/
|
||||
element_type() = default;
|
||||
element_type(element_type const&) = delete;
|
||||
element_type(element_type&& rhs) noexcept
|
||||
{
|
||||
p = rhs.p;
|
||||
rhs.p = nullptr;
|
||||
}
|
||||
};
|
||||
using element_type=foa::element_type<value_type>;
|
||||
|
||||
static value_type& value_from(element_type const& x) { return *(x.p); }
|
||||
|
||||
@@ -77,6 +62,11 @@ namespace boost {
|
||||
}
|
||||
|
||||
static element_type&& move(element_type& x) { return std::move(x); }
|
||||
static moved_type move(init_type& x)
|
||||
{
|
||||
return {std::move(x.first), std::move(x.second)};
|
||||
}
|
||||
|
||||
static moved_type move(value_type& x)
|
||||
{
|
||||
return {std::move(const_cast<raw_key_type&>(x.first)),
|
||||
@@ -96,6 +86,18 @@ namespace boost {
|
||||
construct(al, p, *copy.p);
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, init_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, value_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, element_type* p, Args&&... args)
|
||||
{
|
||||
@@ -106,10 +108,11 @@ namespace boost {
|
||||
}
|
||||
BOOST_CATCH(...)
|
||||
{
|
||||
boost::allocator_deallocate(al,
|
||||
boost::pointer_traits<
|
||||
typename boost::allocator_pointer<A>::type>::pointer_to(*p->p),
|
||||
1);
|
||||
using pointer_type = typename boost::allocator_pointer<A>::type;
|
||||
using pointer_traits = boost::pointer_traits<pointer_type>;
|
||||
|
||||
boost::allocator_deallocate(
|
||||
al, pointer_traits::pointer_to(*(p->p)), 1);
|
||||
BOOST_RETHROW
|
||||
}
|
||||
BOOST_CATCH_END
|
||||
@@ -118,27 +121,32 @@ namespace boost {
|
||||
template <class A> static void destroy(A& al, value_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
boost::allocator_deallocate(al,
|
||||
boost::pointer_traits<
|
||||
typename boost::allocator_pointer<A>::type>::pointer_to(*p),
|
||||
1);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, init_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, element_type* p) noexcept
|
||||
{
|
||||
if (p->p) {
|
||||
destroy(al,p->p);
|
||||
using pointer_type = typename boost::allocator_pointer<A>::type;
|
||||
using pointer_traits = boost::pointer_traits<pointer_type>;
|
||||
|
||||
destroy(al, p->p);
|
||||
boost::allocator_deallocate(
|
||||
al, pointer_traits::pointer_to(*(p->p)), 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class NodeMapTypes, class Allocator>
|
||||
template <class TypePolicy, class Allocator>
|
||||
struct node_map_handle
|
||||
: public detail::foa::node_handle_base<NodeMapTypes, Allocator>
|
||||
: public detail::foa::node_handle_base<TypePolicy, Allocator>
|
||||
{
|
||||
private:
|
||||
using base_type =
|
||||
detail::foa::node_handle_base<NodeMapTypes, Allocator>;
|
||||
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
|
||||
|
||||
using typename base_type::type_policy;
|
||||
|
||||
@@ -146,8 +154,8 @@ namespace boost {
|
||||
friend class boost::unordered::unordered_node_map;
|
||||
|
||||
public:
|
||||
using key_type = typename NodeMapTypes::key_type;
|
||||
using mapped_type = typename NodeMapTypes::mapped_type;
|
||||
using key_type = typename TypePolicy::key_type;
|
||||
using mapped_type = typename TypePolicy::mapped_type;
|
||||
|
||||
constexpr node_map_handle() noexcept = default;
|
||||
node_map_handle(node_map_handle&& nh) noexcept = default;
|
||||
@@ -157,13 +165,13 @@ namespace boost {
|
||||
key_type& key() const
|
||||
{
|
||||
BOOST_ASSERT(!this->empty());
|
||||
return const_cast<key_type&>(this->element().first);
|
||||
return const_cast<key_type&>(this->data().first);
|
||||
}
|
||||
|
||||
mapped_type& mapped() const
|
||||
{
|
||||
BOOST_ASSERT(!this->empty());
|
||||
return const_cast<mapped_type&>(this->element().second);
|
||||
return const_cast<mapped_type&>(this->data().second);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
@@ -402,12 +410,9 @@ namespace boost {
|
||||
|
||||
BOOST_ASSERT(get_allocator() == nh.get_allocator());
|
||||
|
||||
typename map_types::element_type x;
|
||||
x.p=std::addressof(nh.element());
|
||||
|
||||
auto itp = table_.insert(std::move(x));
|
||||
auto itp = table_.insert(std::move(nh.element()));
|
||||
if (itp.second) {
|
||||
nh.clear();
|
||||
nh.reset();
|
||||
return {itp.first, true, node_type{}};
|
||||
} else {
|
||||
return {itp.first, false, std::move(nh)};
|
||||
@@ -422,8 +427,13 @@ namespace boost {
|
||||
|
||||
BOOST_ASSERT(get_allocator() == nh.get_allocator());
|
||||
|
||||
auto itp = table_.insert(map_types::move(nh.element()));
|
||||
return itp.first;
|
||||
auto itp = table_.insert(std::move(nh.element()));
|
||||
if (itp.second) {
|
||||
nh.reset();
|
||||
return itp.first;
|
||||
} else {
|
||||
return itp.first;
|
||||
}
|
||||
}
|
||||
|
||||
template <class M>
|
||||
@@ -594,7 +604,7 @@ namespace boost {
|
||||
node_type extract(key_type const& key)
|
||||
{
|
||||
auto pos = find(key);
|
||||
return pos!=end()?extract(pos):node_type();
|
||||
return pos != end() ? extract(pos) : node_type();
|
||||
}
|
||||
|
||||
template <class K>
|
||||
@@ -605,7 +615,7 @@ namespace boost {
|
||||
extract(K const& key)
|
||||
{
|
||||
auto pos = find(key);
|
||||
return pos!=end()?extract(pos):node_type();
|
||||
return pos != end() ? extract(pos) : node_type();
|
||||
}
|
||||
|
||||
template <class H2, class P2>
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/foa.hpp>
|
||||
#include <boost/unordered/detail/foa/element_type.hpp>
|
||||
#include <boost/unordered/detail/foa/node_handle.hpp>
|
||||
#include <boost/unordered/detail/foa/table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
#include <boost/unordered/unordered_node_set_fwd.hpp>
|
||||
|
||||
@@ -41,23 +42,7 @@ namespace boost {
|
||||
|
||||
static Key const& extract(value_type const& key) { return key; }
|
||||
|
||||
struct element_type
|
||||
{
|
||||
value_type* p;
|
||||
|
||||
/*
|
||||
* we use a deleted copy constructor here so the type is no longer
|
||||
* trivially copy-constructible which inhibits our memcpy
|
||||
* optimizations when copying the tables
|
||||
*/
|
||||
element_type() = default;
|
||||
element_type(element_type const&) = delete;
|
||||
element_type(element_type&& rhs) noexcept
|
||||
{
|
||||
p = rhs.p;
|
||||
rhs.p = nullptr;
|
||||
}
|
||||
};
|
||||
using element_type=foa::element_type<value_type>;
|
||||
|
||||
static value_type& value_from(element_type const& x) { return *x.p; }
|
||||
static Key const& extract(element_type const& k) { return *k.p; }
|
||||
@@ -78,6 +63,12 @@ namespace boost {
|
||||
x.p = nullptr;
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, value_type* p, Args&&... args)
|
||||
{
|
||||
boost::allocator_construct(al, p, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class A, class... Args>
|
||||
static void construct(A& al, element_type* p, Args&&... args)
|
||||
{
|
||||
@@ -100,27 +91,26 @@ namespace boost {
|
||||
template <class A> static void destroy(A& al, value_type* p) noexcept
|
||||
{
|
||||
boost::allocator_destroy(al, p);
|
||||
boost::allocator_deallocate(al,
|
||||
boost::pointer_traits<
|
||||
typename boost::allocator_pointer<A>::type>::pointer_to(*p),
|
||||
1);
|
||||
}
|
||||
|
||||
template <class A> static void destroy(A& al, element_type* p) noexcept
|
||||
{
|
||||
if (p->p) {
|
||||
destroy(al, p->p);
|
||||
boost::allocator_deallocate(al,
|
||||
boost::pointer_traits<typename boost::allocator_pointer<
|
||||
A>::type>::pointer_to(*(p->p)),
|
||||
1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class NodeSetTypes, class Allocator>
|
||||
template <class TypePolicy, class Allocator>
|
||||
struct node_set_handle
|
||||
: public detail::foa::node_handle_base<NodeSetTypes, Allocator>
|
||||
: public detail::foa::node_handle_base<TypePolicy, Allocator>
|
||||
{
|
||||
private:
|
||||
using base_type =
|
||||
detail::foa::node_handle_base<NodeSetTypes, Allocator>;
|
||||
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
|
||||
|
||||
using typename base_type::type_policy;
|
||||
|
||||
@@ -128,7 +118,7 @@ namespace boost {
|
||||
friend class boost::unordered::unordered_node_set;
|
||||
|
||||
public:
|
||||
using value_type = typename NodeSetTypes::value_type;
|
||||
using value_type = typename TypePolicy::value_type;
|
||||
|
||||
constexpr node_set_handle() noexcept = default;
|
||||
node_set_handle(node_set_handle&& nh) noexcept = default;
|
||||
@@ -137,7 +127,7 @@ namespace boost {
|
||||
value_type& value() const
|
||||
{
|
||||
BOOST_ASSERT(!this->empty());
|
||||
return const_cast<value_type&>(this->element());
|
||||
return const_cast<value_type&>(this->data());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
@@ -390,12 +380,9 @@ namespace boost {
|
||||
|
||||
BOOST_ASSERT(get_allocator() == nh.get_allocator());
|
||||
|
||||
typename set_types::element_type x;
|
||||
x.p=std::addressof(nh.element());
|
||||
|
||||
auto itp = table_.insert(std::move(x));
|
||||
auto itp = table_.insert(std::move(nh.element()));
|
||||
if (itp.second) {
|
||||
nh.clear();
|
||||
nh.reset();
|
||||
return {itp.first, true, node_type{}};
|
||||
} else {
|
||||
return {itp.first, false, std::move(nh)};
|
||||
@@ -410,8 +397,13 @@ namespace boost {
|
||||
|
||||
BOOST_ASSERT(get_allocator() == nh.get_allocator());
|
||||
|
||||
auto itp = table_.insert(set_types::move(nh.element()));
|
||||
return itp.first;
|
||||
auto itp = table_.insert(std::move(nh.element()));
|
||||
if (itp.second) {
|
||||
nh.reset();
|
||||
return itp.first;
|
||||
} else {
|
||||
return itp.first;
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
@@ -470,7 +462,7 @@ namespace boost {
|
||||
node_type extract(key_type const& key)
|
||||
{
|
||||
auto pos = find(key);
|
||||
return pos!=end()?extract(pos):node_type();
|
||||
return pos != end() ? extract(pos) : node_type();
|
||||
}
|
||||
|
||||
template <class K>
|
||||
@@ -481,7 +473,7 @@ namespace boost {
|
||||
extract(K const& key)
|
||||
{
|
||||
auto pos = find(key);
|
||||
return pos!=end()?extract(pos):node_type();
|
||||
return pos != end() ? extract(pos) : node_type();
|
||||
}
|
||||
|
||||
template <class H2, class P2>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/unordered/detail/requires_cxx11.hpp>
|
||||
#include <boost/core/explicit_operator_bool.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/move/move.hpp>
|
||||
|
||||
@@ -104,49 +104,51 @@ import ../../config/checks/config : requires ;
|
||||
|
||||
CPP11 = [ requires cxx11_constexpr cxx11_noexcept cxx11_decltype cxx11_alignas ] ;
|
||||
|
||||
rule build_foa ( name )
|
||||
local FOA_TESTS =
|
||||
fwd_set_test
|
||||
fwd_map_test
|
||||
compile_set
|
||||
compile_map
|
||||
noexcept_tests
|
||||
incomplete_test
|
||||
simple_tests
|
||||
equivalent_keys_tests
|
||||
constructor_tests
|
||||
copy_tests
|
||||
move_tests
|
||||
post_move_tests
|
||||
assign_tests
|
||||
insert_tests
|
||||
insert_hint_tests
|
||||
emplace_tests
|
||||
erase_tests
|
||||
merge_tests
|
||||
find_tests
|
||||
at_tests
|
||||
load_factor_tests
|
||||
rehash_tests
|
||||
equality_tests
|
||||
swap_tests
|
||||
transparent_tests
|
||||
reserve_tests
|
||||
contains_tests
|
||||
erase_if
|
||||
scary_tests
|
||||
init_type_insert_tests
|
||||
max_load_tests
|
||||
extract_tests
|
||||
node_handle_tests
|
||||
uses_allocator
|
||||
;
|
||||
|
||||
for local test in $(FOA_TESTS)
|
||||
{
|
||||
run unordered/$(name).cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_$(name) ;
|
||||
run unordered/$(test).cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_$(test) ;
|
||||
}
|
||||
|
||||
build_foa fwd_set_test ;
|
||||
build_foa fwd_map_test ;
|
||||
build_foa compile_set ;
|
||||
build_foa compile_map ;
|
||||
build_foa noexcept_tests ;
|
||||
run unordered/link_test_1.cpp unordered/link_test_2.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_link_test ;
|
||||
build_foa incomplete_test ;
|
||||
build_foa simple_tests ;
|
||||
build_foa equivalent_keys_tests ;
|
||||
build_foa constructor_tests ;
|
||||
build_foa copy_tests ;
|
||||
build_foa move_tests ;
|
||||
build_foa post_move_tests ;
|
||||
build_foa assign_tests ;
|
||||
build_foa insert_tests ;
|
||||
build_foa insert_hint_tests ;
|
||||
build_foa emplace_tests ;
|
||||
build_foa erase_tests ;
|
||||
build_foa merge_tests ;
|
||||
build_foa find_tests ;
|
||||
build_foa at_tests ;
|
||||
build_foa load_factor_tests ;
|
||||
build_foa rehash_tests ;
|
||||
build_foa equality_tests ;
|
||||
build_foa swap_tests ;
|
||||
run unordered/scoped_allocator.cpp : : : $(CPP11) <toolset>msvc-14.0:<build>no <define>BOOST_UNORDERED_FOA_TESTS : foa_scoped_allocator ;
|
||||
build_foa transparent_tests ;
|
||||
build_foa reserve_tests ;
|
||||
build_foa contains_tests ;
|
||||
build_foa erase_if ;
|
||||
build_foa scary_tests ;
|
||||
build_foa init_type_insert_tests ;
|
||||
build_foa max_load_tests ;
|
||||
build_foa extract_tests ;
|
||||
build_foa node_handle_tests ;
|
||||
|
||||
run unordered/hash_is_avalanching_test.cpp ;
|
||||
|
||||
run exception/constructor_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_constructor_exception_tests ;
|
||||
run exception/copy_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_copy_exception_tests ;
|
||||
run exception/assign_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_assign_exception_tests ;
|
||||
@@ -156,3 +158,39 @@ run exception/erase_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORD
|
||||
run exception/rehash_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_rehash_exception_tests ;
|
||||
run exception/swap_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_swap_exception_tests ;
|
||||
run exception/merge_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_merge_exception_tests ;
|
||||
|
||||
alias foa_tests :
|
||||
foa_$(FOA_TESTS)
|
||||
foa_link_test
|
||||
foa_scoped_allocator
|
||||
hash_is_avalanching_test
|
||||
foa_constructor_exception_tests
|
||||
foa_copy_exception_tests
|
||||
foa_assign_exception_tests
|
||||
foa_move_assign_exception_tests
|
||||
foa_insert_exception_tests
|
||||
foa_erase_exception_tests
|
||||
foa_rehash_exception_tests
|
||||
foa_swap_exception_tests
|
||||
foa_merge_exception_tests
|
||||
;
|
||||
|
||||
local CFOA_TESTS =
|
||||
insert_tests
|
||||
erase_tests
|
||||
try_emplace_tests
|
||||
emplace_tests
|
||||
visit_tests
|
||||
constructor_tests
|
||||
assign_tests
|
||||
;
|
||||
|
||||
for local test in $(CFOA_TESTS)
|
||||
{
|
||||
run cfoa/$(test).cpp
|
||||
: requirements $(CPP11)
|
||||
: target-name cfoa_$(test)
|
||||
;
|
||||
}
|
||||
|
||||
alias cfoa_tests : cfoa_$(CFOA_TESTS) ;
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed{2762556623};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
namespace {
|
||||
template <class G> void copy_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
// to test:
|
||||
// self-assign
|
||||
// propagation
|
||||
//
|
||||
|
||||
// lhs empty, rhs empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
map_type y;
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
}
|
||||
|
||||
// lhs non-empty, rhs empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_size = y.size();
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs empty, rhs non-empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size()));
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs non-empty, rhs non-empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
auto const old_size = y.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size()));
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
copy_assign,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,814 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed(4122023);
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
template <class T> struct soccc_allocator
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
using value_type = T;
|
||||
|
||||
soccc_allocator() = default;
|
||||
soccc_allocator(soccc_allocator const&) = default;
|
||||
soccc_allocator(soccc_allocator&&) = default;
|
||||
|
||||
soccc_allocator(int const x) : x_{x} {}
|
||||
|
||||
template <class U> soccc_allocator(soccc_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
soccc_allocator select_on_container_copy_construction() const
|
||||
{
|
||||
return {x_ + 1};
|
||||
}
|
||||
|
||||
bool operator==(soccc_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(soccc_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
};
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
UNORDERED_AUTO_TEST (default_constructor) {
|
||||
boost::unordered::concurrent_flat_map<raii, raii> x;
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) {
|
||||
raii::reset_counts();
|
||||
{
|
||||
map_type x(0);
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1));
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2));
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type{});
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
}
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (soccc) {
|
||||
raii::reset_counts();
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii, hasher, key_equal,
|
||||
soccc_allocator<std::pair<raii const, raii> > >
|
||||
x;
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii, hasher, key_equal,
|
||||
soccc_allocator<std::pair<raii const, raii> > >
|
||||
y(x);
|
||||
|
||||
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
|
||||
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
|
||||
BOOST_TEST(y.get_allocator() != x.get_allocator());
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <class G> void from_iterator_range(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end());
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0);
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1));
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2));
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type{});
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type{});
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void copy_constructor(G gen, test::random_generator rg)
|
||||
{
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type{});
|
||||
map_type y(x);
|
||||
|
||||
BOOST_TEST_EQ(y.size(), x.size());
|
||||
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
|
||||
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
}
|
||||
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type{});
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
map_type y(x);
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(y.size(), x.size());
|
||||
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
|
||||
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
});
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
allocator_type a;
|
||||
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), a);
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, a](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
map_type y(x, a);
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(y.size(), x.size());
|
||||
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
|
||||
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
});
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void copy_constructor_with_insertion(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type{});
|
||||
|
||||
auto f = [&x, &values, &m, &cv, &ready] {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
|
||||
for (auto const& val : values) {
|
||||
x.insert(val);
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(f);
|
||||
std::thread t2(f);
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, &values, rg, &m, &cv, &ready](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&] { return ready; });
|
||||
}
|
||||
|
||||
map_type y(x);
|
||||
|
||||
BOOST_TEST_LE(y.size(), values.size());
|
||||
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
|
||||
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
|
||||
x.visit_all([&reference_map, rg](
|
||||
typename map_type::value_type const& val) {
|
||||
BOOST_TEST(reference_map.contains(val.first));
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(val.second, reference_map.find(val.first)->second);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void move_constructor(G gen, test::random_generator rg)
|
||||
{
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type{});
|
||||
auto const old_size = x.size();
|
||||
|
||||
map_type y(std::move(x));
|
||||
|
||||
BOOST_TEST_EQ(y.size(), old_size);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
}
|
||||
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type{});
|
||||
|
||||
std::atomic_uint num_transfers{0};
|
||||
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, &num_transfers](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
|
||||
auto const old_size = x.size();
|
||||
map_type y(std::move(x));
|
||||
|
||||
if (!y.empty()) {
|
||||
++num_transfers;
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(y.size(), old_size);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.size(), 0u);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
|
||||
// allocator-aware move constructor, unequal allocators
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type{1});
|
||||
|
||||
std::atomic_uint num_transfers{0};
|
||||
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
auto const old_size = x.size();
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, &num_transfers, old_size](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
|
||||
auto a = allocator_type{2};
|
||||
BOOST_TEST(a != x.get_allocator());
|
||||
|
||||
map_type y(std::move(x), a);
|
||||
|
||||
if (!y.empty()) {
|
||||
++num_transfers;
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(y.size(), old_size);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.size(), 0u);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(y.get_allocator() != x.get_allocator());
|
||||
BOOST_TEST(y.get_allocator() == a);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc + (2 * old_size));
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
|
||||
// allocator-aware move constructor, equal allocators
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type{1});
|
||||
|
||||
std::atomic_uint num_transfers{0};
|
||||
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
auto const old_size = x.size();
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, &num_transfers, old_size](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
|
||||
auto a = allocator_type{1};
|
||||
BOOST_TEST(a == x.get_allocator());
|
||||
|
||||
map_type y(std::move(x), a);
|
||||
|
||||
if (!y.empty()) {
|
||||
++num_transfers;
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(y.size(), old_size);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.size(), 0u);
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(y.get_allocator() == x.get_allocator());
|
||||
BOOST_TEST(y.get_allocator() == a);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void move_constructor_with_insertion(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type{});
|
||||
|
||||
std::atomic_uint num_transfers{0};
|
||||
|
||||
std::thread t1([&x, &values] {
|
||||
for (auto const& val : values) {
|
||||
x.insert(val);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread t2([&x, &m, &cv, &ready] {
|
||||
while (x.empty()) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map, &num_transfers, rg, &m, &ready, &cv](
|
||||
boost::span<span_value_type<decltype(values)> > s) {
|
||||
(void)s;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&] { return ready; });
|
||||
}
|
||||
|
||||
map_type y(std::move(x));
|
||||
|
||||
if (!y.empty()) {
|
||||
++num_transfers;
|
||||
y.cvisit_all([&reference_map, rg](map_value_type const& val) {
|
||||
BOOST_TEST(reference_map.contains(val.first));
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(
|
||||
val.second, reference_map.find(val.first)->second);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BOOST_TEST_GE(num_transfers, 1u);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void iterator_range_with_allocator(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
allocator_type a;
|
||||
map_type x(values.begin(), values.end(), a);
|
||||
|
||||
BOOST_TEST_GT(x.size(), 0u);
|
||||
BOOST_TEST_LE(x.size(), values.size());
|
||||
if (rg == sequential) {
|
||||
BOOST_TEST_EQ(x.size(), values.size());
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(x.get_allocator() == a);
|
||||
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (explicit_allocator) {
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
allocator_type a;
|
||||
map_type x(a);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
|
||||
BOOST_TEST(x.get_allocator() == a);
|
||||
}
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (initializer_list_with_all_params) {
|
||||
std::initializer_list<map_value_type> ilist{
|
||||
map_value_type{raii{0}, raii{0}},
|
||||
map_value_type{raii{1}, raii{1}},
|
||||
map_value_type{raii{2}, raii{2}},
|
||||
map_value_type{raii{3}, raii{3}},
|
||||
map_value_type{raii{4}, raii{4}},
|
||||
map_value_type{raii{5}, raii{5}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{7}, raii{7}},
|
||||
map_value_type{raii{8}, raii{8}},
|
||||
map_value_type{raii{9}, raii{9}},
|
||||
map_value_type{raii{10}, raii{10}},
|
||||
map_value_type{raii{9}, raii{9}},
|
||||
map_value_type{raii{8}, raii{8}},
|
||||
map_value_type{raii{7}, raii{7}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{5}, raii{5}},
|
||||
map_value_type{raii{4}, raii{4}},
|
||||
map_value_type{raii{3}, raii{3}},
|
||||
map_value_type{raii{2}, raii{2}},
|
||||
map_value_type{raii{1}, raii{1}},
|
||||
map_value_type{raii{0}, raii{0}},
|
||||
};
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(ilist, 0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 11u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(ilist, allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 11u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(ilist, 0, allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 11u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(ilist, 0, hasher(1), allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 11u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (bucket_count_and_allocator) {
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(0, allocator_type(3));
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
}
|
||||
|
||||
{
|
||||
map_type x(4096, allocator_type(3));
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
}
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (bucket_count_with_hasher_and_allocator) {
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), allocator_type(3));
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
}
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void iterator_range_with_bucket_count_and_allocator(
|
||||
G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
allocator_type a(3);
|
||||
map_type x(values.begin(), values.end(), 0, a);
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == a);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void iterator_range_with_bucket_count_hasher_and_allocator(
|
||||
G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
allocator_type a(3);
|
||||
hasher hf(1);
|
||||
map_type x(values.begin(), values.end(), 0, hf, a);
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hf);
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == a);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
from_iterator_range,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
copy_constructor,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
copy_constructor_with_insertion,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
move_constructor,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
move_constructor_with_insertion,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
iterator_range_with_allocator,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
iterator_range_with_bucket_count_and_allocator,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
iterator_range_with_bucket_count_hasher_and_allocator,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(335740237);
|
||||
|
||||
struct lvalue_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto const& r : s) {
|
||||
bool b = x.emplace(r.first.x_, r.second.x_);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_emplacer;
|
||||
|
||||
struct norehash_lvalue_emplacer_type : public lvalue_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
lvalue_emplacer_type::operator()(values, x);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
|
||||
}
|
||||
} norehash_lvalue_emplacer;
|
||||
|
||||
struct lvalue_emplace_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.emplace_or_cvisit(
|
||||
r.first.x_, r.second.x_,
|
||||
[&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} lvalue_emplace_or_cvisit;
|
||||
|
||||
struct lvalue_emplace_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.emplace_or_visit(
|
||||
r.first.x_, r.second.x_,
|
||||
[&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} lvalue_emplace_or_visit;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void emplace(X*, G gen, F emplacer, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
|
||||
emplacer(values, x);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
|
||||
UNORDERED_TEST(
|
||||
emplace,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_emplacer)(norehash_lvalue_emplacer)
|
||||
(lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,368 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(3292023);
|
||||
|
||||
struct lvalue_eraser_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor + 2 * x.size());
|
||||
|
||||
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
|
||||
for (auto const& k : values) {
|
||||
auto count = x.erase(k.first);
|
||||
num_erased += count;
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(num_erased, old_size);
|
||||
}
|
||||
} lvalue_eraser;
|
||||
|
||||
struct transp_lvalue_eraser_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor + 2 * x.size());
|
||||
|
||||
thread_runner(values, [&num_erased, &x](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
auto count = x.erase(k.first.x_);
|
||||
num_erased += count;
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), 0u);
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST_EQ(num_erased, old_size);
|
||||
}
|
||||
} transp_lvalue_eraser;
|
||||
|
||||
struct lvalue_eraser_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
auto count = x.erase_if(k.first,
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} lvalue_eraser_if;
|
||||
|
||||
struct transp_lvalue_eraser_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
auto count = x.erase_if(k.first.x_,
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} transp_lvalue_eraser_if;
|
||||
|
||||
struct erase_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
(void)k;
|
||||
auto count = x.erase_if(
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} erase_if;
|
||||
|
||||
struct erase_if_exec_policy_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(values, [&num_invokes, &x, threshold](boost::span<T> s) {
|
||||
(void)s;
|
||||
x.erase_if(
|
||||
std::execution::par_unseq, [&num_invokes, threshold](value_type& v) {
|
||||
++num_invokes;
|
||||
return v.second.x_ > threshold;
|
||||
});
|
||||
});
|
||||
|
||||
BOOST_TEST_GE(+num_invokes, old_size);
|
||||
BOOST_TEST_LE(+num_invokes, old_size * num_threads);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * expected_erasures);
|
||||
#else
|
||||
(void)values;
|
||||
(void)x;
|
||||
#endif
|
||||
}
|
||||
} erase_if_exec_policy;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void erase(X*, G gen, F eraser, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
|
||||
x.insert(values.begin(), values.end());
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}
|
||||
}));
|
||||
|
||||
eraser(values, x);
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* transparent_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
erase,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(erase_if_exec_policy))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
erase,
|
||||
((transparent_map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((transp_lvalue_eraser)(transp_lvalue_eraser_if)(erase_if_exec_policy))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,390 @@
|
||||
#ifndef BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
#define BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
|
||||
#include "../helpers/generators.hpp"
|
||||
#include "../helpers/test.hpp"
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
constexpr std::size_t const num_threads = 16;
|
||||
|
||||
struct transp_hash
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
template <class T> std::size_t operator()(T const& t) const noexcept
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return boost::hash<T>()(t);
|
||||
}
|
||||
};
|
||||
|
||||
struct transp_key_equal
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
template <class T, class U> bool operator()(T const& lhs, U const& rhs) const
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct stateful_hash
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
stateful_hash() = default;
|
||||
stateful_hash(stateful_hash const&) = default;
|
||||
stateful_hash(stateful_hash&& rhs) noexcept
|
||||
{
|
||||
auto tmp = x_;
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = tmp;
|
||||
}
|
||||
|
||||
stateful_hash(int const x) : x_{x} {}
|
||||
|
||||
template <class T> std::size_t operator()(T const& t) const noexcept
|
||||
{
|
||||
std::size_t h = static_cast<std::size_t>(x_);
|
||||
boost::hash_combine(h, t);
|
||||
std::this_thread::yield();
|
||||
return h;
|
||||
}
|
||||
|
||||
bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept
|
||||
{
|
||||
if (&lhs != &rhs) {
|
||||
std::swap(lhs.x_, rhs.x_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct stateful_key_equal
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
stateful_key_equal() = default;
|
||||
stateful_key_equal(stateful_key_equal const&) = default;
|
||||
stateful_key_equal(stateful_key_equal&& rhs) noexcept
|
||||
{
|
||||
auto tmp = x_;
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = tmp;
|
||||
}
|
||||
|
||||
stateful_key_equal(int const x) : x_{x} {}
|
||||
|
||||
template <class T, class U> bool operator()(T const& t, U const& u) const
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return t == u;
|
||||
}
|
||||
|
||||
bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; }
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, stateful_key_equal const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept
|
||||
{
|
||||
if (&lhs != &rhs) {
|
||||
std::swap(lhs.x_, rhs.x_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> struct stateful_allocator
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
using value_type = T;
|
||||
|
||||
stateful_allocator() = default;
|
||||
stateful_allocator(stateful_allocator const&) = default;
|
||||
stateful_allocator(stateful_allocator&&) = default;
|
||||
|
||||
stateful_allocator(int const x) : x_{x} {}
|
||||
|
||||
template <class U>
|
||||
stateful_allocator(stateful_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
bool operator==(stateful_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
};
|
||||
|
||||
struct raii
|
||||
{
|
||||
static std::atomic<std::uint32_t> default_constructor;
|
||||
static std::atomic<std::uint32_t> copy_constructor;
|
||||
static std::atomic<std::uint32_t> move_constructor;
|
||||
static std::atomic<std::uint32_t> destructor;
|
||||
|
||||
static std::atomic<std::uint32_t> copy_assignment;
|
||||
static std::atomic<std::uint32_t> move_assignment;
|
||||
|
||||
int x_ = -1;
|
||||
|
||||
raii() { ++default_constructor; }
|
||||
raii(int const x) : x_{x} { ++default_constructor; }
|
||||
raii(raii const& rhs) : x_{rhs.x_} { ++copy_constructor; }
|
||||
raii(raii&& rhs) noexcept : x_{rhs.x_}
|
||||
{
|
||||
rhs.x_ = -1;
|
||||
++move_constructor;
|
||||
}
|
||||
~raii() { ++destructor; }
|
||||
|
||||
raii& operator=(raii const& rhs)
|
||||
{
|
||||
++copy_assignment;
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
raii& operator=(raii&& rhs) noexcept
|
||||
{
|
||||
++move_assignment;
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend bool operator==(raii const& lhs, raii const& rhs)
|
||||
{
|
||||
return lhs.x_ == rhs.x_;
|
||||
}
|
||||
|
||||
friend bool operator!=(raii const& lhs, raii const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; }
|
||||
friend bool operator!=(raii const& lhs, int const x)
|
||||
{
|
||||
return !(lhs.x_ == x);
|
||||
}
|
||||
|
||||
friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; }
|
||||
|
||||
friend bool operator!=(int const x, raii const& rhs)
|
||||
{
|
||||
return !(rhs.x_ == x);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, raii const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, std::pair<raii const, raii> const& rhs)
|
||||
{
|
||||
os << "pair<" << rhs.first << ", " << rhs.second << ">";
|
||||
return os;
|
||||
}
|
||||
|
||||
static void reset_counts()
|
||||
{
|
||||
default_constructor = 0;
|
||||
copy_constructor = 0;
|
||||
move_constructor = 0;
|
||||
destructor = 0;
|
||||
copy_assignment = 0;
|
||||
move_assignment = 0;
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<std::uint32_t> raii::default_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::copy_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::move_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::destructor{0};
|
||||
std::atomic<std::uint32_t> raii::copy_assignment{0};
|
||||
std::atomic<std::uint32_t> raii::move_assignment{0};
|
||||
|
||||
std::size_t hash_value(raii const& r) noexcept
|
||||
{
|
||||
boost::hash<int> hasher;
|
||||
return hasher(r.x_);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
|
||||
{
|
||||
using vector_type = std::vector<decltype(f())>;
|
||||
|
||||
vector_type v;
|
||||
v.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
v.emplace_back(f());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
struct value_type_generator_type
|
||||
{
|
||||
std::pair<raii const, raii> operator()(test::random_generator rg)
|
||||
{
|
||||
int* p = nullptr;
|
||||
int a = generate(p, rg);
|
||||
int b = generate(p, rg);
|
||||
return std::make_pair(raii{a}, raii{b});
|
||||
}
|
||||
} value_type_generator;
|
||||
|
||||
struct init_type_generator_type
|
||||
{
|
||||
std::pair<raii, raii> operator()(test::random_generator rg)
|
||||
{
|
||||
int* p = nullptr;
|
||||
int a = generate(p, rg);
|
||||
int b = generate(p, rg);
|
||||
return std::make_pair(raii{a}, raii{b});
|
||||
}
|
||||
} init_type_generator;
|
||||
|
||||
template <class T>
|
||||
std::vector<boost::span<T> > split(
|
||||
boost::span<T> s, std::size_t const nt /* num threads*/)
|
||||
{
|
||||
std::vector<boost::span<T> > subslices;
|
||||
subslices.reserve(nt);
|
||||
|
||||
auto a = s.size() / nt;
|
||||
auto b = a;
|
||||
if (s.size() % nt != 0) {
|
||||
++b;
|
||||
}
|
||||
|
||||
auto num_a = nt;
|
||||
auto num_b = std::size_t{0};
|
||||
|
||||
if (nt * b > s.size()) {
|
||||
num_a = nt * b - s.size();
|
||||
num_b = nt - num_a;
|
||||
}
|
||||
|
||||
auto sub_b = s.subspan(0, num_b * b);
|
||||
auto sub_a = s.subspan(num_b * b);
|
||||
|
||||
for (std::size_t i = 0; i < num_b; ++i) {
|
||||
subslices.push_back(sub_b.subspan(i * b, b));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < num_a; ++i) {
|
||||
auto const is_last = i == (num_a - 1);
|
||||
subslices.push_back(
|
||||
sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a));
|
||||
}
|
||||
|
||||
return subslices;
|
||||
}
|
||||
|
||||
template <class T, class F> void thread_runner(std::vector<T>& values, F f)
|
||||
{
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::size_t c = 0;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
auto subslices = split<T>(values, num_threads);
|
||||
|
||||
for (std::size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([&f, &subslices, i, &m, &cv, &c] {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
++c;
|
||||
if (c == num_threads) {
|
||||
lk.unlock();
|
||||
cv.notify_all();
|
||||
} else {
|
||||
cv.wait(lk, [&] { return c == num_threads; });
|
||||
}
|
||||
}
|
||||
|
||||
auto s = subslices[i];
|
||||
f(s);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
template <class X, class Y>
|
||||
void test_matches_reference(X const& x, Y const& reference_map)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
}));
|
||||
}
|
||||
|
||||
template <class X, class Y>
|
||||
void test_fuzzy_matches_reference(
|
||||
X const& x, Y const& reference_map, test::random_generator rg)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
template <class T> using span_value_type = typename T::value_type;
|
||||
|
||||
void check_raii_counts()
|
||||
{
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
raii::default_constructor + raii::copy_constructor + raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
#endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
@@ -0,0 +1,585 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(78937);
|
||||
|
||||
struct lvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto const& r : s) {
|
||||
bool b = x.insert(r);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_inserter;
|
||||
|
||||
struct norehash_lvalue_inserter_type : public lvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
lvalue_inserter_type::operator()(values, x);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
} norehash_lvalue_inserter;
|
||||
|
||||
struct rvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.insert(std::move(r));
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} rvalue_inserter;
|
||||
|
||||
struct norehash_rvalue_inserter_type : public rvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
|
||||
rvalue_inserter_type::operator()(values, x);
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
|
||||
}
|
||||
}
|
||||
} norehash_rvalue_inserter;
|
||||
|
||||
struct iterator_range_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x](boost::span<T> s) { x.insert(s.begin(), s.end()); });
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} iterator_range_inserter;
|
||||
|
||||
struct lvalue_insert_or_assign_copy_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(r.first, r.second);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size());
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_assign_copy_assign;
|
||||
|
||||
struct lvalue_insert_or_assign_move_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(r.first, std::move(r.second));
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size());
|
||||
}
|
||||
} lvalue_insert_or_assign_move_assign;
|
||||
|
||||
struct rvalue_insert_or_assign_copy_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(std::move(r.first), r.second);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing
|
||||
BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size());
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} rvalue_insert_or_assign_copy_assign;
|
||||
|
||||
struct rvalue_insert_or_assign_move_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(std::move(r.first), std::move(r.second));
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size());
|
||||
}
|
||||
} rvalue_insert_or_assign_move_assign;
|
||||
|
||||
struct trans_insert_or_assign_copy_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using is_transparent =
|
||||
typename boost::make_void<typename X::hasher::is_transparent,
|
||||
typename X::key_equal::is_transparent>::type;
|
||||
|
||||
boost::ignore_unused<is_transparent>();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(r.first.x_, r.second);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing
|
||||
BOOST_TEST_EQ(raii::copy_assignment, values.size() - x.size());
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} trans_insert_or_assign_copy_assign;
|
||||
|
||||
struct trans_insert_or_assign_move_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using is_transparent =
|
||||
typename boost::make_void<typename X::hasher::is_transparent,
|
||||
typename X::key_equal::is_transparent>::type;
|
||||
|
||||
boost::ignore_unused<is_transparent>();
|
||||
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
x.insert_or_assign(r.first.x_, std::move(r.second));
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::move_constructor, 2 * x.size()); // rehashing
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, values.size() - x.size());
|
||||
}
|
||||
} trans_insert_or_assign_move_assign;
|
||||
|
||||
struct lvalue_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.insert_or_cvisit(
|
||||
r, [&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_cvisit;
|
||||
|
||||
struct lvalue_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b =
|
||||
x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_visit;
|
||||
|
||||
struct rvalue_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.insert_or_cvisit(
|
||||
std::move(r), [&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
}
|
||||
}
|
||||
} rvalue_insert_or_cvisit;
|
||||
|
||||
struct rvalue_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.insert_or_visit(
|
||||
std::move(r), [&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
|
||||
}
|
||||
}
|
||||
} rvalue_insert_or_visit;
|
||||
|
||||
struct iterator_range_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_invokes](boost::span<T> s) {
|
||||
x.insert_or_cvisit(
|
||||
s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
}
|
||||
} iterator_range_insert_or_cvisit;
|
||||
|
||||
struct iterator_range_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_invokes](boost::span<T> s) {
|
||||
x.insert_or_visit(
|
||||
s.begin(), s.end(), [&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
}
|
||||
} iterator_range_insert_or_visit;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void insert(X*, G gen, F inserter, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
|
||||
inserter(values, x);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
template <class X> void insert_initializer_list(X*)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::initializer_list<value_type> values{
|
||||
value_type{raii{0}, raii{0}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{10}, raii{10}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{0}, raii{0}},
|
||||
};
|
||||
|
||||
std::vector<raii> dummy;
|
||||
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
{
|
||||
X x;
|
||||
|
||||
thread_runner(
|
||||
dummy, [&x, &values](boost::span<raii>) { x.insert(values); });
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}));
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
|
||||
X x;
|
||||
|
||||
thread_runner(dummy, [&x, &values, &num_invokes](boost::span<raii>) {
|
||||
x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
x.insert_or_cvisit(
|
||||
values, [&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_invokes, (values.size() - x.size()) +
|
||||
(num_threads - 1) * values.size() +
|
||||
num_threads * values.size());
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}));
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* trans_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
insert_initializer_list,
|
||||
((map)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
|
||||
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
|
||||
(lvalue_insert_or_cvisit)(lvalue_insert_or_visit)
|
||||
(rvalue_insert_or_cvisit)(rvalue_insert_or_visit)
|
||||
(iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((map))
|
||||
((init_type_generator))
|
||||
((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)
|
||||
(rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((trans_map))
|
||||
((init_type_generator))
|
||||
((trans_insert_or_assign_copy_assign)(trans_insert_or_assign_move_assign))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,396 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(511933564);
|
||||
|
||||
struct lvalue_try_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto const& r : s) {
|
||||
bool b = x.try_emplace(r.first, r.second.x_);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_try_emplacer;
|
||||
|
||||
struct norehash_lvalue_try_emplacer_type : public lvalue_try_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
lvalue_try_emplacer_type::operator()(values, x);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
} norehash_lvalue_try_emplacer;
|
||||
|
||||
struct rvalue_try_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace(std::move(r.first), r.second.x_);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} rvalue_try_emplacer;
|
||||
|
||||
struct norehash_rvalue_try_emplacer_type : public rvalue_try_emplacer_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
|
||||
rvalue_try_emplacer_type::operator()(values, x);
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, x.size());
|
||||
}
|
||||
}
|
||||
} norehash_rvalue_try_emplacer;
|
||||
|
||||
struct transp_try_emplace_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using is_transparent =
|
||||
typename boost::make_void<typename X::hasher::is_transparent,
|
||||
typename X::key_equal::is_transparent>::type;
|
||||
|
||||
boost::ignore_unused<is_transparent>();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace(r.first.x_, r.second.x_);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} transp_try_emplace;
|
||||
|
||||
struct norehash_transp_try_emplace_type : public transp_try_emplace_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
transp_try_emplace_type::operator()(values, x);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
} norehash_transp_try_emplace;
|
||||
|
||||
struct lvalue_try_emplace_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_cvisit(
|
||||
r.first, r.second.x_,
|
||||
[&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} lvalue_try_emplace_or_cvisit;
|
||||
|
||||
struct lvalue_try_emplace_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_visit(
|
||||
r.first, r.second.x_,
|
||||
[&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} lvalue_try_emplace_or_visit;
|
||||
|
||||
struct rvalue_try_emplace_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_cvisit(
|
||||
std::move(r.first), r.second.x_,
|
||||
[&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
}
|
||||
}
|
||||
} rvalue_try_emplace_or_cvisit;
|
||||
|
||||
struct rvalue_try_emplace_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_visit(
|
||||
std::move(r.first), r.second.x_,
|
||||
[&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, x.size());
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, x.size());
|
||||
}
|
||||
}
|
||||
} rvalue_try_emplace_or_visit;
|
||||
|
||||
struct transp_try_emplace_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_cvisit(
|
||||
r.first.x_, r.second.x_,
|
||||
[&num_invokes](typename X::value_type const& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
} transp_try_emplace_or_cvisit;
|
||||
|
||||
struct transp_try_emplace_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
std::atomic<std::uint64_t> num_invokes{0};
|
||||
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
bool b = x.try_emplace_or_visit(
|
||||
r.first.x_, r.second.x_,
|
||||
[&num_invokes](typename X::value_type& v) {
|
||||
(void)v;
|
||||
++num_invokes;
|
||||
});
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_inserts, x.size());
|
||||
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
} transp_try_emplace_or_visit;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void try_emplace(X*, G gen, F try_emplacer, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
|
||||
try_emplacer(values, x);
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* transp_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
try_emplace,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_try_emplacer)(norehash_lvalue_try_emplacer)
|
||||
(rvalue_try_emplacer)(norehash_rvalue_try_emplacer)
|
||||
(lvalue_try_emplace_or_cvisit)(lvalue_try_emplace_or_visit)
|
||||
(rvalue_try_emplace_or_cvisit)(rvalue_try_emplace_or_visit))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
try_emplace,
|
||||
((transp_map))
|
||||
((init_type_generator))
|
||||
((transp_try_emplace)(norehash_transp_try_emplace)
|
||||
(transp_try_emplace_or_cvisit)(transp_try_emplace_or_visit))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,461 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(335740237);
|
||||
|
||||
struct lvalue_visitor_type
|
||||
{
|
||||
template <class T, class X, class M>
|
||||
void operator()(std::vector<T>& values, X& x, M const& reference_map)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
std::atomic<std::uint64_t> total_count{0};
|
||||
|
||||
auto mut_visitor = [&num_visits, &reference_map](value_type& v) {
|
||||
BOOST_TEST(reference_map.contains(v.first));
|
||||
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
|
||||
auto const_visitor = [&num_visits, &reference_map](value_type const& v) {
|
||||
BOOST_TEST(reference_map.contains(v.first));
|
||||
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &mut_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.visit(val.first, mut_visitor);
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.visit(val.second, mut_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto const& y = x;
|
||||
auto count = y.visit(val.first, const_visitor);
|
||||
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = y.visit(val.second, const_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.cvisit(val.first, const_visitor);
|
||||
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.cvisit(val.second, const_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
}
|
||||
} lvalue_visitor;
|
||||
|
||||
struct transp_visitor_type
|
||||
{
|
||||
template <class T, class X, class M>
|
||||
void operator()(std::vector<T>& values, X& x, M const& reference_map)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
std::atomic<std::uint64_t> total_count{0};
|
||||
|
||||
auto mut_visitor = [&num_visits, &reference_map](value_type& v) {
|
||||
BOOST_TEST(reference_map.contains(v.first));
|
||||
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
|
||||
auto const_visitor = [&num_visits, &reference_map](value_type const& v) {
|
||||
BOOST_TEST(reference_map.contains(v.first));
|
||||
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &mut_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.visit(val.first.x_, mut_visitor);
|
||||
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.visit(val.second.x_, mut_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto const& y = x;
|
||||
auto count = y.visit(val.first.x_, const_visitor);
|
||||
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = y.visit(val.second.x_, const_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.cvisit(val.first.x_, const_visitor);
|
||||
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.cvisit(val.second.x_, const_visitor);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_visits, values.size());
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
}
|
||||
} transp_visitor;
|
||||
|
||||
struct visit_all_type
|
||||
{
|
||||
template <class T, class X, class M>
|
||||
void operator()(std::vector<T>& values, X& x, M const& reference_map)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> total_count{0};
|
||||
|
||||
auto mut_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
|
||||
return [&reference_map, &num_visits](value_type& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
};
|
||||
|
||||
auto const_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
|
||||
return [&reference_map, &num_visits](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
};
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &total_count, &mut_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
total_count += x.visit_all(mut_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(total_count, num_threads * x.size());
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &total_count, &const_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
auto const& y = x;
|
||||
total_count += y.visit_all(const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(total_count, num_threads * x.size());
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(
|
||||
values, [&x, &total_count, &const_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
total_count += x.cvisit_all(const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(total_count, num_threads * x.size());
|
||||
total_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} visit_all;
|
||||
|
||||
struct exec_policy_visit_all_type
|
||||
{
|
||||
template <class T, class X, class M>
|
||||
void operator()(std::vector<T>& values, X& x, M const& reference_map)
|
||||
{
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
auto mut_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
|
||||
return [&reference_map, &num_visits](value_type& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
};
|
||||
|
||||
auto const_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
|
||||
return [&reference_map, &num_visits](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
++num_visits;
|
||||
};
|
||||
};
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &mut_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
|
||||
x.visit_all(std::execution::par_unseq, mut_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &const_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
auto const& y = x;
|
||||
|
||||
y.visit_all(std::execution::par_unseq, const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &const_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
x.cvisit_all(std::execution::par_unseq, const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
#else
|
||||
(void)values;
|
||||
(void)x;
|
||||
(void)reference_map;
|
||||
#endif
|
||||
}
|
||||
} exec_policy_visit_all;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void visit(X*, G gen, F visitor, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
for (auto& val : values) {
|
||||
if (val.second.x_ == 0) {
|
||||
val.second.x_ = 1;
|
||||
}
|
||||
val.second.x_ *= -1;
|
||||
}
|
||||
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
for (auto const& v : values) {
|
||||
x.insert(v);
|
||||
}
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
std::uint64_t old_default_constructor = raii::default_constructor;
|
||||
std::uint64_t old_copy_constructor = raii::copy_constructor;
|
||||
std::uint64_t old_move_constructor = raii::move_constructor;
|
||||
std::uint64_t old_copy_assignment = raii::copy_assignment;
|
||||
std::uint64_t old_move_assignment = raii::move_assignment;
|
||||
|
||||
visitor(values, x, reference_map);
|
||||
|
||||
BOOST_TEST_EQ(old_default_constructor, raii::default_constructor);
|
||||
BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor);
|
||||
BOOST_TEST_EQ(old_move_constructor, raii::move_constructor);
|
||||
BOOST_TEST_EQ(old_copy_assignment, raii::copy_assignment);
|
||||
BOOST_TEST_EQ(old_move_assignment, raii::move_assignment);
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
template <class X, class G>
|
||||
void empty_visit(X*, G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
using values_type = decltype(values);
|
||||
using span_value_type = typename values_type::value_type;
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x;
|
||||
|
||||
std::uint64_t old_default_constructor = raii::default_constructor;
|
||||
std::uint64_t old_copy_constructor = raii::copy_constructor;
|
||||
std::uint64_t old_move_constructor = raii::move_constructor;
|
||||
std::uint64_t old_copy_assignment = raii::copy_assignment;
|
||||
std::uint64_t old_move_assignment = raii::move_assignment;
|
||||
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<span_value_type> s) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
|
||||
x.visit_all(
|
||||
[&num_visits](typename X::value_type const&) { ++num_visits; });
|
||||
BOOST_TEST_EQ(num_visits, 0u);
|
||||
|
||||
for (auto const& val : s) {
|
||||
auto count = x.visit(val.first,
|
||||
[&num_visits](typename X::value_type const&) { ++num_visits; });
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(old_default_constructor, raii::default_constructor);
|
||||
BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor);
|
||||
BOOST_TEST_EQ(old_move_constructor, raii::move_constructor);
|
||||
BOOST_TEST_EQ(old_copy_assignment, raii::copy_assignment);
|
||||
BOOST_TEST_EQ(old_move_assignment, raii::move_assignment);
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* transp_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
|
||||
UNORDERED_TEST(
|
||||
visit,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_visitor)(visit_all)(exec_policy_visit_all))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
visit,
|
||||
((transp_map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((transp_visitor))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
empty_visit,
|
||||
((map)(transp_map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((default_generator)(sequential)(limited_range))
|
||||
)
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -6,6 +6,7 @@
|
||||
#if !defined(BOOST_UNORDERED_TEST_TEST_HEADER)
|
||||
#define BOOST_UNORDERED_TEST_TEST_HEADER
|
||||
|
||||
#include <boost/unordered/detail/fwd.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace insert_tests {
|
||||
test::check_instances check_;
|
||||
|
||||
X x;
|
||||
iterator pos = x.begin();
|
||||
const_iterator pos = x.begin();
|
||||
tracker_type tracker = test::create_ordered(x);
|
||||
|
||||
test::random_values<X> v(1000, generator);
|
||||
@@ -248,7 +248,7 @@ namespace insert_tests {
|
||||
test::check_instances check_;
|
||||
|
||||
X x;
|
||||
iterator pos = x.begin();
|
||||
const_iterator pos = x.begin();
|
||||
tracker_type tracker = test::create_ordered(x);
|
||||
|
||||
test::random_values<X> v(1000, generator);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
|
||||
// Copyright 2022-2023 Christian Mazakas.
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or move at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#if !defined(BOOST_UNORDERED_FOA_TESTS)
|
||||
#error "max_load_tests is currently only supported by open-addressed containers"
|
||||
#else
|
||||
|
||||
@@ -337,8 +337,12 @@ static void insert_node_handle_unique(Container1& c1, Container2& c2)
|
||||
(boost::is_same<node_type, typename Container2::node_type>::value));
|
||||
|
||||
typedef typename Container1::iterator iterator1;
|
||||
typedef typename Container2::iterator iterator2;
|
||||
typedef typename Container2::insert_return_type insert_return_type2;
|
||||
|
||||
Container1 c1_copy(c1);
|
||||
Container2 c2_copy;
|
||||
|
||||
iterator1 r1 = insert_empty_node(c1);
|
||||
insert_return_type2 r2 = c2.insert(node_type());
|
||||
BOOST_TEST(r1 == c1.end());
|
||||
@@ -367,6 +371,24 @@ static void insert_node_handle_unique(Container1& c1, Container2& c2)
|
||||
node_handle_compare(r.node, v);
|
||||
}
|
||||
}
|
||||
|
||||
while (!c1_copy.empty()) {
|
||||
value_type v = *c1_copy.begin();
|
||||
value_type const* v_ptr = boost::to_address(c1_copy.begin());
|
||||
std::size_t count = c2_copy.count(test::get_key<Container1>(v));
|
||||
iterator2 pos =
|
||||
c2_copy.insert(c2_copy.begin(), c1_copy.extract(c1_copy.begin()));
|
||||
if (!count) {
|
||||
BOOST_TEST_EQ(c2_copy.count(test::get_key<Container1>(v)), count + 1);
|
||||
BOOST_TEST(pos != c2.end());
|
||||
BOOST_TEST(boost::to_address(pos) == v_ptr);
|
||||
} else {
|
||||
BOOST_TEST_EQ(c2_copy.count(test::get_key<Container1>(v)), count);
|
||||
BOOST_TEST(pos != c2_copy.end());
|
||||
BOOST_TEST(
|
||||
test::get_key<Container2>(*pos) == test::get_key<Container2>(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
// Copyright 2013 Daniel James.
|
||||
// Copyright 2022 Christian Mazakas.
|
||||
// Copyright 2022-2023 Christian Mazakas.
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Christian Mazakas.
|
||||
// Copyright 2022-2023 Christian Mazakas.
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
|
||||
@@ -1833,7 +1833,6 @@ multiset_extract_const_overload_compile_test()
|
||||
|
||||
template <class UnorderedSet> void test_set_transparent_extract(UnorderedSet*)
|
||||
{
|
||||
#ifndef BOOST_UNORDERED_FOA_TESTS
|
||||
typedef typename UnorderedSet::node_type node_type;
|
||||
|
||||
count_reset();
|
||||
@@ -1877,7 +1876,6 @@ template <class UnorderedSet> void test_set_transparent_extract(UnorderedSet*)
|
||||
BOOST_TEST_EQ(set.size(), set_size);
|
||||
|
||||
BOOST_TEST_EQ(key::count_, expected_key_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class UnorderedSet>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
|
||||
// Copyright 2023 Christian Mazakas.
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "../helpers/test.hpp"
|
||||
|
||||
#include <boost/unordered/detail/implementation.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_set.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
#include <boost/unordered/unordered_node_set.hpp>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/config/pragma_message.hpp>
|
||||
#include <boost/config/workaround.hpp>
|
||||
|
||||
#if BOOST_CXX_VERSION <= 199711L || \
|
||||
BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) || \
|
||||
(defined(BOOST_LIBSTDCXX_VERSION) && BOOST_CXX_VERSION > 201703L) || \
|
||||
(defined(BOOST_MSVC_FULL_VER) && BOOST_MSVC_FULL_VER >= 192000000 && \
|
||||
BOOST_MSVC_FULL_VER < 193000000)
|
||||
|
||||
// automatically disable this test for C++03 builds so we can use the STL's
|
||||
// scoped_allocator_adaptor
|
||||
// we remove C++20 support for libstdc++ builds because of:
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108952
|
||||
//
|
||||
// msvc-14.2 w/ C++20 is similarly affected
|
||||
//
|
||||
|
||||
|
||||
BOOST_PRAGMA_MESSAGE("uses_allocator tests require C++11, scoped_allocator")
|
||||
|
||||
int main() {}
|
||||
|
||||
#else
|
||||
|
||||
#include <memory>
|
||||
#include <scoped_allocator>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
template <class T> struct allocator
|
||||
{
|
||||
typedef T value_type;
|
||||
|
||||
int tag_ = -1;
|
||||
|
||||
allocator() = default;
|
||||
allocator(int tag) : tag_{tag} {}
|
||||
allocator(allocator const&) = default;
|
||||
allocator(allocator&&) = default;
|
||||
|
||||
template <class U> allocator(allocator<U> const& rhs) : tag_{rhs.tag_} {}
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) noexcept { ::operator delete(p); }
|
||||
|
||||
allocator& operator=(allocator const& rhs)
|
||||
{
|
||||
tag_ = rhs.tag_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
allocator& operator=(allocator&& rhs) noexcept
|
||||
{
|
||||
tag_ = rhs.tag_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(allocator const&) const { return true; }
|
||||
bool operator!=(allocator const&) const { return false; }
|
||||
};
|
||||
|
||||
struct raii_tracker
|
||||
{
|
||||
static int count;
|
||||
static int copy_count;
|
||||
static int move_count;
|
||||
static int alloc_move_count;
|
||||
|
||||
using allocator_type = allocator<int>;
|
||||
|
||||
allocator_type a_;
|
||||
|
||||
raii_tracker(allocator_type a) : a_(a) { ++count; }
|
||||
raii_tracker(int, allocator_type const& a) : a_(a) { ++count; }
|
||||
|
||||
raii_tracker(raii_tracker const&) { ++copy_count; }
|
||||
raii_tracker(raii_tracker&&) noexcept { ++move_count; }
|
||||
raii_tracker(raii_tracker&&, allocator_type const& a) noexcept : a_(a)
|
||||
{
|
||||
++alloc_move_count;
|
||||
}
|
||||
|
||||
allocator_type get_allocator() const noexcept { return a_; }
|
||||
|
||||
friend bool operator==(raii_tracker const&, raii_tracker const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int raii_tracker::count = 0;
|
||||
int raii_tracker::copy_count = 0;
|
||||
int raii_tracker::move_count = 0;
|
||||
int raii_tracker::alloc_move_count = 0;
|
||||
|
||||
static void reset_counts()
|
||||
{
|
||||
raii_tracker::count = 0;
|
||||
raii_tracker::copy_count = 0;
|
||||
raii_tracker::move_count = 0;
|
||||
raii_tracker::alloc_move_count = 0;
|
||||
}
|
||||
|
||||
std::size_t hash_value(raii_tracker const&) { return 0; }
|
||||
|
||||
using map_allocator_type = std::scoped_allocator_adaptor<
|
||||
allocator<std::pair<raii_tracker const, raii_tracker> >, allocator<int> >;
|
||||
|
||||
using set_allocator_type =
|
||||
std::scoped_allocator_adaptor<allocator<raii_tracker>, allocator<int> >;
|
||||
|
||||
using map_type = boost::unordered_flat_map<raii_tracker, raii_tracker,
|
||||
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
|
||||
|
||||
using node_map_type = boost::unordered_node_map<raii_tracker, raii_tracker,
|
||||
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
|
||||
|
||||
using set_type = boost::unordered_flat_set<raii_tracker,
|
||||
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
|
||||
|
||||
using node_set_type = boost::unordered_node_set<raii_tracker,
|
||||
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
|
||||
|
||||
map_type* flat_map;
|
||||
node_map_type* node_map;
|
||||
|
||||
set_type* flat_set;
|
||||
node_set_type* node_set;
|
||||
|
||||
template <class X> static void map_uses_allocator_construction(X*)
|
||||
{
|
||||
reset_counts();
|
||||
|
||||
map_allocator_type alloc(
|
||||
allocator<std::pair<raii_tracker const, raii_tracker> >{12},
|
||||
allocator<int>{34});
|
||||
|
||||
X map(1, alloc);
|
||||
map.emplace(
|
||||
std::piecewise_construct, std::make_tuple(1337), std::make_tuple(7331));
|
||||
|
||||
BOOST_TEST_EQ(raii_tracker::count, 2);
|
||||
BOOST_TEST_EQ(raii_tracker::move_count, 0);
|
||||
BOOST_TEST_EQ(raii_tracker::alloc_move_count, 2);
|
||||
|
||||
BOOST_TEST_EQ(map.begin()->first.get_allocator().tag_, 34);
|
||||
BOOST_TEST_EQ(map.begin()->second.get_allocator().tag_, 34);
|
||||
}
|
||||
|
||||
template <class X> static void set_uses_allocator_construction(X*)
|
||||
{
|
||||
reset_counts();
|
||||
|
||||
set_allocator_type alloc(allocator<raii_tracker>{12}, allocator<int>{34});
|
||||
|
||||
X set(1, alloc);
|
||||
set.emplace();
|
||||
|
||||
BOOST_TEST_EQ(raii_tracker::count, 1);
|
||||
BOOST_TEST_EQ(raii_tracker::move_count, 0);
|
||||
BOOST_TEST_EQ(raii_tracker::alloc_move_count, 1);
|
||||
|
||||
BOOST_TEST_EQ(set.begin()->get_allocator().tag_, 34);
|
||||
}
|
||||
|
||||
UNORDERED_TEST(map_uses_allocator_construction, ((flat_map)(node_map)))
|
||||
UNORDERED_TEST(set_uses_allocator_construction, ((flat_set)(node_set)))
|
||||
|
||||
RUN_TESTS()
|
||||
|
||||
#endif
|
||||