Squashed 'src/immer/' changes from 9cb6a5a845..5875f7739a

5875f7739a Merge pull request #269 from pinotree/cmake-current-dir
e0c4b1793d cmake: use ${CMAKE_CURRENT_SOURCE_DIR} where possible
d0b79d5cbd Merge pull request #268 from pinotree/drop-findguile
5612ec803d cmake: drop FindGuile.cmake
b60403ca83 Merge pull request #267 from pinotree/system-catch2
eec85078f2 Drop unused copy of Catch2
ac49b71e42 Switch to system Catch2
615ed31635 nix: disable tests & examples
b70f1c27f1 Merge pull request #266 from pinotree/drop-doctest
b133b774d4 Drop the unused doctest copy
168768c5de tests: port dvektor to catch2
8872e233c3 Merge pull request #257 from vector-of-bool/cxx20-ranges-fixes
0b3aaf699b Merge pull request #263 from colugomusic/fix-msvc-explicit-destructor-call
10cd1f3312 Merge pull request #262 from colugomusic/fix-msvc-c2975
daff65041c Trying to satisfy LLVM.
88076f7329 Remove unneeded include
4585522553 Merge branch 'fix-msvc-explicit-destructor-call' of https://github.com/colugomusic/immer into fix-msvc-explicit-destructor-call
5516624d67 Manual destroy_at for C++14
205180bb13 Add missing #include required by gcc
77c2f422fe Fix whitespace
6672e57e26 Remove unused capture default
30fb619ce7 Use destroy_at to call explicit destructor in make_inner_n
813e891e25 Fix MSVC compile error C2975
21cc60ef11 Merge pull request #261 from colugomusic/fix-msvc-c2059
5125b83afb Fix MSVC compile error C2059
9549696683 Merge pull request #260 from arximboldi/fmt-fixes
4871c7455a Merge pull request #256 from dhly-etc/robust-loading
8a165e7bd2 Upgrade Nix in Actions
1b66f4ad2a Fix includes
4513c2f80d Upgrade install-nix-action
5f96a39d0d Merge pull request #259 from tocic/fix/dark_theme_logo
04c7d90fc4 Use alternative logo in dark mode
ae8abe0ce0 Return a direct reference from iterator operator[]
5722567343 Merge pull request #258 from bobkocisko/master
e382feab06 Fixing annoying unused local variable warning
ffb051721b Fixes and tests for C++20 Range compatibility
a983d6fe1a Make pretty-printer loading more robust
d98a68cd6c Merge pull request #255 from dhly-etc/gdb-pretty-printers
7ff66e0934 Add .gdbinit file to autoload pretty printers
9b8e1b340e Add array printer
0220ba4389 Add table printer
17915a5881 Add GDB pretty-printing support
adbb4cd311 Merge pull request #253 from tocic/fix/typos
c69ad466a1 Fix typos
9dad616455 Merge pull request #250 from LilyWangLL/AddConfigVersion
3630ec4080 Add quotes
54ba35145b Add export CMake Config version file
3c527e434b Merge pull request #246 from maierlars/feature/nothrow-move-constructible-flex-vector
119f9d858a Merge pull request #247 from urbanwe/urbanwe-patch-1
ec81042a26 Fix wrong order in operator<(T2, box<T>)
b7756f5e85 Use lambda for initialization.
9c67e60b44 Fixing assertions.
38edbd8be4 Attempt to make flex_vector nothrow move constructible/assignable.
a5991efbe4 Merge pull request #243 from arximboldi/memory-usage
ffef2712f3 Lower constants for memory usage so CI does not timeout
20eb876b80 Add benchmarks for checking CHAMP memory usage
58036b4eae Merge pull request #239 from arximboldi/release-v0.8
a814bcd592 Do not install specific old version of Nix
6b05223054 Upgrade install-nix-action
ad5acab104 Upgrade cachix action
de91bdb28e Prepare release 0.8.0
459854dc80 Merge pull request #238 from arximboldi/add-identity-api
2c8ceb6160 Add identity API
ca78d685ad Merge pull request #236 from DavidKorczynski/cifuzz-int
e870de6c0b Add CIFuzz GitHub action
4100eff397 Merge pull request #235 from arximboldi/more-fuzz-conflict
6e8a4f8f83 Add fuzzers that produce more conflicts
53b1e37e38 Merge pull request #233 from asa/make_gcc_pragma_ignore_clang
75cc8984e2 make clang ignore gcc pragma warning for champ
93ab7151cd Merge pull request #231 from arximboldi/fuzz-move
d5b6bf6b45 Strengthen the fuzzers even further
35c94037d4 Fix issues in map/set erase() found by fuzzer
b43a1ca2ac Fix fuzzer was not doing what we thought it did
048891443c Add set fuzzer that can find if the tree is corrupted by moves
8f105e275c Add map fuzzer that can find if the tree is corrupted by moves
54f0d543dc Merge pull request #230 from Kraks/patch-1
8acee494ba Fix typo in comment
006461c8ec Merge pull request #227 from arximboldi/fix-debug-stats
0e07630e22 Fix debug stats
d98e82d66a Merge pull request #226 from arximboldi/fix-dec-unsafe
ad759c4783 Remove dec_unsafe() from the policies
4a8fd59e35 Remove dec_unsafe() usage in HAMT data-structures
a7b0e23771 Remove usage of dec_unsafe() in RBTS data-structures
e02cbd795e Merge pull request #225 from arximboldi/fix-champ-move
db3ea447ad Fix sometimes HAMTs would move out values that it should not!
34deab8781 Fix iteration over data-structures would sometimes allow mutation!
e5d79ed80e Merge pull request #222 from arximboldi/more-meta-improvements
6b57f13664 Add update_if_exists method to map and table and related transients
f3cebc63a1 Fix table and table_transient update()
bd1f0c00c0 Improve debug stats API
ad73ea14bf Merge pull request #221 from arximboldi/champ-stats
893c08461a Add tools for printing statistics about trees
037b34afe3 Fix memory leak
79ebe07f31 Merge pull request #220 from arximboldi/map-leak
c4be744a49 Try something
c37693ac4a Remove vestige
1c780ba19b Fix memory leak
30ce53b4df Add test for memory leak found by oss-fuzz
2a32d5db4d Add update_move operation to map fuzzer
d22262967a Merge pull request #216 from arximboldi/hamt-transients-faster-rc-sub
1897252813 Remove vestige
f0defde7dd Ensure that champ update operations move the payload in the value
175c05fb3a Fix #219, ambiguous calls to destroy_n
c25a272ac7 Move objects whenever possible during champ erasure operations
95889a7aa2 Merge pull request #218 from cyrilRomain/master
898f7d8e82 Fix -fno-exceptions build (regression from 35147686)
1bbfb46bf3 Merge pull request #217 from arximboldi/hamt-transients-faster-rc
9caf11101a Fix ambiguities when compiling in C++17 mode
27132d5183 Mark a bunch of functions as noexcept
d004c6e4bf Fix implementation of unordered_copy
e3ddbb7a98 Optimize our own implementation of uninitialized_copy
778ad6718f Use std::copy when possible, our own uninitialized_copy elsewhere
265d79a5ba Our own implementations for uninitialied_... are better, use always
54ff4a21a7 Improve implementation of std shims
3514768617 Use standard library implementations of shims whenever possible
98d4f983cb Move values when node can be mutated but needs realloc (champ::update_mut)
a89f7c98c6 Move values when node can be mutated but needs realloc (champ::add_mut)
5e9fe0b347 Fix table documentation header
72be85b931 Merge pull request #214 from arximboldi/table-fixes
1f41bbf0ac Improve sequential initialization of HAMTs
4736697d3b Update the documentation of HAMT-based transients
efc91fe724 Make immer::table and table_transient use mutation when possible
00f9e49882 Add missing link table_transient in the documentation
b8dbc6ab96 Merge pull request #213 from arximboldi/sub-transients
f5ebe48b13 Add benchmarks for HAMT erasure
9e62f8ccd5 Ensure that edit markers are set properly during transient updates
175edad908 Sketch mutable removal from HAMTs
815541445d Merge pull request #210 from deadb0d4/single_hash_index
6cc2b39159 Merge pull request #212 from arximboldi/more-transients
9985fc131e review: add  to docs/container & fix update docs
002f4e6ba5 Fix mutable HAMT insertion with conflicts
dd8a3ac8c5 Test more exception safety of mutable operations on vectors
0f72f261aa Style: clang-format
416f4c71c4 Implement transients for map update()
8f0b8a043f Merge branch 'master' into single_hash_index
3a38bd7662 add docs & run clang-format
dbc96d6fc4 add test stub for table_transient
db88f93ed5 cosmetics + table tests
70a3620c4e Merge pull request #211 from arximboldi/fix-map-leak
54037e1aaf Fix sanitizers were not properly enabled in CI
cf20d291e5 Fix memory leak when mutating inner nodes
88cf946cfc Merge pull request #209 from arximboldi/map-add-transience
cb76e80313 add headers + examples
8f01c1babe cosmetic fixes
982aa1f6dc Fix insertion benchmarks
5f42f9069d Mutate collision nodes in-place when possible
7fd0ec4d8d Mutate champ nodes values in place when possible
0c0d313c40 Fix potential destruction of uninitialized values
91d065c9bf Reuse same-sized inner nodes during HAMT insertions
3ea3ae9c45 Sketch implementation interface for transient insertion
e3744032a8 Merge pull request #208 from arximboldi/map-transients
22323150fb Add move and transient versions for the HAMT insertion benchmarks
a56e6f8007 Style: clang-format
a9a672bfb0 Add naive implementation of map and set transients
ca255452c3 Add move-based updates on immutable maps and sets
eb5056c4cb Add map and set range constructors
440f7519b2 Sketch map and set transient types
6b27170212 Fix compiling some tests would crash
b38c67b3a1 Merge pull request #206 from audulus/spm
b3139b4055 Immer is compatible with C++14
380dc52dfc Add spm job
9370c89597 Work on SPM support.
a1271fa712 Merge pull request #202 from philipcraig/patch-1
9af8724378 Update README.rst
9c7bb910a2 Update README.rst
88887fd9a5 Update README.rst
9da861dea5 Update memory.rst
fa61a3ed9d Update atom.hpp
d7dd8a729f Update transients.rst
3ceb4be6a1 Update map.hpp
e1d29d0dad Update vector.hpp
3d12e12cbb Update array.hpp
749035b5c9 Update array.hpp
1f9910eae1 fix typo
d2648aceea Merge pull request #201 from mvtec-richter/patch-1
70d6b6eb16 Fix typo in documentation of diff
0fcf66743e Tweak diff docs
ef80e6c5f7 Merge pull request #200 from arximboldi/diff-polish
7ba2196e4c Add missing header
36178a7aa6 Add the diffing algorithm to the fuzzers
af99b84519 Document diff algorithm
25b20e77c1 Simplify diff algorithm interface
3db544b73a Avoid passing Differ template param, use universal references
d372683b7a Merge pull request #199 from mvtec-richter/add-diff-method-to-map
f23dc6cdf7 Remove incorrect usage of std::forward
a4482d56e0 Introduce struct differ
f91922ec95 Add diff to set and avoid std::pair in champ
0f8eae238c Add diffing algorithm to maps
42e6beafed Merge pull request #197 from bollu/master
926098ccc8 Typo fix: frony -> front
24214158f5 Merge pull request #196 from arximboldi/nix-build-issues
9b770ec715 Do not try to use a build dir, because we have a BUILD file
98a8f1b7a5 Do not rely on git to fetch the code locally
2f12be4d65 Allow changing nixpkgs version
d6d27de64a Merge pull request #193 from arximboldi/fix-github-actions
6b299fc0a5 Upgrade Nix related actions
a3617d5f3b Disable ssh stuff when it's not me
5adcd67e55 Ignore Nix result links
36d1000deb Fix comment

git-subtree-dir: src/immer
git-subtree-split: 5875f7739a6c642ad58cbedadb509c86d421217e
This commit is contained in:
Kittywhiskers Van Gogh 2024-11-02 17:50:50 +00:00
parent 46b560d508
commit a9f46b32c3
154 changed files with 8823 additions and 24492 deletions

2
.gdbinit Normal file
View File

@ -0,0 +1,2 @@
# Load pretty printers
source tools/gdb_pretty_printers/autoload.py

26
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: CIFuzz
on: [pull_request]
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'immer'
dry-run: false
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'immer'
fuzz-seconds: 300
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@ -7,27 +7,33 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # needed for fetchGit in default.nix
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v20
with:
nix_path: nixpkgs=channel:nixos-unstable
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
- uses: cachix/cachix-action@v8
- uses: cachix/cachix-action@v12
with:
name: arximboldi
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
- run: nix-build
build-spm:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- run: swift build
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v20
with:
nix_path: nixpkgs=channel:nixos-unstable
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
- uses: cachix/cachix-action@v8
- uses: cachix/cachix-action@v12
with:
name: arximboldi
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
@ -35,12 +41,12 @@ jobs:
- run: nix-shell --run "cd build && cmake .."
- run: nix-shell --run "cd build && make docs"
- uses: shimataro/ssh-key-action@v2
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi'
with:
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
- run: nix-shell --run "cd build && make upload-docs"
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi'
check:
strategy:
@ -74,7 +80,7 @@ jobs:
- type: Debug
toolchain: llvm-8
std: 14
opts: ['sanitizer']
opts: ['sanitize']
# benchmarks
- type: Release
toolchain: gnu-9
@ -87,11 +93,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v20
with:
nix_path: nixpkgs=channel:nixos-unstable
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
- uses: cachix/cachix-action@v8
- uses: cachix/cachix-action@v12
with:
name: arximboldi
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
@ -113,11 +118,11 @@ jobs:
"
- run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "cd build && make check -j`nproc`"
- run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "bash <(curl -s https://codecov.io/bash)"
if: ${{ contains(matrix.opts, 'coverage') }}
if: contains(matrix.opts, 'coverage')
- uses: shimataro/ssh-key-action@v2
if: ${{ contains(matrix.opts, 'benchmark') }}
if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi'
with:
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
- run: nix-shell --run "cd build && make upload-benchmark-reports"
if: ${{ contains(matrix.opts, 'benchmark') }}
if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi'

5
.gitignore vendored
View File

@ -22,3 +22,8 @@ __pycache__
tools/clojure/.lein*
*.pyc
/result*
.build
.swiftpm

View File

@ -4,7 +4,7 @@ cmake_policy(SET CMP0048 NEW) # enable project VERSION
cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
project(immer VERSION 0.7.0)
project(immer VERSION 0.8.0)
if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-extended-offsetof -Wno-c++17-extensions -Wno-c++1z-extensions -Wno-unknown-warning-option -Wno-type-limits")
@ -99,6 +99,14 @@ install(TARGETS immer EXPORT ImmerConfig)
install(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer")
install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion )
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer" )
# development target to be used in tests, examples, benchmarks...
immer_canonicalize_cmake_booleans(
DISABLE_FREE_LIST
@ -131,6 +139,8 @@ endif()
if (immer_BUILD_TESTS)
enable_testing()
find_package(Catch2 REQUIRED)
add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}

29
Package.swift Normal file
View File

@ -0,0 +1,29 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "immer",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "immer",
targets: ["immer"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "immer",
dependencies: [],
path: ".",
sources: ["spm.cpp"],
publicHeadersPath: ".")
],
cxxLanguageStandard: .cxx14
)

View File

@ -1,6 +1,6 @@
.. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg
:target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster
:alt: Github Actions Badge
:alt: GitHub Actions Badge
.. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg
:target: https://codecov.io/gh/arximboldi/immer
@ -13,7 +13,10 @@
.. raw:: html
<img width="100%" src="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-front.svg" alt="Logotype"/>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-black.svg">
<img width="100%" src="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-front.svg" alt="Logotype">
</picture>
.. include:introduction/start
@ -74,7 +77,7 @@ Example
For a **complete example** check `Ewig, a simple didactic
text-editor <https://github.com/arximboldi/ewig>`_ built with this
library. You may also wanna check `Lager, a Redux-like library
<https://github.com/arximboldi/lager>`_ for writting interactive
<https://github.com/arximboldi/lager>`_ for writing interactive
software in C++ using a value-oriented design.

85
benchmark/set/erase.hpp Normal file
View File

@ -0,0 +1,85 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#pragma once
#include "benchmark/config.hpp"
#include <boost/container/flat_set.hpp>
#include <hash_trie.hpp> // Phil Nash
#include <immer/set.hpp>
#include <immer/set_transient.hpp>
#include <set>
#include <unordered_set>
namespace {
template <typename Generator, typename Set>
auto benchmark_erase_mut_std()
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
auto v_ = [&] {
auto v = Set{};
for (auto i = 0u; i < n; ++i)
v.insert(g[i]);
return v;
}();
measure(meter, [&] {
auto v = v_;
for (auto i = 0u; i < n; ++i)
v.erase(g[i]);
return v;
});
};
}
template <typename Generator, typename Set>
auto benchmark_erase()
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
auto v_ = [&] {
auto v = Set{}.transient();
for (auto i = 0u; i < n; ++i)
v.insert(g[i]);
return v.persistent();
}();
measure(meter, [&] {
auto v = v_;
for (auto i = 0u; i < n; ++i)
v = v.erase(g[i]);
return v;
});
};
}
template <typename Generator, typename Set>
auto benchmark_erase_move()
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
auto v_ = [&] {
auto v = Set{}.transient();
for (auto i = 0u; i < n; ++i)
v.insert(g[i]);
return v.persistent();
}();
measure(meter, [&] {
auto v = v_;
for (auto i = 0u; i < n; ++i)
v = std::move(v).erase(g[i]);
return v;
});
};
}
} // namespace

44
benchmark/set/erase.ipp Normal file
View File

@ -0,0 +1,44 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "erase.hpp"
#ifndef GENERATOR_T
#error "you must define a GENERATOR_T"
#endif
using generator__ = GENERATOR_T;
using t__ = typename decltype(generator__{}(0))::value_type;
// clang-format off
NONIUS_BENCHMARK("std::set", benchmark_erase_mut_std<generator__, std::set<t__>>())
NONIUS_BENCHMARK("std::unordered_set", benchmark_erase_mut_std<generator__, std::unordered_set<t__>>())
NONIUS_BENCHMARK("boost::flat_set", benchmark_erase_mut_std<generator__, boost::container::flat_set<t__>>())
// Phil Nash's hash_trie seems to not include an erase operation... at least at
// the version that we have included in the nix-shell here...
// NONIUS_BENCHMARK("hamt::hash_trie", benchmark_erase_mut_hash_trie<generator__, hamt::hash_trie<t__>>())
NONIUS_BENCHMARK("immer::set/5B", benchmark_erase<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
NONIUS_BENCHMARK("immer::set/4B", benchmark_erase<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
#ifndef DISABLE_GC_BENCHMARKS
NONIUS_BENCHMARK("immer::set/GC", benchmark_erase<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
#endif
NONIUS_BENCHMARK("immer::set/UN", benchmark_erase<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
NONIUS_BENCHMARK("immer::set/move/5B", benchmark_erase_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
NONIUS_BENCHMARK("immer::set/move/4B", benchmark_erase_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
NONIUS_BENCHMARK("immer::set/move/UN", benchmark_erase_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_erase_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_erase_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
#ifndef DISABLE_GC_BENCHMARKS
NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_erase_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
#endif
NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_erase_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
// clang-format on

View File

@ -10,9 +10,10 @@
#include "benchmark/config.hpp"
#include <immer/set.hpp>
#include <hash_trie.hpp> // Phil Nash
#include <boost/container/flat_set.hpp>
#include <hash_trie.hpp> // Phil Nash
#include <immer/set.hpp>
#include <immer/set_transient.hpp>
#include <set>
#include <unordered_set>
@ -21,8 +22,7 @@ namespace {
template <typename Generator, typename Set>
auto benchmark_insert_mut_std()
{
return [] (nonius::chronometer meter)
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
@ -38,8 +38,7 @@ auto benchmark_insert_mut_std()
template <typename Generator, typename Set>
auto benchmark_insert()
{
return [] (nonius::chronometer meter)
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
@ -52,4 +51,20 @@ auto benchmark_insert()
};
}
template <typename Generator, typename Set>
auto benchmark_insert_move()
{
return [](nonius::chronometer meter) {
auto n = meter.param<N>();
auto g = Generator{}(n);
measure(meter, [&] {
auto v = Set{};
for (auto i = 0u; i < n; ++i)
v = std::move(v).insert(g[i]);
return v;
});
};
}
} // namespace

View File

@ -13,8 +13,9 @@
#endif
using generator__ = GENERATOR_T;
using t__ = typename decltype(generator__{}(0))::value_type;
using t__ = typename decltype(generator__{}(0))::value_type;
// clang-format off
NONIUS_BENCHMARK("std::set", benchmark_insert_mut_std<generator__, std::set<t__>>())
NONIUS_BENCHMARK("std::unordered_set", benchmark_insert_mut_std<generator__, std::unordered_set<t__>>())
NONIUS_BENCHMARK("boost::flat_set", benchmark_insert_mut_std<generator__, boost::container::flat_set<t__>>())
@ -26,3 +27,16 @@ NONIUS_BENCHMARK("immer::set/4B", benchmark_insert<generator__, immer::set<t__,
NONIUS_BENCHMARK("immer::set/GC", benchmark_insert<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
#endif
NONIUS_BENCHMARK("immer::set/UN", benchmark_insert<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
NONIUS_BENCHMARK("immer::set/move/5B", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
NONIUS_BENCHMARK("immer::set/move/4B", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
NONIUS_BENCHMARK("immer::set/move/UN", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
#ifndef DISABLE_GC_BENCHMARKS
NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
#endif
NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
// clang-format on

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
#include "memory.hpp"
int main() { return main_basic(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
#include "memory.hpp"
int main() { return main_basic(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
#include "memory.hpp"
int main() { return main_basic(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
#include "memory.hpp"
int main() { return main_exp(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
#include "memory.hpp"
int main() { return main_exp(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
#include "memory.hpp"
int main() { return main_exp(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
#include "memory.hpp"
int main() { return main_lin(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
#include "memory.hpp"
int main() { return main_lin(); }

View File

@ -0,0 +1,5 @@
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
#include "memory.hpp"
int main() { return main_lin(); }

View File

@ -0,0 +1,451 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#pragma once
//
// These are some experiments to get insights about memory usage with various
// data-structures and configurations.
//
// The idea is to run this inside valgrind's massif tool and see what comes
// out. The following is the libraries that we do check.
//
// these are for "exp" tests
#include <boost/container/flat_set.hpp>
#include <hash_trie.hpp> // Phil Nash
#include <immer/set.hpp>
#include <immer/set_transient.hpp>
#include <set>
#include <unordered_set>
// these are for "lin" tests, which are map based actually
#include <boost/container/flat_map.hpp>
#include <hash_trie.hpp> // Phil Nash
#include <immer/map.hpp>
#include <immer/map_transient.hpp>
#include <map>
#include <unordered_map>
#include <boost/core/demangle.hpp>
#include <iostream>
#include <random>
#include <vector>
#include <valgrind/valgrind.h>
struct generate_string_short
{
static constexpr auto char_set =
"_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static constexpr auto max_length = 15;
static constexpr auto min_length = 4;
auto operator()() const
{
auto engine = std::default_random_engine{42};
auto dist = std::uniform_int_distribution<unsigned>{};
auto gen = std::bind(dist, engine);
return [=]() mutable {
auto len = gen() % (max_length - min_length) + min_length;
auto str = std::string(len, ' ');
std::generate_n(str.begin(), len, [&] {
return char_set[gen() % sizeof(char_set)];
});
return str;
};
}
};
struct generate_string_long
{
static constexpr auto char_set =
"_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static constexpr auto max_length = 256;
static constexpr auto min_length = 32;
auto operator()() const
{
auto engine = std::default_random_engine{42};
auto dist = std::uniform_int_distribution<unsigned>{};
auto gen = std::bind(dist, engine);
return [=]() mutable {
auto len = gen() % (max_length - min_length) + min_length;
auto str = std::string(len, ' ');
std::generate_n(str.begin(), len, [&] {
return char_set[gen() % sizeof(char_set)];
});
return str;
};
}
};
struct generate_unsigned
{
auto operator()() const
{
auto engine = std::default_random_engine{42};
auto dist = std::uniform_int_distribution<unsigned>{};
auto gen = std::bind(dist, engine);
return gen;
}
};
namespace basic_params {
constexpr auto N = 1 << 20;
void take_snapshot(std::size_t i)
{
std::cerr << " snapshot " << i << " / " << N << std::endl;
// This is not doing what we thing it does.. it is better to control the
// snapshot generation with something like this:
//
// --max-snapshots=1000 --detailed-freq=1
//
// VALGRIND_MONITOR_COMMAND("detailed_snapshot");
// VALGRIND_MONITOR_COMMAND("all_snapshots");
}
} // namespace basic_params
template <typename Generator, typename Set>
auto benchmark_memory_basic_std()
{
using namespace basic_params;
std::cerr << "running... " << boost::core::demangle(typeid(Set).name())
<< std::endl;
auto rs = std::vector<Set>{};
auto g = Generator{}();
auto v = Set{};
take_snapshot(0);
for (auto i = 0u; i < N; ++i) {
v.insert(g());
}
take_snapshot(N);
volatile auto dont_optimize_ = v.size();
return dont_optimize_;
}
template <typename Generator, typename Set>
auto benchmark_memory_basic()
{
using namespace basic_params;
std::cerr << "running... " << boost::core::demangle(typeid(Set).name())
<< std::endl;
auto g = Generator{}();
auto v = Set{};
take_snapshot(0);
for (auto i = 0u; i < N; ++i) {
v = std::move(v).insert(g());
}
take_snapshot(N);
volatile auto dont_optimize_ = v.size();
return dont_optimize_;
}
namespace exp_params {
constexpr auto N = 1 << 20;
constexpr auto E = 2;
void take_snapshot(std::size_t i)
{
std::cerr << " snapshot " << i << " / " << N << std::endl;
// This is not doing what we thing it does.. it is better to control the
// snapshot generation with something like this:
//
// --max-snapshots=1000 --detailed-freq=1
//
// VALGRIND_MONITOR_COMMAND("detailed_snapshot");
// VALGRIND_MONITOR_COMMAND("all_snapshots");
}
} // namespace exp_params
template <typename Generator, typename Set>
auto benchmark_memory_exp_std()
{
using namespace exp_params;
std::cerr << "running... " << boost::core::demangle(typeid(Set).name())
<< std::endl;
auto rs = std::vector<Set>{};
auto g = Generator{}();
auto v = Set{};
take_snapshot(0);
for (auto i = 0u, n = 1u; i < N; ++i) {
if (i == n) {
rs.push_back(v);
n *= E;
take_snapshot(i);
}
v.insert(g());
}
take_snapshot(N);
volatile auto dont_optimize_ = rs.data();
return dont_optimize_;
}
template <typename Generator, typename Set>
auto benchmark_memory_exp()
{
using namespace exp_params;
std::cerr << "running... " << boost::core::demangle(typeid(Set).name())
<< std::endl;
auto rs = std::vector<Set>{};
auto g = Generator{}();
auto v = Set{};
take_snapshot(0);
for (auto i = 0u, n = 1u; i < N; ++i) {
if (i == n) {
rs.push_back(v);
n *= E;
take_snapshot(i);
}
v = std::move(v).insert(g());
}
take_snapshot(N);
volatile auto dont_optimize_ = rs.data();
return dont_optimize_;
}
namespace lin_params {
constexpr auto N = 1 << 14;
constexpr auto M = 1 << 7;
constexpr auto S = 1;
void take_snapshot(std::size_t i)
{
std::cerr << " snapshot " << i << " / " << M << std::endl;
// This is not doing what we thing it does.. it is better to control the
// snapshot generation with something like this:
//
// --max-snapshots=1000 --detailed-freq=1
//
// VALGRIND_MONITOR_COMMAND("detailed_snapshot");
// VALGRIND_MONITOR_COMMAND("all_snapshots");
}
} // namespace lin_params
template <typename Generator, typename Map>
auto benchmark_memory_lin_std()
{
using namespace lin_params;
std::cerr << "running... " << boost::core::demangle(typeid(Map).name())
<< std::endl;
auto rs = std::vector<Map>{};
auto ks = std::vector<typename Map::key_type>{};
auto g = Generator{}();
auto v = Map{};
auto ug = generate_unsigned{}();
take_snapshot(0);
for (auto i = 0u; i < N; ++i) {
auto k = g();
v.insert({k, 0u});
ks.push_back(std::move(k));
}
take_snapshot(N);
take_snapshot(0);
for (auto i = 0u; i < M; ++i) {
for (auto j = 0u; j < S; ++j) {
auto&& k = ks[ug() % ks.size()];
++v.at(k);
}
rs.push_back(v);
take_snapshot(i);
}
take_snapshot(M);
volatile auto dont_optimize_ = rs.data();
return dont_optimize_;
}
template <typename Generator, typename Map>
auto benchmark_memory_lin()
{
using namespace lin_params;
std::cerr << "running... " << boost::core::demangle(typeid(Map).name())
<< std::endl;
auto rs = std::vector<Map>{};
auto ks = std::vector<typename Map::key_type>{};
auto g = Generator{}();
auto v = Map{};
auto ug = generate_unsigned{}();
take_snapshot(0);
for (auto i = 0u; i < N; ++i) {
auto k = g();
v = std::move(v).insert({k, 0u});
ks.push_back(std::move(k));
}
take_snapshot(N);
take_snapshot(0);
for (auto i = 0u; i < M; ++i) {
for (auto j = 0u; j < S; ++j) {
auto&& k = ks[ug() % ks.size()];
v = std::move(v).update(k, [](auto x) { return ++x; });
}
rs.push_back(v);
take_snapshot(i);
}
take_snapshot(M);
volatile auto dont_optimize_ = rs.data();
return dont_optimize_;
}
#if IMMER_BENCHMARK_MEMORY_STRING_SHORT
using generator__ = generate_string_short;
using t__ = std::string;
#elif IMMER_BENCHMARK_MEMORY_STRING_LONG
using generator__ = generate_string_long;
using t__ = std::string;
#elif IMMER_BENCHMARK_MEMORY_UNSIGNED
using generator__ = generate_unsigned;
using t__ = unsigned;
#else
#error "choose some type!"
#endif
int main_basic()
{
benchmark_memory_basic_std<generator__, std::set<t__>>();
benchmark_memory_basic_std<generator__, std::unordered_set<t__>>();
// too slow, why?
// benchmark_memory_basic_std<generator__,
// boost::container::flat_set<t__>>();
// very bad... just ignore...
// benchmark_memory_basic_std<generator__, hamt::hash_trie<t__>>();
using def_memory = immer::default_memory_policy;
benchmark_memory_basic<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 2>>();
benchmark_memory_basic<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 3>>();
benchmark_memory_basic<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 4>>();
benchmark_memory_basic<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 5>>();
benchmark_memory_basic<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 6>>();
return 0;
}
int main_exp()
{
benchmark_memory_exp_std<generator__, std::set<t__>>();
benchmark_memory_exp_std<generator__, std::unordered_set<t__>>();
// too slow, why?
// benchmark_memory_exp_std<generator__, boost::container::flat_set<t__>>();
// very bad... just ignore...
// benchmark_memory_exp_std<generator__, hamt::hash_trie<t__>>();
using def_memory = immer::default_memory_policy;
benchmark_memory_exp<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 2>>();
benchmark_memory_exp<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 3>>();
benchmark_memory_exp<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 4>>();
benchmark_memory_exp<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 5>>();
benchmark_memory_exp<
generator__,
immer::set<t__, std::hash<t__>, std::equal_to<t__>, def_memory, 6>>();
return 0;
}
int main_lin()
{
benchmark_memory_lin_std<generator__, std::map<t__, unsigned>>();
benchmark_memory_lin_std<generator__, std::unordered_map<t__, unsigned>>();
// too slow, why?
// benchmark_memory_lin_std<generator__, boost::container::flat_map<t__>>();
// very bad... just ignore...
// benchmark_memory_lin_std<generator__, hamt::hash_trie<t__>>();
using def_memory = immer::default_memory_policy;
benchmark_memory_lin<generator__,
immer::map<t__,
unsigned,
std::hash<t__>,
std::equal_to<t__>,
def_memory,
2>>();
benchmark_memory_lin<generator__,
immer::map<t__,
unsigned,
std::hash<t__>,
std::equal_to<t__>,
def_memory,
3>>();
benchmark_memory_lin<generator__,
immer::map<t__,
unsigned,
std::hash<t__>,
std::equal_to<t__>,
def_memory,
4>>();
benchmark_memory_lin<generator__,
immer::map<t__,
unsigned,
std::hash<t__>,
std::equal_to<t__>,
def_memory,
5>>();
benchmark_memory_lin<generator__,
immer::map<t__,
unsigned,
std::hash<t__>,
std::equal_to<t__>,
def_memory,
6>>();
return 0;
}

View File

@ -0,0 +1,11 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "generator.ipp"
#include "../erase.ipp"

View File

@ -0,0 +1,11 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "generator.ipp"
#include "../erase.ipp"

View File

@ -0,0 +1,11 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "generator.ipp"
#include "../erase.ipp"

View File

@ -0,0 +1,11 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "generator.ipp"
#include "../erase.ipp"

View File

@ -1,326 +0,0 @@
#[[.rst
#
# FindGuile
# ---------
# Find the development libraries for Guile.
#
# Exported Vars
# ~~~~~~~~~~~~~
#
# .. variable:: Guile_FOUND
#
# Set to *true* if Guile was found.
#
# .. variable:: Guile_INCLUDE_DIRS
#
# A list of include directories.
#
# .. variable:: Guile_LIBRARIES
#
# A list of libraries needed to build you project.
#
# .. variable:: Guile_VERSION_STRING
#
# Guile full version.
#
# .. variable:: Guile_VERSION_MAJOR
#
# Guile major version.
#
# .. variable:: Guile_VERSION_MINOR
#
# Guile minor version.
#
# .. variable:: Guile_VERSION_PATCH
#
# Guile patch version.
#
# .. variable:: Guile_EXECUTABLE
#
# Guile executable (optional).
#
# .. variable:: Guile_CONFIG_EXECUTABLE
#
# Guile configuration executable (optional).
#
# .. variable:: Guile_ROOT_DIR
#
# Guile installation root dir (optional).
#
# .. variable:: Guile_SITE_DIR
#
# Guile installation module site dir (optional).
#
# .. variable:: Guile_EXTENSION_DIR
#
# Guile installation extension dir (optional).
#
# Control VARS
# ~~~~~~~~~~~~
# :envvar:`Guile_ROOT_DIR`
#
# Use this variable to provide hints to :filename:`find_{*}` commands,
# you may pass it to :command:`cmake` or set the environtment variable.
#
# .. code-block:: cmake
#
# % cmake . -Bbuild -DGuile_ROOT_DIR=<extra-path>
#
# # or
# % export Guile_ROOT_DIR=<extra-path>;
# % cmake . -Bbuild
#
# # or
# % Guile_ROOT_DIR=<extra-path> cmake . -Bbuild
#
#
#]]
#[[.rst
#
# Copyright © 2016, Edelcides Gonçalves <eatg75 |0x40| gmail>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# *THE SOFTWARE IS PROVIDED* **AS IS** *AND ISC DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE
# LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE*.
#
# This file is not part of CMake
#
#]]
include (SelectLibraryConfigurations)
include (FindPackageHandleStandardArgs)
function (_guile_find_component_include_dir component header)
find_path ("${component}_INCLUDE_DIR"
${header}
HINTS
"${GUile_ROOT_DIR}"
ENV Guile_ROOT_DIR
PATH_SUFFIXES
Guile guile Guile-2.0 guile-2.0 Guile/2.0 guile/2.0 GC
gc)
set ("${component}_INCLUDE_DIR" "${${component}_INCLUDE_DIR}"
PARENT_SCOPE)
endfunction ()
function (_guile_find_component_library component_name component)
find_library ("${component_name}_LIBRARY_RELEASE"
NAMES "${component}" "${component}-2.0"
HINTS
"${GUile_ROOT_DIR}"
ENV Guile_ROOT_DIR
PATHS
/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}
/usr/lib64/${CMAKE_LIBRARY_ARCHITECTURE}
/usr/lib32/${CMAKE_LIBRARY_ARCHITECTURE})
if (${component_name}_LIBRARY_RELEASE)
select_library_configurations (${component_name})
set ("${component_name}_LIBRARY_RELEASE"
"${${component_name}_LIBRARY_RELEASE}" PARENT_SCOPE)
set ("${component_name}_LIBRARY"
"${${component_name}_LIBRARY}" PARENT_SCOPE)
set ("${component_name}_LIBRARIES"
"${${component_name}_LIBRARIES}" PARENT_SCOPE)
endif ()
endfunction ()
function (_guile_find_version_2 header_file macro_name)
file (STRINGS "${header_file}" _VERSION
REGEX "#define[\t ]+${macro_name}[\t ]+[0-9]+")
if (_VERSION)
string (REGEX REPLACE
".*#define[\t ]+${macro_name}[\t ]+([0-9]+).*"
"\\1" _VERSION_VALUE "${_VERSION}")
if ("${_VERSION}" STREQUAL "${_VERSION_VALUE}")
set (_VERSION_FOUND 0 PARENT_SCOPE)
else ()
set (_VERSION_FOUND 1 PARENT_SCOPE)
set (_VERSION "${_VERSION_VALUE}" PARENT_SCOPE)
endif ()
else ()
set (_VERSION_FOUND 0 PARENT_SCOPE)
endif ()
endfunction ()
##### Entry Point #####
set (Guile_FOUND)
set (Guile_INCLUDE_DIRS)
set (Guile_LIBRARIES)
set (Guile_VERSION_STRING)
set (Guile_VERSION_MAJOR)
set (Guile_VERSION_MINOR)
set (Guile_VERSION_PATCH)
set (Guile_EXECUTABLE)
_guile_find_component_include_dir (Guile "libguile.h")
if (Guile_INCLUDE_DIR)
_guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h"
SCM_MAJOR_VERSION)
if (_VERSION_FOUND)
set (Guile_VERSION_MAJOR "${_VERSION}")
else ()
message (FATAL_ERROR "FindGuile: Failed to find Guile_MAJOR_VERSION.")
endif ()
_guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h"
SCM_MINOR_VERSION)
if (_VERSION_FOUND)
set (Guile_VERSION_MINOR "${_VERSION}")
else ()
message (FATAL_ERROR "FindGuile: Failed to find Guile_MINOR_VERSION.")
endif ()
_guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h"
SCM_MICRO_VERSION)
if (_VERSION_FOUND)
set (Guile_VERSION_PATCH "${_VERSION}")
else ()
message (FATAL_ERROR "FindGuile: Failed to find Guile_MICRO_VERSION.")
endif ()
set (Guile_VERSION_STRING "${Guile_VERSION_MAJOR}.${Guile_VERSION_MINOR}.${Guile_VERSION_PATCH}")
unset (_VERSION_FOUND)
unset (_VERSION)
endif ()
_guile_find_component_include_dir (Guile_GC "gc.h")
_guile_find_component_library (Guile guile)
_guile_find_component_library (Guile_GC gc)
find_program (Guile_EXECUTABLE
guile
DOC "Guile executable.")
if (Guile_EXECUTABLE)
execute_process (COMMAND ${Guile_EXECUTABLE} --version
RESULT_VARIABLE _status
OUTPUT_VARIABLE _output
OUTPUT_STRIP_TRAILING_WHITESPACE)
string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+([0-9]+)\\..*" "\\1"
_guile_ver_major "${_output}")
string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+[0-9]+\\.([0-9]+).*" "\\1"
_guile_ver_minor "${_output}")
string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1"
_guile_ver_patch "${_output}")
set (_version "${_guile_ver_major}.${_guile_ver_minor}.${_guile_ver_patch}")
if (NOT Guile_FIND_QUIETLY)
if (NOT Guile_VERSION_STRING STREQUAL _version)
message (WARNING "FindGuile: Versions provided by library differs from the one provided by executable.")
endif ()
if (NOT _status STREQUAL "0")
message (WARNING "FindGuile: guile (1) process exited abnormally.")
endif ()
endif ()
unset (_version)
unset (_status)
unset (_version)
unset (_guile_ver_major)
unset (_guile_ver_minor)
unset (_guile_ver_patch)
endif ()
find_package_handle_standard_args (GC
"FindGuile: Failed to find dependency GC."
Guile_GC_INCLUDE_DIR
Guile_GC_LIBRARY
Guile_GC_LIBRARIES
Guile_GC_LIBRARY_RELEASE)
find_package_handle_standard_args (Guile
REQUIRED_VARS
Guile_INCLUDE_DIR
Guile_LIBRARY
Guile_LIBRARIES
Guile_LIBRARY_RELEASE
GC_FOUND
VERSION_VAR Guile_VERSION_STRING)
if (Guile_FOUND)
list (APPEND Guile_INCLUDE_DIRS "${Guile_INCLUDE_DIR}"
"${Guile_GC_INCLUDE_DIR}")
if (NOT TARGET Guile::Library AND NOT TARGET GC::Library)
add_library (Guile::GC::Library UNKNOWN IMPORTED)
set_property (TARGET Guile::GC::Library APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties (Guile::GC::Library
PROPERTIES
INTERFACE_INCLUDE_DIRS
"${Guile_GC_INCLUDE_DIR}"
IMPORTED_LOCATION
"${Guile_GC_LIBRARY}"
IMPORTED_LOCATION_RELEASE
"${Guile_GC_LIBRARY_RELEASE}")
add_library (Guile::Library UNKNOWN IMPORTED)
set_property (TARGET Guile::Library APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property (TARGET Guile::Library APPEND
PROPERTY
INTERFACE_LINK_LIBRARIES
Guile::GC::Library)
set_target_properties (Guile::Library
PROPERTIES
INTERFACE_INCLUDE_DIRSr
"${Guile_INCLUDE_DIR}"
IMPORTED_LOCATION "${Guile_LIBRARY}"
IMPORTED_LOCATION_RELEASE
"${Guile_LIBRARY_RELEASE}")
set (Guile_LIBRARIES Guile::Library Guile::GC::Library)
endif ()
endif ()
find_program(Guile_CONFIG_EXECUTABLE
NAMES guile-config
DOC "Guile configutration binary")
if (Guile_CONFIG_EXECUTABLE)
execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info prefix
OUTPUT_VARIABLE Guile_ROOT_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info sitedir
OUTPUT_VARIABLE Guile_SITE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info extensiondir
OUTPUT_VARIABLE Guile_EXTENSION_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif ()
mark_as_advanced (Guile_EXECUTABLE
Guile_INCLUDE_DIR
Guile_LIBRARY
Guile_LIBRARY_RELEASE
Guile_GC_INCLUDE_DIR
Guile_GC_LIBRARY
Guile_GC_LIBRARY_RELEASE)

View File

@ -1,11 +1,33 @@
with import <nixpkgs> {};
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
let
inherit (import (pkgs.fetchFromGitHub {
owner = "hercules-ci";
repo = "gitignore.nix";
rev = "80463148cd97eebacf80ba68cf0043598f0d7438";
sha256 = "1l34rmh4lf4w8a1r8vsvkmg32l1chl0p593fl12r28xx83vn150v";
}) {}) gitignoreSource;
nixFilter = name: type: !(lib.hasSuffix ".nix" name);
srcFilter = src: lib.cleanSourceWith {
filter = nixFilter;
src = gitignoreSource src;
};
in
stdenv.mkDerivation rec {
name = "immer-git";
version = "git";
src = fetchGit ./.;
src = srcFilter ./.;
nativeBuildInputs = [ cmake ];
dontBuild = true;
dontUseCmakeBuildDir = true;
cmakeFlags = [
"-Dimmer_BUILD_TESTS=OFF"
"-Dimmer_BUILD_EXAMPLES=OFF"
];
meta = {
homepage = "https://github.com/arximboldi/immer";
description = "Postmodern immutable data structures for C++";

View File

@ -4,11 +4,11 @@
add_custom_target(doxygen
COMMAND doxygen doxygen.config
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
add_custom_target(docs
COMMAND make html
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
add_dependencies(docs doxygen)
set(immer_ssh_method
@ -18,5 +18,5 @@ set(immer_ssh_method
add_custom_target(upload-docs
COMMAND
rsync -av -e \"${immer_ssh_method}\"
${CMAKE_SOURCE_DIR}/doc/_build/html/*
${CMAKE_CURRENT_SOURCE_DIR}/_build/html/*
raskolnikov@sinusoid.es:public/immer/)

View File

@ -48,3 +48,10 @@ map
.. doxygenclass:: immer::map
:members:
:undoc-members:
table
-----
.. doxygenclass:: immer::table
:members:
:undoc-members:

View File

@ -18,7 +18,7 @@ internally, common data with other objects. We sometimes refer to
this property as **structural sharing**. This behaviour is
transparent to the user.
Assigment
Assignment
---------
We are sorry, we lied. These containers provide *one mutating
@ -79,14 +79,14 @@ removing it, as in:
:end-before: move-good/end
So, is it bad style then to use ``const`` as much as possible? I
wouldn't say so and it is advisable when ``std::move()`` is not used.
An alternative style is to not use ``const`` but adopt an `AAA-style
<aaa>`_ (*Almost Always use Auto*). This way, it is easy to look for
wouldn't say so, and it is advisable when ``std::move()`` is not used.
An alternative style is to not use ``const`` but adopt an `AAA-style`_
(*Almost Always use Auto*). This way, it is easy to look for
mutations by looking for lines that contain ``=`` but no ``auto``.
Remember that when using our immutable containers ``operator=`` is the
only way to mutate a variable.
.. _aaa: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
.. _AAA-style: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
.. admonition:: Why does ``const`` prevent move semantics?
@ -149,7 +149,7 @@ mutate part of the internal data structure in place when possible.
If you don't like this syntax, :doc:`transients<transients>` may be
used to obtain similar performance benefits.
.. admonition:: Assigment guarantees
.. admonition:: Assignment guarantees
From the language point of view, the only requirement on moved from
values is that they should still be destructible. We provide the
@ -208,7 +208,7 @@ Efficient batch manipulations
Sometimes you may write a function that needs to do multiple changes
to a container. Like most code you write with this library, this
function is *pure*: it takes one container value in, and produces a
new container value out, no side-effects.
new container value out, no side effects.
Let's say we want to write a function that inserts all integers in the
range :math:`[first, last)` into an immutable vector:

View File

@ -1,7 +1,7 @@
Memory management
=================
Memory management is specially important for immutable data
Memory management is especially important for immutable data
structures. This is mainly due to:
#. In order to preserve the old value, new memory has to be allocated
@ -39,7 +39,7 @@ Memory policy
Example: tracing garbage collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is note worthy that all aspects of a
It is noteworthy that all aspects of a
:cpp:class:`immer::memory_policy` are not completely orthogonal.
Let's say you want to use a `tracing garbage collector`_. Actually, we
@ -79,7 +79,7 @@ allocate objects of those sizes.
.. _metafunction class: http://www.boost.org/doc/libs/1_62_0/libs/mpl/doc/refmanual/metafunction-class.html
A **heap** is a type with a methods ``void* allocate(std::size_t)``
A **heap** is a type with methods ``void* allocate(std::size_t)``
and ``void deallocate(void*)`` that return and release raw memory.
For a canonical model of this concept check the
:cpp:class:`immer::cpp_heap`.
@ -95,8 +95,8 @@ For a canonical model of this concept check the
On the other hand, having some **scoped state** does make
sense for some use-cases of immutable data structures. For
example, we might want to support variations of `region
based allocation`_. This interface might evolve to evolve
to support some kind of non-global state to accommodate
based allocation`_. This interface might evolve to support
some kind of non-global state to accommodate
these use cases.
.. _region based allocation: https://en.wikipedia.org/wiki/Region-based_memory_management
@ -143,7 +143,7 @@ Heap adaptors
Inspired by `Andrei Alexandrescu's talk on allocators <allocation
vexation>`_ and `Emery Berger's heap layers <heap layers>`_ we provide
allocator adaptors that can be combined using C++ mixins. These
enable building more complex allocator out of simpler strategies, or
enable building more complex allocators out of simpler strategies, or
provide application specific optimizations on top of general
allocators.

View File

@ -4,9 +4,9 @@ Transients
==========
*Transients* is a concept borrowed `from Clojure
<clojure-transients>`_, with some twists to turn make more idiomatic
<clojure-transients>`_, with some twists to make it more idiomatic
in C++. Essentially, they are a mutable interface built on top of the
same data structures the implements the immutable containers under the
same data structures that implement the immutable containers under the
hood.
These can be useful for :ref:`performing efficient batch
@ -49,3 +49,10 @@ map_transient
.. doxygenclass:: immer::map_transient
:members:
:undoc-members:
table_transient
---------------
.. doxygenclass:: immer::table_transient
:members:
:undoc-members:

29
example/table/intro.cpp Normal file
View File

@ -0,0 +1,29 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include <string>
// include:intro/start
#include <immer/table.hpp>
int main()
{
struct Item
{
std::string id;
int value;
};
const auto v0 = immer::table<Item>{};
const auto v1 = v0.insert({"hello", 42});
assert(v0["hello"].value == 0);
assert(v1["hello"].value == 42);
const auto v2 = v1.erase("hello");
assert(v1.find("hello")->value == 42);
assert(!v2.find("hello"));
}
// include:intro/end

View File

@ -12,6 +12,10 @@
#include <memory>
#include <stdexcept>
#if defined(__GNUC__) && (__GNUC__ == 9 || __GNUC__ == 8 || __GNUC__ == 10)
#define IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG 1
#endif
struct no_more_input : std::exception
{};

View File

@ -13,6 +13,8 @@
#include <immer/map.hpp>
#include <immer/refcount/no_refcount_policy.hpp>
#include <immer/algorithm.hpp>
#include <array>
using gc_memory = immer::memory_policy<immer::heap_policy<immer::gc_heap>,
@ -49,7 +51,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase_move,
op_iterate,
op_find,
op_update
op_update,
op_update_move,
op_update_if_exists,
op_update_if_exists_move,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -94,6 +100,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
break;
}
case op_update_move: {
auto key = read<size_t>(in);
vars[dst] =
std::move(vars[src]).update(key, [](int x) { return x + 1; });
break;
}
case op_update_if_exists: {
auto key = read<size_t>(in);
vars[dst] =
vars[src].update_if_exists(key, [](int x) { return x + 1; });
break;
}
case op_update_if_exists_move: {
auto key = read<size_t>(in);
vars[dst] = std::move(vars[src]).update_if_exists(
key, [](int x) { return x + 1; });
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x.first));
assert(b.count(x.first));
},
[&](auto&& x) {
assert(a.count(x.first));
assert(!b.count(x.first));
},
[&](auto&& x, auto&& y) {
assert(x.first == y.first);
assert(x.second != y.second);
});
}
default:
break;
};

View File

@ -0,0 +1,136 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "fuzzer_input.hpp"
#include <immer/box.hpp>
#include <immer/map.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
immer::unsafe_refcount_policy,
immer::no_lock_policy,
immer::no_transience_policy,
false>;
struct colliding_hash_t
{
std::size_t operator()(const std::string& x) const
{
return std::hash<std::string>{}(x) & ~((std::size_t{1} << 48) - 1);
}
};
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
std::size_t size)
{
constexpr auto var_count = 4;
using map_t = immer::map<std::string,
immer::box<std::string>,
colliding_hash_t,
std::equal_to<>,
st_memory,
3>;
auto vars = std::array<map_t, var_count>{};
auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; };
return fuzzer_input{data, size}.run([&](auto& in) {
enum ops
{
op_set,
op_erase,
op_set_move,
op_erase_move,
op_iterate,
op_find,
op_update,
op_update_move,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
assert(vars[src].impl().check_champ());
switch (read<char>(in)) {
case op_set: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].set(value, "foo");
break;
}
case op_erase: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].erase(value);
break;
}
case op_set_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).set(value, "foo");
break;
}
case op_erase_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).erase(value);
break;
}
case op_iterate: {
auto srcv = vars[src];
for (const auto& v : srcv) {
vars[dst] = vars[dst].set(v.first, v.second);
}
break;
}
case op_find: {
auto value = std::to_string(read<size_t>(in));
auto res = vars[src].find(value);
if (res != nullptr) {
vars[dst] = vars[dst].set(*res, "foo");
}
break;
}
case op_update: {
auto key = std::to_string(read<size_t>(in));
vars[dst] = vars[src].update(
key, [](std::string x) { return std::move(x) + "bar"; });
break;
}
case op_update_move: {
auto key = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).update(
key, [](std::string x) { return std::move(x) + "baz"; });
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x.first));
assert(b.count(x.first));
},
[&](auto&& x) {
assert(a.count(x.first));
assert(!b.count(x.first));
},
[&](auto&& x, auto&& y) {
assert(x.first == y.first);
assert(x.second != y.second);
});
}
default:
break;
};
return true;
});
}

135
extra/fuzzer/map-st-str.cpp Normal file
View File

@ -0,0 +1,135 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "fuzzer_input.hpp"
#include <immer/box.hpp>
#include <immer/map.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
immer::unsafe_refcount_policy,
immer::no_lock_policy,
immer::no_transience_policy,
false>;
struct colliding_hash_t
{
std::size_t operator()(const std::string& x) const
{
return std::hash<std::string>{}(x) & ~15;
}
};
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
std::size_t size)
{
constexpr auto var_count = 4;
using map_t = immer::map<std::string,
immer::box<std::string>,
colliding_hash_t,
std::equal_to<>,
st_memory>;
auto vars = std::array<map_t, var_count>{};
auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; };
return fuzzer_input{data, size}.run([&](auto& in) {
enum ops
{
op_set,
op_erase,
op_set_move,
op_erase_move,
op_iterate,
op_find,
op_update,
op_update_move,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
assert(vars[src].impl().check_champ());
switch (read<char>(in)) {
case op_set: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].set(value, "foo");
break;
}
case op_erase: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].erase(value);
break;
}
case op_set_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).set(value, "foo");
break;
}
case op_erase_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).erase(value);
break;
}
case op_iterate: {
auto srcv = vars[src];
for (const auto& v : srcv) {
vars[dst] = vars[dst].set(v.first, v.second);
}
break;
}
case op_find: {
auto value = std::to_string(read<size_t>(in));
auto res = vars[src].find(value);
if (res != nullptr) {
vars[dst] = vars[dst].set(*res, "foo");
}
break;
}
case op_update: {
auto key = std::to_string(read<size_t>(in));
vars[dst] = vars[src].update(
key, [](std::string x) { return std::move(x) + "bar"; });
break;
}
case op_update_move: {
auto key = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).update(
key, [](std::string x) { return std::move(x) + "baz"; });
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x.first));
assert(b.count(x.first));
},
[&](auto&& x) {
assert(a.count(x.first));
assert(!b.count(x.first));
},
[&](auto&& x, auto&& y) {
assert(x.first == y.first);
assert(x.second != y.second);
});
}
default:
break;
};
return true;
});
}

View File

@ -10,6 +10,8 @@
#include <immer/map.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
@ -44,7 +46,9 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase_move,
op_iterate,
op_find,
op_update
op_update,
op_update_move,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -89,6 +93,31 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
break;
}
case op_update_move: {
auto key = read<size_t>(in);
vars[dst] =
std::move(vars[src]).update(key, [](int x) { return x + 1; });
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x.first));
assert(b.count(x.first));
},
[&](auto&& x) {
assert(a.count(x.first));
assert(!b.count(x.first));
},
[&](auto&& x, auto&& y) {
assert(x.first == y.first);
assert(x.second != y.second);
});
}
default:
break;
};

View File

@ -10,6 +10,8 @@
#include <immer/map.hpp>
#include <immer/algorithm.hpp>
#include <array>
struct colliding_hash_t
@ -37,7 +39,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase_move,
op_iterate,
op_find,
op_update
op_update,
op_update_move,
op_update_if_exists,
op_update_if_exists_move,
op_diff,
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -82,6 +88,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
break;
}
case op_update_move: {
auto key = read<size_t>(in);
vars[dst] =
std::move(vars[src]).update(key, [](int x) { return x + 1; });
break;
}
case op_update_if_exists: {
auto key = read<size_t>(in);
vars[dst] =
vars[src].update_if_exists(key, [](int x) { return x + 1; });
break;
}
case op_update_if_exists_move: {
auto key = read<size_t>(in);
vars[dst] = std::move(vars[src]).update_if_exists(
key, [](int x) { return x + 1; });
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x.first));
assert(b.count(x.first));
},
[&](auto&& x) {
assert(a.count(x.first));
assert(!b.count(x.first));
},
[&](auto&& x, auto&& y) {
assert(x.first == y.first);
assert(x.second != y.second);
});
}
default:
break;
};

View File

@ -49,7 +49,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase,
op_insert_move,
op_erase_move,
op_iterate
op_iterate,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -81,6 +82,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
});
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x));
assert(b.count(x));
},
[&](auto&& x) {
assert(a.count(x));
assert(!b.count(x));
},
[&](auto&& x, auto&& y) { assert(false); });
}
default:
break;
};

View File

@ -0,0 +1,108 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "fuzzer_input.hpp"
#include <immer/box.hpp>
#include <immer/heap/gc_heap.hpp>
#include <immer/refcount/no_refcount_policy.hpp>
#include <immer/set.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
immer::unsafe_refcount_policy,
immer::no_lock_policy,
immer::no_transience_policy,
false>;
struct colliding_hash_t
{
std::size_t operator()(const std::string& x) const
{
return std::hash<std::string>{}(x) & ~((std::size_t{1} << 48) - 1);
}
};
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
std::size_t size)
{
constexpr auto var_count = 4;
using set_t = immer::
set<std::string, colliding_hash_t, std::equal_to<>, st_memory, 3>;
auto vars = std::array<set_t, var_count>{};
auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; };
return fuzzer_input{data, size}.run([&](auto& in) {
enum ops
{
op_insert,
op_erase,
op_insert_move,
op_erase_move,
op_iterate,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
assert(vars[src].impl().check_champ());
switch (read<char>(in)) {
case op_insert: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].insert(value);
break;
}
case op_erase: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].erase(value);
break;
}
case op_insert_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).insert(value);
break;
}
case op_erase_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).erase(value);
break;
}
case op_iterate: {
auto srcv = vars[src];
for (const auto& v : srcv) {
vars[dst] = vars[dst].insert(v);
}
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x));
assert(b.count(x));
},
[&](auto&& x) {
assert(a.count(x));
assert(!b.count(x));
},
[&](auto&& x, auto&& y) { assert(false); });
}
default:
break;
};
return true;
});
}

108
extra/fuzzer/set-st-str.cpp Normal file
View File

@ -0,0 +1,108 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include "fuzzer_input.hpp"
#include <immer/box.hpp>
#include <immer/heap/gc_heap.hpp>
#include <immer/refcount/no_refcount_policy.hpp>
#include <immer/set.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
immer::unsafe_refcount_policy,
immer::no_lock_policy,
immer::no_transience_policy,
false>;
struct colliding_hash_t
{
std::size_t operator()(const std::string& x) const
{
return std::hash<std::string>{}(x) & ~15;
}
};
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
std::size_t size)
{
constexpr auto var_count = 4;
using set_t =
immer::set<std::string, colliding_hash_t, std::equal_to<>, st_memory>;
auto vars = std::array<set_t, var_count>{};
auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; };
return fuzzer_input{data, size}.run([&](auto& in) {
enum ops
{
op_insert,
op_erase,
op_insert_move,
op_erase_move,
op_iterate,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
assert(vars[src].impl().check_champ());
switch (read<char>(in)) {
case op_insert: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].insert(value);
break;
}
case op_erase: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = vars[src].erase(value);
break;
}
case op_insert_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).insert(value);
break;
}
case op_erase_move: {
auto value = std::to_string(read<size_t>(in));
vars[dst] = std::move(vars[src]).erase(value);
break;
}
case op_iterate: {
auto srcv = vars[src];
for (const auto& v : srcv) {
vars[dst] = vars[dst].insert(v);
}
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x));
assert(b.count(x));
},
[&](auto&& x) {
assert(a.count(x));
assert(!b.count(x));
},
[&](auto&& x, auto&& y) { assert(false); });
}
default:
break;
};
return true;
});
}

View File

@ -12,6 +12,8 @@
#include <immer/refcount/no_refcount_policy.hpp>
#include <immer/set.hpp>
#include <immer/algorithm.hpp>
#include <array>
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
@ -44,7 +46,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase,
op_insert_move,
op_erase_move,
op_iterate
op_iterate,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -66,7 +69,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
}
case op_erase_move: {
auto value = read<size_t>(in);
vars[dst] = vars[src].erase(value);
vars[dst] = std::move(vars[src]).erase(value);
break;
}
case op_iterate: {
@ -76,6 +79,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
}
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x));
assert(b.count(x));
},
[&](auto&& x) {
assert(a.count(x));
assert(!b.count(x));
},
[&](auto&& x, auto&& y) { assert(false); });
}
default:
break;
};

View File

@ -8,6 +8,7 @@
#include "fuzzer_input.hpp"
#include <immer/algorithm.hpp>
#include <immer/set.hpp>
#include <array>
@ -35,7 +36,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
op_erase,
op_insert_move,
op_erase_move,
op_iterate
op_iterate,
op_diff
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
@ -67,6 +69,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
}
break;
}
case op_diff: {
auto&& a = vars[src];
auto&& b = vars[dst];
diff(
a,
b,
[&](auto&& x) {
assert(!a.count(x));
assert(b.count(x));
},
[&](auto&& x) {
assert(a.count(x));
assert(!b.count(x));
},
[&](auto&& x, auto&& y) { assert(false); });
}
default:
break;
};

View File

@ -2,7 +2,7 @@
Guile bindings
==============
This library includes experimental bindings bring efficient immutable
This library includes experimental bindings that provide efficient immutable
vectors for the `GNU Guile`_ Scheme implementation. The interface is
somewhat **incomplete**, but you can already do something interesting
things like:
@ -15,7 +15,7 @@ things like:
**Do you want to help** making these bindings complete and production
ready? Drop a line at `immer@sinusoid.al
<mailto:immer@sinusoid.al>`_ or `open an issue on Github
<mailto:immer@sinusoid.al>`_ or `open an issue on GitHub
<https://github.com/arximboldi/immer>`_
.. _GNU Guile: https://www.gnu.org/software/guile/

View File

@ -2,7 +2,7 @@
Python bindings
===============
This library includes experimental bindings bring efficient immutable
This library includes experimental bindings that provide efficient immutable
vectors for the Python language. They were developed as part of the
research for the `ICFP'17 paper`_. The interface is quite
**incomplete**, yet you can already do some things like:
@ -15,7 +15,7 @@ research for the `ICFP'17 paper`_. The interface is quite
**Do you want to help** making these bindings complete and production
ready? Drop a line at `immer@sinusoid.al
<mailto:immer@sinusoid.al>`_ or `open an issue on Github
<mailto:immer@sinusoid.al>`_ or `open an issue on GitHub
<https://github.com/arximboldi/immer>`_
Installation

View File

@ -10,6 +10,7 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <numeric>
#include <type_traits>
@ -92,6 +93,26 @@ bool for_each_chunk_p(const T* first, const T* last, Fn&& fn)
return std::forward<Fn>(fn)(first, last);
}
namespace detail {
template <class Iter, class T>
T accumulate_move(Iter first, Iter last, T init)
{
for (; first != last; ++first)
init = std::move(init) + *first;
return init;
}
template <class Iter, class T, class Fn>
T accumulate_move(Iter first, Iter last, T init, Fn op)
{
for (; first != last; ++first)
init = op(std::move(init), *first);
return init;
}
} // namespace detail
/*!
* Equivalent of `std::accumulate` applied to the range `r`.
*/
@ -99,7 +120,7 @@ template <typename Range, typename T>
T accumulate(Range&& r, T init)
{
for_each_chunk(r, [&](auto first, auto last) {
init = std::accumulate(first, last, init);
init = detail::accumulate_move(first, last, init);
});
return init;
}
@ -108,7 +129,7 @@ template <typename Range, typename T, typename Fn>
T accumulate(Range&& r, T init, Fn fn)
{
for_each_chunk(r, [&](auto first, auto last) {
init = std::accumulate(first, last, init, fn);
init = detail::accumulate_move(first, last, init, fn);
});
return init;
}
@ -121,7 +142,7 @@ template <typename Iterator, typename T>
T accumulate(Iterator first, Iterator last, T init)
{
for_each_chunk(first, last, [&](auto first, auto last) {
init = std::accumulate(first, last, init);
init = detail::accumulate_move(first, last, init);
});
return init;
}
@ -130,7 +151,7 @@ template <typename Iterator, typename T, typename Fn>
T accumulate(Iterator first, Iterator last, T init, Fn fn)
{
for_each_chunk(first, last, [&](auto first, auto last) {
init = std::accumulate(first, last, init, fn);
init = detail::accumulate_move(first, last, init, fn);
});
return init;
}
@ -208,6 +229,97 @@ bool all_of(Iter first, Iter last, Pred p)
});
}
/*!
* Object that can be used to process changes as computed by the @a diff
* algorithm.
*
* @tparam AddedFn Unary function that is be called whenever an added element is
* found. It is called with the added element as argument.
*
* @tparam RemovedFn Unary function that is called whenever a removed element is
* found. It is called with the removed element as argument.
*
* @tparam ChangedFn Unary function that is called whenever a changed element is
* found. It is called with the changed element as argument.
*/
template <class AddedFn, class RemovedFn, class ChangedFn>
struct differ
{
AddedFn added;
RemovedFn removed;
ChangedFn changed;
};
/*!
* Produces a @a differ object with `added`, `removed` and `changed` functions.
*/
template <class AddedFn, class RemovedFn, class ChangedFn>
auto make_differ(AddedFn&& added, RemovedFn&& removed, ChangedFn&& changed)
-> differ<std::decay_t<AddedFn>,
std::decay_t<RemovedFn>,
std::decay_t<ChangedFn>>
{
return {std::forward<AddedFn>(added),
std::forward<RemovedFn>(removed),
std::forward<ChangedFn>(changed)};
}
/*!
* Produces a @a differ object with `added` and `removed` functions and no
* `changed` function.
*/
template <class AddedFn, class RemovedFn>
auto make_differ(AddedFn&& added, RemovedFn&& removed)
{
return make_differ(std::forward<AddedFn>(added),
std::forward<RemovedFn>(removed),
[](auto&&...) {});
}
/*!
* Compute the differences between `a` and `b`.
*
* Changes detected are notified via the differ object, which should support the
* following expressions:
*
* - `differ.added(x)`, invoked when element `x` is found in `b` but not in
* `a`.
*
* - `differ.removed(x)`, invoked when element `x` is found in `a` but not in
* `b`.
*
* - `differ.changed(x, y)`, invoked when element `x` and `y` from `a` and `b`
* share the same key but map to a different value.
*
* This method leverages structural sharing to offer a complexity @f$ O(|diff|)
* @f$ when `b` is derived from `a` by performing @f$ |diff| @f$ updates. This
* is, this function can detect changes in effectively constant time per update,
* as oposed to the @f$ O(|a|+|b|) @f$ complexity of a trivial implementation.
*
* @rst
*
* .. note:: This method is only implemented for ``map`` and ``set``. When sets
* are diffed, the ``changed`` function is never called.
*
* @endrst
*/
template <typename T, typename Differ>
void diff(const T& a, const T& b, Differ&& differ)
{
a.impl().template diff<std::equal_to<typename T::value_type>>(
b.impl(), std::forward<Differ>(differ));
}
/*!
* Compute the differences between `a` and `b` using the callbacks in `fns` as
* differ. Equivalent to `diff(a, b, make_differ(fns)...)`.
*/
template <typename T, typename... Fns>
void diff(const T& a, const T& b, Fns&&... fns)
{
diff(a, b, make_differ(std::forward<Fns>(fns)...));
}
/** @} */ // group: algorithm
} // namespace immer

View File

@ -33,7 +33,7 @@ class array_transient;
* .. tip:: Don't be fooled by the bad complexity of this data
* structure. It is a great choice for short sequence or when it
* is seldom or never changed. This depends on the ``sizeof(T)``
* and the expensiveness of its ``T``'s copy constructor, in case
* and the expensiveness of its ``T``'s copy constructor. In case
* of doubt, measure. For basic types, using an `array` when
* :math:`n < 100` is a good heuristic.
*
@ -91,7 +91,7 @@ public:
{}
/*!
* Constructs a array containing the element `val` repeated `n`
* Constructs an array containing the element `val` repeated `n`
* times.
*/
array(size_type n, T v = {})
@ -107,7 +107,8 @@ public:
/*!
* Returns an iterator pointing just after the last element of the
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
* collection. It does not allocate memory and its complexity is @f$ O(1)
* @f$.
*/
IMMER_NODISCARD iterator end() const { return impl_.data() + impl_.size; }
@ -160,7 +161,7 @@ public:
/*!
* Returns a `const` reference to the element at position `index`.
* It is undefined when @f$ 0 index \geq size() @f$. It does not
* It is undefined when @f$ index \geq size() @f$. It does not
* allocate memory and its complexity is *effectively* @f$ O(1)
* @f$.
*/
@ -308,6 +309,14 @@ public:
return transient_type{std::move(impl_)};
}
/*!
* Returns a value that can be used as identity for the container. If two
* values have the same identity, they are guaranteed to be equal and to
* contain the same objects. However, two equal containers are not
* guaranteed to have the same identity.
*/
void* identity() const { return impl_.ptr; }
// Semi-private
const impl_t& impl() const { return impl_; }

View File

@ -129,7 +129,7 @@ private:
* @rst
*
* .. warning:: If memory policy used includes thread unsafe reference counting,
* no no thread safety is assumed, and the atom becomes thread unsafe too!
* no thread safety is assumed, and the atom becomes thread unsafe too!
*
* .. note:: ``box<T>`` provides a value based box of type ``T``, this is, we
* can think about it as a value-based version of ``std::shared_ptr``. In a
@ -138,7 +138,7 @@ private:
* ``std::atomic`` interface closely, since it attempts to be a higher level
* construction, most similar to Clojure's ``(atom)``. It is remarkable in
* particular that, since ``box<T>`` underlying object is immutable, using
* ``atom<T>`` is fully thread-safe in ways that ``std::atmic_shared_ptr`` is
* ``atom<T>`` is fully thread-safe in ways that ``std::atomic_shared_ptr`` is
* not. This is so because dereferencing the underlying pointer in a
* ``std::atomic_share_ptr`` may require further synchronization, in
* particular when invoking non-const methods.

View File

@ -219,9 +219,9 @@ IMMER_NODISCARD auto operator!=(T2&& b, const box<T, MP>& a)
template <typename T2, typename T, typename MP>
IMMER_NODISCARD auto operator<(T2&& b, const box<T, MP>& a)
-> std::enable_if_t<!std::is_same<box<T, MP>, std::decay_t<T2>>::value,
decltype(a.get() < b)>
decltype(b < a.get())>
{
return a.get() < b;
return b < a.get();
}
} // namespace immer

View File

@ -8,6 +8,10 @@
#pragma once
#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#define IMMER_HAS_CPP17 1
#endif
#if defined(__has_cpp_attribute)
#if __has_cpp_attribute(nodiscard)
#define IMMER_NODISCARD [[nodiscard]]
@ -66,6 +70,10 @@
#define IMMER_DEBUG_PRINT 0
#endif
#ifndef IMMER_DEBUG_STATS
#define IMMER_DEBUG_STATS 0
#endif
#ifndef IMMER_DEBUG_DEEP_CHECK
#define IMMER_DEBUG_DEEP_CHECK 0
#endif
@ -75,6 +83,10 @@
#include <prettyprint.hpp>
#endif
#if IMMER_DEBUG_STATS
#include <iostream>
#endif
#if IMMER_DEBUG_TRACES
#define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl
#else

View File

@ -61,7 +61,7 @@ struct node
static void delete_n(node_t* p, size_t sz, size_t cap)
{
destroy_n(p->data(), sz);
detail::destroy_n(p->data(), sz);
heap::deallocate(sizeof_n(cap), p);
}
@ -98,7 +98,7 @@ struct node
{
auto p = make_n(n);
IMMER_TRY {
uninitialized_copy(first, last, p->data());
detail::uninitialized_copy(first, last, p->data());
return p;
}
IMMER_CATCH (...) {

View File

@ -178,17 +178,18 @@ struct with_capacity
static size_t recommend_up(size_t sz, size_t cap)
{
auto max = std::numeric_limits<size_t>::max();
return sz <= cap ? cap
: cap >= max / 2 ? max
/* otherwise */
: std::max(2 * cap, sz);
return sz <= cap ? cap
: cap >= max / 2 ? max
/* otherwise */
: std::max(2 * cap, sz);
}
static size_t recommend_down(size_t sz, size_t cap)
{
return sz == 0 ? 1
: sz < cap / 2 ? sz * 2 :
/* otherwise */ cap;
return sz == 0 ? 1
: sz < cap / 2 ? sz * 2
:
/* otherwise */ cap;
}
with_capacity push_back(T value) const
@ -291,6 +292,7 @@ struct with_capacity
with_capacity take(std::size_t sz) const
{
assert(sz <= size);
auto cap = recommend_down(sz, capacity);
auto p = node_t::copy_n(cap, ptr, sz);
return {p, sz, cap};
@ -298,8 +300,9 @@ struct with_capacity
void take_mut(edit_t e, std::size_t sz)
{
assert(sz <= size);
if (ptr->can_mutate(e)) {
destroy_n(data() + size, size - sz);
detail::destroy_n(data() + size, size - sz);
size = sz;
} else {
auto cap = recommend_down(sz, capacity);

View File

@ -125,6 +125,51 @@ inline count_t popcount(std::uint8_t x)
return popcount(static_cast<std::uint32_t>(x));
}
template <typename bitmap_t>
class set_bits_range
{
bitmap_t bitmap;
class set_bits_iterator
{
bitmap_t bitmap;
inline static bitmap_t clearlsbit(bitmap_t bitmap)
{
return bitmap & (bitmap - 1);
}
inline static bitmap_t lsbit(bitmap_t bitmap)
{
return bitmap ^ clearlsbit(bitmap);
}
public:
set_bits_iterator(bitmap_t bitmap)
: bitmap(bitmap){};
set_bits_iterator operator++()
{
bitmap = clearlsbit(bitmap);
return *this;
}
bool operator!=(set_bits_iterator const& other) const
{
return bitmap != other.bitmap;
}
bitmap_t operator*() const { return lsbit(bitmap); }
};
public:
set_bits_range(bitmap_t bitmap)
: bitmap(bitmap)
{}
set_bits_iterator begin() const { return set_bits_iterator(bitmap); }
set_bits_iterator end() const { return set_bits_iterator(0); }
};
} // namespace hamts
} // namespace detail
} // namespace immer

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,8 @@ struct champ_iterator
using tree_t = champ<T, Hash, Eq, MP, B>;
using node_t = typename tree_t::node_t;
champ_iterator() = default;
struct end_t
{};

View File

@ -20,6 +20,11 @@ namespace immer {
namespace detail {
namespace hamts {
// For C++14 support.
// Calling the destructor inline breaks MSVC in some obscure
// corner cases.
template <typename T> constexpr void destroy_at(T* p) { p->~T(); }
template <typename T,
typename Hash,
typename Equal,
@ -56,7 +61,7 @@ struct node
aligned_storage_for<T> buffer;
};
using values_t = combine_standard_layout_t<values_data_t, refs_t>;
using values_t = combine_standard_layout_t<values_data_t, refs_t, ownee_t>;
struct inner_t
{
@ -80,7 +85,7 @@ struct node
data_t data;
};
using impl_t = combine_standard_layout_t<impl_data_t, refs_t>;
using impl_t = combine_standard_layout_t<impl_data_t, refs_t, ownee_t>;
impl_t impl;
@ -193,6 +198,10 @@ struct node
}
static const ownee_t& ownee(const values_t* x) { return get<ownee_t>(*x); }
static ownee_t& ownee(values_t* x) { return get<ownee_t>(*x); }
static bool can_mutate(values_t* x, edit_t e)
{
return refs(x).unique() || ownee(x).can_mutate(e);
}
static refs_t& refs(const node_t* x)
{
@ -204,6 +213,15 @@ struct node
}
static ownee_t& ownee(node_t* x) { return get<ownee_t>(x->impl); }
bool can_mutate(edit_t e) const
{
return refs(this).unique() || ownee(this).can_mutate(e);
}
bool can_mutate_values(edit_t e) const
{
return can_mutate(impl.d.data.inner.values, e);
}
static node_t* make_inner_n(count_t n)
{
assert(n <= branches<B>);
@ -284,7 +302,7 @@ struct node
new (vp + 1) T{std::move(x2)};
}
IMMER_CATCH (...) {
vp->~T();
immer::detail::hamts::destroy_at(vp);
IMMER_RETHROW;
}
}
@ -337,6 +355,32 @@ struct node
return p;
}
T* ensure_mutable_values(edit_t e)
{
assert(can_mutate(e));
auto old = impl.d.data.inner.values;
if (node_t::can_mutate(old, e))
return values();
else {
auto nv = data_count();
auto nxt = new (heap::allocate(sizeof_values_n(nv))) values_t{};
auto dst = (T*) &nxt->d.buffer;
auto src = values();
ownee(nxt) = e;
IMMER_TRY {
detail::uninitialized_copy(src, src + nv, dst);
}
IMMER_CATCH (...) {
deallocate_values(nxt, nv);
IMMER_RETHROW;
}
impl.d.data.inner.values = nxt;
if (refs(old).dec())
delete_values(old, nv);
return dst;
}
}
static node_t* copy_collision_insert(node_t* src, T v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
@ -347,7 +391,7 @@ struct node
IMMER_TRY {
new (dstp) T{std::move(v)};
IMMER_TRY {
std::uninitialized_copy(srcp, srcp + n, dstp + 1);
detail::uninitialized_copy(srcp, srcp + n, dstp + 1);
}
IMMER_CATCH (...) {
dstp->~T();
@ -361,6 +405,31 @@ struct node
return dst;
}
static node_t* move_collision_insert(node_t* src, T v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
auto n = src->collision_count();
auto dst = make_collision_n(n + 1);
auto srcp = src->collisions();
auto dstp = dst->collisions();
IMMER_TRY {
new (dstp) T{std::move(v)};
IMMER_TRY {
detail::uninitialized_move(srcp, srcp + n, dstp + 1);
}
IMMER_CATCH (...) {
dstp->~T();
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_collision(dst, n + 1);
IMMER_RETHROW;
}
delete_collision(src);
return dst;
}
static node_t* copy_collision_remove(node_t* src, T* v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
@ -370,12 +439,12 @@ struct node
auto srcp = src->collisions();
auto dstp = dst->collisions();
IMMER_TRY {
dstp = std::uninitialized_copy(srcp, v, dstp);
dstp = detail::uninitialized_copy(srcp, v, dstp);
IMMER_TRY {
std::uninitialized_copy(v + 1, srcp + n, dstp);
detail::uninitialized_copy(v + 1, srcp + n, dstp);
}
IMMER_CATCH (...) {
destroy(dst->collisions(), dstp);
detail::destroy(dst->collisions(), dstp);
IMMER_RETHROW;
}
}
@ -386,6 +455,32 @@ struct node
return dst;
}
static node_t* move_collision_remove(node_t* src, T* v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
assert(src->collision_count() > 1);
auto n = src->collision_count();
auto dst = make_collision_n(n - 1);
auto srcp = src->collisions();
auto dstp = dst->collisions();
IMMER_TRY {
dstp = detail::uninitialized_move(srcp, v, dstp);
IMMER_TRY {
detail::uninitialized_move(v + 1, srcp + n, dstp);
}
IMMER_CATCH (...) {
detail::destroy(dst->collisions(), dstp);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_collision(dst, n - 1);
IMMER_RETHROW;
}
delete_collision(src);
return dst;
}
static node_t* copy_collision_replace(node_t* src, T* pos, T v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
@ -397,12 +492,12 @@ struct node
IMMER_TRY {
new (dstp) T{std::move(v)};
IMMER_TRY {
dstp = std::uninitialized_copy(srcp, pos, dstp + 1);
dstp = detail::uninitialized_copy(srcp, pos, dstp + 1);
IMMER_TRY {
std::uninitialized_copy(pos + 1, srcp + n, dstp);
detail::uninitialized_copy(pos + 1, srcp + n, dstp);
}
IMMER_CATCH (...) {
destroy(dst->collisions(), dstp);
detail::destroy(dst->collisions(), dstp);
IMMER_RETHROW;
}
}
@ -428,13 +523,34 @@ struct node
auto dstp = dst->children();
dst->impl.d.data.inner.datamap = src->datamap();
dst->impl.d.data.inner.nodemap = src->nodemap();
std::uninitialized_copy(srcp, srcp + n, dstp);
inc_nodes(srcp, n);
srcp[offset]->dec_unsafe();
std::copy(srcp, srcp + n, dstp);
inc_nodes(srcp, offset);
inc_nodes(srcp + offset + 1, n - offset - 1);
dstp[offset] = child;
return dst;
}
static node_t* owned(node_t* n, edit_t e)
{
ownee(n) = e;
return n;
}
static node_t* owned_values(node_t* n, edit_t e)
{
ownee(n) = e;
ownee(n->impl.d.data.inner.values) = e;
return n;
}
static node_t* owned_values_safe(node_t* n, edit_t e)
{
ownee(n) = e;
if (n->impl.d.data.inner.values)
ownee(n->impl.d.data.inner.values) = e;
return n;
}
static node_t* copy_inner_replace_value(node_t* src, count_t offset, T v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
@ -445,13 +561,13 @@ struct node
dst->impl.d.data.inner.datamap = src->datamap();
dst->impl.d.data.inner.nodemap = src->nodemap();
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src->values(), src->values() + nv, dst->values());
IMMER_TRY {
dst->values()[offset] = std::move(v);
}
IMMER_CATCH (...) {
destroy_n(dst->values(), nv);
detail::destroy_n(dst->values(), nv);
IMMER_RETHROW;
}
}
@ -460,8 +576,7 @@ struct node
IMMER_RETHROW;
}
inc_nodes(src->children(), n);
std::uninitialized_copy(
src->children(), src->children() + n, dst->children());
std::copy(src->children(), src->children() + n, dst->children());
return dst;
}
@ -482,15 +597,15 @@ struct node
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
if (nv > 1) {
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
std::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
detail::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
}
IMMER_CATCH (...) {
destroy_n(dst->values(), voffset);
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
@ -500,15 +615,66 @@ struct node
}
}
inc_nodes(src->children(), n);
std::uninitialized_copy(
src->children(), src->children() + noffset, dst->children());
std::uninitialized_copy(src->children() + noffset,
src->children() + n,
dst->children() + noffset + 1);
std::copy(src->children(), src->children() + noffset, dst->children());
std::copy(src->children() + noffset,
src->children() + n,
dst->children() + noffset + 1);
dst->children()[noffset] = node;
return dst;
}
static node_t* move_inner_replace_merged(
edit_t e, node_t* src, bitmap_t bit, count_t voffset, node_t* node)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
assert(!(src->nodemap() & bit));
assert(src->datamap() & bit);
assert(voffset == src->data_count(bit));
auto n = src->children_count();
auto nv = src->data_count();
auto dst = make_inner_n(n + 1, nv - 1);
auto noffset = src->children_count(bit);
dst->impl.d.data.inner.datamap = src->datamap() & ~bit;
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
if (nv > 1) {
auto mutate_values = can_mutate(src->impl.d.data.inner.values, e);
IMMER_TRY {
if (mutate_values)
detail::uninitialized_move(
src->values(), src->values() + voffset, dst->values());
else
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
if (mutate_values)
detail::uninitialized_move(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
else
detail::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
}
IMMER_CATCH (...) {
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_inner(dst, n + 1, nv - 1);
IMMER_RETHROW;
}
}
// inc_nodes(src->children(), n);
std::copy(src->children(), src->children() + noffset, dst->children());
std::copy(src->children() + noffset,
src->children() + n,
dst->children() + noffset + 1);
dst->children()[noffset] = node;
delete_inner(src);
return dst;
}
static node_t* copy_inner_replace_inline(node_t* src,
bitmap_t bit,
count_t noffset,
@ -526,15 +692,15 @@ struct node
dst->impl.d.data.inner.datamap = src->datamap() | bit;
IMMER_TRY {
if (nv)
std::uninitialized_copy(
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
new (dst->values() + voffset) T{std::move(value)};
IMMER_TRY {
if (nv)
std::uninitialized_copy(src->values() + voffset,
src->values() + nv,
dst->values() + voffset + 1);
detail::uninitialized_copy(src->values() + voffset,
src->values() + nv,
dst->values() + voffset + 1);
}
IMMER_CATCH (...) {
dst->values()[voffset].~T();
@ -542,7 +708,7 @@ struct node
}
}
IMMER_CATCH (...) {
destroy_n(dst->values(), voffset);
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
@ -550,13 +716,74 @@ struct node
deallocate_inner(dst, n - 1, nv + 1);
IMMER_RETHROW;
}
inc_nodes(src->children(), n);
src->children()[noffset]->dec_unsafe();
std::uninitialized_copy(
src->children(), src->children() + noffset, dst->children());
std::uninitialized_copy(src->children() + noffset + 1,
src->children() + n,
dst->children() + noffset);
inc_nodes(src->children(), noffset);
inc_nodes(src->children() + noffset + 1, n - noffset - 1);
std::copy(src->children(), src->children() + noffset, dst->children());
std::copy(src->children() + noffset + 1,
src->children() + n,
dst->children() + noffset);
return dst;
}
static node_t* move_inner_replace_inline(
edit_t e, node_t* src, bitmap_t bit, count_t noffset, T value)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
assert(!(src->datamap() & bit));
assert(src->nodemap() & bit);
assert(noffset == src->children_count(bit));
auto n = src->children_count();
auto nv = src->data_count();
auto dst = make_inner_n(n - 1, nv + 1);
auto voffset = src->data_count(bit);
dst->impl.d.data.inner.nodemap = src->nodemap() & ~bit;
dst->impl.d.data.inner.datamap = src->datamap() | bit;
IMMER_TRY {
auto mutate_values =
nv && can_mutate(src->impl.d.data.inner.values, e);
if (nv) {
if (mutate_values)
detail::uninitialized_move(
src->values(), src->values() + voffset, dst->values());
else
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
}
IMMER_TRY {
new (dst->values() + voffset) T{std::move(value)};
IMMER_TRY {
if (nv) {
if (mutate_values)
detail::uninitialized_move(src->values() + voffset,
src->values() + nv,
dst->values() + voffset +
1);
else
detail::uninitialized_copy(src->values() + voffset,
src->values() + nv,
dst->values() + voffset +
1);
}
}
IMMER_CATCH (...) {
dst->values()[voffset].~T();
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_inner(dst, n - 1, nv + 1);
IMMER_RETHROW;
}
std::copy(src->children(), src->children() + noffset, dst->children());
std::copy(src->children() + noffset + 1,
src->children() + n,
dst->children() + noffset);
delete_inner(src);
return dst;
}
@ -574,15 +801,15 @@ struct node
dst->impl.d.data.inner.nodemap = src->nodemap();
if (nv > 1) {
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
std::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
detail::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
}
IMMER_CATCH (...) {
destroy_n(dst->values(), voffset);
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
@ -592,8 +819,66 @@ struct node
}
}
inc_nodes(src->children(), n);
std::uninitialized_copy(
src->children(), src->children() + n, dst->children());
std::copy(src->children(), src->children() + n, dst->children());
return dst;
}
static node_t* move_inner_remove_value(edit_t e,
node_t* src,
bitmap_t bit,
count_t voffset)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
assert(!(src->nodemap() & bit));
assert(src->datamap() & bit);
assert(voffset == src->data_count(bit));
auto n = src->children_count();
auto nv = src->data_count();
auto dst = make_inner_n(n, nv - 1);
dst->impl.d.data.inner.datamap = src->datamap() & ~bit;
dst->impl.d.data.inner.nodemap = src->nodemap();
if (nv > 1) {
auto mutate_values = can_mutate(src->impl.d.data.inner.values, e);
if (mutate_values) {
IMMER_TRY {
detail::uninitialized_move(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
detail::uninitialized_move(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
}
IMMER_CATCH (...) {
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_inner(dst, n, nv - 1);
IMMER_RETHROW;
}
} else {
IMMER_TRY {
detail::uninitialized_copy(
src->values(), src->values() + voffset, dst->values());
IMMER_TRY {
detail::uninitialized_copy(src->values() + voffset + 1,
src->values() + nv,
dst->values() + voffset);
}
IMMER_CATCH (...) {
detail::destroy_n(dst->values(), voffset);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_inner(dst, n, nv - 1);
IMMER_RETHROW;
}
}
}
std::copy(src->children(), src->children() + n, dst->children());
delete_inner(src);
return dst;
}
@ -608,15 +893,15 @@ struct node
dst->impl.d.data.inner.nodemap = src->nodemap();
IMMER_TRY {
if (nv)
std::uninitialized_copy(
detail::uninitialized_copy(
src->values(), src->values() + offset, dst->values());
IMMER_TRY {
new (dst->values() + offset) T{std::move(v)};
IMMER_TRY {
if (nv)
std::uninitialized_copy(src->values() + offset,
src->values() + nv,
dst->values() + offset + 1);
detail::uninitialized_copy(src->values() + offset,
src->values() + nv,
dst->values() + offset + 1);
}
IMMER_CATCH (...) {
dst->values()[offset].~T();
@ -624,7 +909,7 @@ struct node
}
}
IMMER_CATCH (...) {
destroy_n(dst->values(), offset);
detail::destroy_n(dst->values(), offset);
IMMER_RETHROW;
}
}
@ -633,8 +918,63 @@ struct node
IMMER_RETHROW;
}
inc_nodes(src->children(), n);
std::uninitialized_copy(
src->children(), src->children() + n, dst->children());
std::copy(src->children(), src->children() + n, dst->children());
return dst;
}
static node_t*
move_inner_insert_value(edit_t e, node_t* src, bitmap_t bit, T v)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
auto n = src->children_count();
auto nv = src->data_count();
auto offset = src->data_count(bit);
auto dst = make_inner_n(n, nv + 1);
dst->impl.d.data.inner.datamap = src->datamap() | bit;
dst->impl.d.data.inner.nodemap = src->nodemap();
IMMER_TRY {
auto mutate_values =
nv && can_mutate(src->impl.d.data.inner.values, e);
if (nv) {
if (mutate_values)
detail::uninitialized_move(
src->values(), src->values() + offset, dst->values());
else
detail::uninitialized_copy(
src->values(), src->values() + offset, dst->values());
}
IMMER_TRY {
new (dst->values() + offset) T{std::move(v)};
IMMER_TRY {
if (nv) {
if (mutate_values)
detail::uninitialized_move(src->values() + offset,
src->values() + nv,
dst->values() + offset +
1);
else
detail::uninitialized_copy(src->values() + offset,
src->values() + nv,
dst->values() + offset +
1);
}
}
IMMER_CATCH (...) {
dst->values()[offset].~T();
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
detail::destroy_n(dst->values(), offset);
IMMER_RETHROW;
}
}
IMMER_CATCH (...) {
deallocate_inner(dst, n, nv + 1);
IMMER_RETHROW;
}
std::copy(src->children(), src->children() + n, dst->children());
delete_inner(src);
return dst;
}
@ -648,7 +988,8 @@ struct node
auto merged = make_merged(
shift + B, std::move(v1), hash1, std::move(v2), hash2);
IMMER_TRY {
return make_inner_n(1, idx1 >> shift, merged);
return make_inner_n(
1, static_cast<count_t>(idx1 >> shift), merged);
}
IMMER_CATCH (...) {
delete_deep_shift(merged, shift + B);
@ -656,9 +997,9 @@ struct node
}
} else {
return make_inner_n(0,
idx1 >> shift,
static_cast<count_t>(idx1 >> shift),
std::move(v1),
idx2 >> shift,
static_cast<count_t>(idx2 >> shift),
std::move(v2));
}
} else {
@ -666,6 +1007,38 @@ struct node
}
}
static node_t* make_merged_e(
edit_t e, shift_t shift, T v1, hash_t hash1, T v2, hash_t hash2)
{
if (shift < max_shift<B>) {
auto idx1 = hash1 & (mask<B> << shift);
auto idx2 = hash2 & (mask<B> << shift);
if (idx1 == idx2) {
auto merged = make_merged_e(
e, shift + B, std::move(v1), hash1, std::move(v2), hash2);
IMMER_TRY {
return owned(
make_inner_n(
1, static_cast<count_t>(idx1 >> shift), merged),
e);
}
IMMER_CATCH (...) {
delete_deep_shift(merged, shift + B);
IMMER_RETHROW;
}
} else {
auto r = make_inner_n(0,
static_cast<count_t>(idx1 >> shift),
std::move(v1),
static_cast<count_t>(idx2 >> shift),
std::move(v2));
return owned_values(r, e);
}
} else {
return owned(make_collision(std::move(v1), std::move(v2)), e);
}
}
node_t* inc()
{
refs(this).inc();
@ -679,7 +1052,6 @@ struct node
}
bool dec() const { return refs(this).dec(); }
void dec_unsafe() const { refs(this).dec_unsafe(); }
static void inc_nodes(node_t** p, count_t n)
{
@ -690,6 +1062,7 @@ struct node
static void delete_values(values_t* p, count_t n)
{
assert(p);
detail::destroy_n((T*) &p->d.buffer, n);
deallocate_values(p, n);
}
@ -708,6 +1081,7 @@ struct node
assert(p);
IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision);
auto n = p->collision_count();
detail::destroy_n(p->collisions(), n);
deallocate_collision(p, n);
}
@ -741,13 +1115,11 @@ struct node
static void deallocate_values(values_t* p, count_t n)
{
destroy_n((T*) &p->d.buffer, n);
heap::deallocate(node_t::sizeof_values_n(n), p);
}
static void deallocate_collision(node_t* p, count_t n)
{
destroy_n(p->collisions(), n);
heap::deallocate(node_t::sizeof_collision_n(n), p);
}

View File

@ -82,19 +82,6 @@ protected:
std::is_base_of<std::bidirectional_iterator_tag,
IteratorCategoryT>::value;
class reference_proxy
{
friend iterator_facade;
DerivedT iter_;
reference_proxy(DerivedT iter)
: iter_{std::move(iter)}
{}
public:
operator ReferenceT() const { return *iter_; }
};
const DerivedT& derived() const
{
static_assert(std::is_base_of<iterator_facade, DerivedT>::value,
@ -111,10 +98,10 @@ protected:
public:
ReferenceT operator*() const { return access_t::dereference(derived()); }
PointerT operator->() const { return &access_t::dereference(derived()); }
reference_proxy operator[](DifferenceTypeT n) const
ReferenceT operator[](DifferenceTypeT n) const
{
static_assert(is_random_access, "");
return derived() + n;
return *(derived() + n);
}
friend bool operator==(const DerivedT& a, const DerivedT& b)

View File

@ -199,17 +199,23 @@ struct node
}
static ownee_t& ownee(node_t* x) { return get<ownee_t>(x->impl); }
static node_t* make_inner_n(count_t n)
static node_t* make_inner_n_into(void* buffer, std::size_t size, count_t n)
{
assert(n <= branches<B>);
auto m = heap::allocate(sizeof_inner_n(n));
auto p = new (m) node_t;
assert(size >= sizeof_inner_n(n));
auto p = new (buffer) node_t;
p->impl.d.data.inner.relaxed = nullptr;
#if IMMER_TAGGED_NODE
p->impl.d.kind = node_t::kind_t::inner;
#endif
return p;
}
static node_t* make_inner_n(count_t n)
{
assert(n <= branches<B>);
auto m = heap::allocate(sizeof_inner_n(n));
return make_inner_n_into(m, sizeof_inner_n(n), n);
}
static node_t* make_inner_e(edit_t e)
{
@ -310,16 +316,24 @@ struct node
});
}
static node_t* make_leaf_n(count_t n)
static node_t* make_leaf_n_into(void* buffer, std::size_t size, count_t n)
{
assert(n <= branches<BL>);
auto p = new (heap::allocate(sizeof_leaf_n(n))) node_t;
assert(size >= sizeof_leaf_n(n));
auto p = new (buffer) node_t;
#if IMMER_TAGGED_NODE
p->impl.d.kind = node_t::kind_t::leaf;
#endif
return p;
}
static node_t* make_leaf_n(count_t n)
{
assert(n <= branches<BL>);
auto m = heap::allocate(sizeof_leaf_n(n));
return make_leaf_n_into(m, sizeof_leaf_n(n), n);
}
static node_t* make_leaf_e(edit_t e)
{
auto p = new (heap::allocate(max_sizeof_leaf)) node_t;
@ -511,7 +525,7 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
auto dst = make_inner_n(n);
inc_nodes(src->inner(), n);
std::uninitialized_copy(src->inner(), src->inner() + n, dst->inner());
std::copy(src->inner(), src->inner() + n, dst->inner());
return dst;
}
@ -536,7 +550,20 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
auto p = src->inner();
inc_nodes(p, n);
std::uninitialized_copy(p, p + n, dst->inner());
std::copy(p, p + n, dst->inner());
return dst;
}
static node_t* do_copy_inner_replace(
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
{
IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner);
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
auto p = src->inner();
inc_nodes(p, offset);
inc_nodes(p + offset + 1, n - offset - 1);
std::copy(p, p + n, dst->inner());
dst->inner()[offset] = child;
return dst;
}
@ -582,6 +609,23 @@ struct node
return dst;
}
static node_t* do_copy_inner_replace_r(
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
{
IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner);
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
auto src_r = src->relaxed();
auto dst_r = dst->relaxed();
auto p = src->inner();
inc_nodes(p, offset);
inc_nodes(p + offset + 1, n - offset - 1);
std::copy(src->inner(), src->inner() + n, dst->inner());
std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
dst_r->d.count = n;
dst->inner()[offset] = child;
return dst;
}
static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n)
{
if (embed_relaxed)
@ -593,12 +637,28 @@ struct node
}
}
static node_t* do_copy_inner_replace_sr(
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
{
if (embed_relaxed)
return do_copy_inner_replace_r(dst, src, n, offset, child);
else {
auto p = src->inner();
inc_nodes(p, offset);
inc_nodes(p + offset + 1, n - offset - 1);
std::copy(p, p + n, dst->inner());
dst->inner()[offset] = child;
return dst;
}
}
static node_t* copy_leaf(node_t* src, count_t n)
{
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
auto dst = make_leaf_n(n);
IMMER_TRY {
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
detail::uninitialized_copy(
src->leaf(), src->leaf() + n, dst->leaf());
}
IMMER_CATCH (...) {
heap::deallocate(node_t::sizeof_leaf_n(n), dst);
@ -612,7 +672,8 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
auto dst = make_leaf_e(e);
IMMER_TRY {
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
detail::uninitialized_copy(
src->leaf(), src->leaf() + n, dst->leaf());
}
IMMER_CATCH (...) {
heap::deallocate(node_t::max_sizeof_leaf, dst);
@ -627,7 +688,8 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
auto dst = make_leaf_n(allocn);
IMMER_TRY {
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
detail::uninitialized_copy(
src->leaf(), src->leaf() + n, dst->leaf());
}
IMMER_CATCH (...) {
heap::deallocate(node_t::sizeof_leaf_n(allocn), dst);
@ -642,7 +704,7 @@ struct node
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
auto dst = make_leaf_n(n1 + n2);
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src1->leaf(), src1->leaf() + n1, dst->leaf());
}
IMMER_CATCH (...) {
@ -650,11 +712,11 @@ struct node
IMMER_RETHROW;
}
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
}
IMMER_CATCH (...) {
destroy_n(dst->leaf(), n1);
detail::destroy_n(dst->leaf(), n1);
heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst);
IMMER_RETHROW;
}
@ -668,7 +730,7 @@ struct node
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
auto dst = make_leaf_e(e);
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src1->leaf(), src1->leaf() + n1, dst->leaf());
}
IMMER_CATCH (...) {
@ -676,11 +738,11 @@ struct node
IMMER_RETHROW;
}
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
}
IMMER_CATCH (...) {
destroy_n(dst->leaf(), n1);
detail::destroy_n(dst->leaf(), n1);
heap::deallocate(max_sizeof_leaf, dst);
IMMER_RETHROW;
}
@ -692,7 +754,7 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
auto dst = make_leaf_e(e);
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src->leaf() + idx, src->leaf() + last, dst->leaf());
}
IMMER_CATCH (...) {
@ -707,7 +769,7 @@ struct node
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
auto dst = make_leaf_n(last - idx);
IMMER_TRY {
std::uninitialized_copy(
detail::uninitialized_copy(
src->leaf() + idx, src->leaf() + last, dst->leaf());
}
IMMER_CATCH (...) {
@ -725,7 +787,7 @@ struct node
new (dst->leaf() + n) T{std::forward<U>(x)};
}
IMMER_CATCH (...) {
destroy_n(dst->leaf(), n);
detail::destroy_n(dst->leaf(), n);
heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst);
IMMER_RETHROW;
}
@ -788,7 +850,7 @@ struct node
static void delete_leaf(node_t* p, count_t n)
{
IMMER_ASSERT_TAGGED(p->kind() == kind_t::leaf);
destroy_n(p->leaf(), n);
detail::destroy_n(p->leaf(), n);
heap::deallocate(ownee(p).owned() ? node_t::max_sizeof_leaf
: node_t::sizeof_leaf_n(n),
p);
@ -814,10 +876,12 @@ struct node
auto dst_r = impl.d.data.inner.relaxed =
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
if (src_r) {
node_t::refs(src_r).dec_unsafe();
auto n = dst_r->d.count = src_r->d.count;
std::copy(
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
if (node_t::refs(src_r).dec())
heap::deallocate(node_t::sizeof_inner_r_n(n),
src_r);
}
node_t::ownee(dst_r) = e;
return dst_r;
@ -839,10 +903,12 @@ struct node
auto dst_r = impl.d.data.inner.relaxed =
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
if (src_r) {
node_t::refs(src_r).dec_unsafe();
auto n = dst_r->d.count = src_r->d.count;
std::copy(
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
if (node_t::refs(src_r).dec())
heap::deallocate(node_t::sizeof_inner_r_n(n),
src_r);
}
node_t::ownee(dst_r) = ec;
return dst_r;
@ -863,9 +929,11 @@ struct node
auto dst_r =
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
if (src_r) {
node_t::refs(src_r).dec_unsafe();
std::copy(
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
if (node_t::refs(src_r).dec())
heap::deallocate(node_t::sizeof_inner_r_n(n),
src_r);
}
dst_r->d.count = n;
node_t::ownee(dst_r) = e;
@ -887,7 +955,6 @@ struct node
}
bool dec() const { return refs(this).dec(); }
void dec_unsafe() const { refs(this).dec_unsafe(); }
static void inc_nodes(node_t** p, count_t n)
{

View File

@ -92,7 +92,7 @@ struct for_each_chunk_visitor : visitor_base<for_each_chunk_visitor>
static void visit_leaf(Pos&& pos, Fn&& fn)
{
auto data = pos.node()->leaf();
fn(data, data + pos.count());
fn(as_const(data), as_const(data) + pos.count());
}
};
@ -109,7 +109,7 @@ struct for_each_chunk_p_visitor : visitor_base<for_each_chunk_p_visitor>
template <typename Pos, typename Fn>
static bool visit_leaf(Pos&& pos, Fn&& fn)
{
auto data = pos.node()->leaf();
auto data = as_const(pos.node()->leaf());
return fn(data, data + pos.count());
}
};
@ -468,9 +468,8 @@ struct update_visitor : visitor_base<update_visitor<NodeT>>
auto node = node_t::make_inner_sr_n(count, pos.relaxed());
IMMER_TRY {
auto child = pos.towards_oh(this_t{}, idx, offset, fn);
node_t::do_copy_inner_sr(node, pos.node(), count);
node->inner()[offset]->dec_unsafe();
node->inner()[offset] = child;
node_t::do_copy_inner_replace_sr(
node, pos.node(), count, offset, child);
return node;
}
IMMER_CATCH (...) {
@ -487,9 +486,8 @@ struct update_visitor : visitor_base<update_visitor<NodeT>>
auto node = node_t::make_inner_n(count);
IMMER_TRY {
auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn);
node_t::do_copy_inner(node, pos.node(), count);
node->inner()[offset]->dec_unsafe();
node->inner()[offset] = child;
node_t::do_copy_inner_replace(
node, pos.node(), count, offset, child);
return node;
}
IMMER_CATCH (...) {
@ -1067,8 +1065,8 @@ struct slice_right_mut_visitor
node->inc();
return std::make_tuple(0, nullptr, new_tail_size, node);
} else if (mutate) {
destroy_n(node->leaf() + new_tail_size,
old_tail_size - new_tail_size);
detail::destroy_n(node->leaf() + new_tail_size,
old_tail_size - new_tail_size);
return std::make_tuple(0, nullptr, new_tail_size, node);
} else {
auto new_tail = node_t::copy_leaf_e(e, node, new_tail_size);
@ -1183,8 +1181,8 @@ struct slice_right_visitor : visitor_base<slice_right_visitor<NodeT, Collapse>>
auto old_tail_size = pos.count();
auto new_tail_size = pos.index(last) + 1;
auto new_tail = new_tail_size == old_tail_size
? pos.node()->inc()
: node_t::copy_leaf(pos.node(), new_tail_size);
? pos.node()->inc()
: node_t::copy_leaf(pos.node(), new_tail_size);
return std::make_tuple(0, nullptr, new_tail_size, new_tail);
}
};
@ -1263,10 +1261,10 @@ struct slice_left_mut_visitor
return r;
} else {
using std::get;
auto newn = mutate ? (node->ensure_mutable_relaxed(e), node)
: node_t::make_inner_r_e(e);
auto newr = newn->relaxed();
auto newcount = count - idx;
auto newn = mutate ? (node->ensure_mutable_relaxed(e), node)
: node_t::make_inner_r_e(e);
auto newr = newn->relaxed();
auto newcount = count - idx;
auto new_child_size = child_size - child_dropped_size;
IMMER_TRY {
auto subs =
@ -1277,9 +1275,9 @@ struct slice_left_mut_visitor
pos.each_left(dec_visitor{}, idx);
pos.copy_sizes(
idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1);
std::uninitialized_copy(node->inner() + idx + 1,
node->inner() + count,
newn->inner() + 1);
std::copy(node->inner() + idx + 1,
node->inner() + count,
newn->inner() + 1);
newn->inner()[0] = get<1>(subs);
newr->d.sizes[0] = new_child_size;
newr->d.count = newcount;
@ -1348,9 +1346,9 @@ struct slice_left_mut_visitor
idx + 1, newcount - 1, newr->d.sizes[0], newr->d.sizes + 1);
newr->d.count = newcount;
newn->inner()[0] = get<1>(subs);
std::uninitialized_copy(node->inner() + idx + 1,
node->inner() + count,
newn->inner() + 1);
std::copy(node->inner() + idx + 1,
node->inner() + count,
newn->inner() + 1);
if (!mutate) {
node_t::inc_nodes(newn->inner() + 1, newcount - 1);
if (Mutating)
@ -1386,7 +1384,7 @@ struct slice_left_mut_visitor
auto data = node->leaf();
auto newcount = count - idx;
std::move(data + idx, data + count, data);
destroy_n(data + newcount, idx);
detail::destroy_n(data + newcount, idx);
return std::make_tuple(0, node);
} else {
auto newn = node_t::copy_leaf_e(e, node, idx, count);
@ -1439,9 +1437,9 @@ struct slice_left_visitor : visitor_base<slice_left_visitor<NodeT, Collapse>>
assert(newr->d.sizes[newr->d.count - 1] ==
pos.size() - dropped_size);
newn->inner()[0] = get<1>(subs);
std::uninitialized_copy(n->inner() + idx + 1,
n->inner() + count,
newn->inner() + 1);
std::copy(n->inner() + idx + 1,
n->inner() + count,
newn->inner() + 1);
node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1);
return std::make_tuple(pos.shift(), newn);
}
@ -1627,9 +1625,9 @@ struct concat_merger
auto data = to_->leaf();
auto to_copy =
std::min(from_count - from_offset, *curr_ - to_offset_);
std::uninitialized_copy(from_data + from_offset,
from_data + from_offset + to_copy,
data + to_offset_);
detail::uninitialized_copy(from_data + from_offset,
from_data + from_offset + to_copy,
data + to_offset_);
to_offset_ += to_copy;
from_offset += to_copy;
if (*curr_ == to_offset_) {
@ -1662,9 +1660,9 @@ struct concat_merger
auto data = to_->inner();
auto to_copy =
std::min(from_count - from_offset, *curr_ - to_offset_);
std::uninitialized_copy(from_data + from_offset,
from_data + from_offset + to_copy,
data + to_offset_);
std::copy(from_data + from_offset,
from_data + from_offset + to_copy,
data + to_offset_);
node_t::inc_nodes(from_data + from_offset, to_copy);
auto sizes = to_->relaxed()->d.sizes;
p.copy_sizes(
@ -2124,10 +2122,10 @@ struct concat_merger_mut
data + to_offset_);
} else {
if (!from_mutate)
std::uninitialized_copy(from_data + from_offset,
from_data + from_offset +
to_copy,
data + to_offset_);
detail::uninitialized_copy(from_data + from_offset,
from_data + from_offset +
to_copy,
data + to_offset_);
else
detail::uninitialized_move(from_data + from_offset,
from_data + from_offset +

View File

@ -15,9 +15,9 @@
#include <immer/detail/type_traits.hpp>
#include <cassert>
#include <stdexcept>
#include <memory>
#include <numeric>
#include <stdexcept>
namespace immer {
namespace detail {
@ -465,7 +465,7 @@ struct rbtree
auto ts = size - tail_off;
auto newts = new_size - tail_off;
if (tail->can_mutate(e)) {
destroy_n(tail->leaf() + newts, ts - newts);
detail::destroy_n(tail->leaf() + newts, ts - newts);
} else {
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
dec_leaf(tail, ts);

View File

@ -49,13 +49,21 @@ struct rrbtree
static node_t* empty_root()
{
static const auto empty_ = node_t::make_inner_n(0u);
static const auto empty_ = []{
constexpr auto size = node_t::sizeof_inner_n(0);
static std::aligned_storage_t<size, alignof(std::max_align_t)> storage;
return node_t::make_inner_n_into(&storage, size, 0u);
}();
return empty_->inc();
}
static node_t* empty_tail()
{
static const auto empty_ = node_t::make_leaf_n(0u);
static const auto empty_ = []{
constexpr auto size = node_t::sizeof_leaf_n(0);
static std::aligned_storage_t<size, alignof(std::max_align_t)> storage;
return node_t::make_leaf_n_into(&storage, size, 0u);
}();
return empty_->inc();
}
@ -90,7 +98,7 @@ struct rrbtree
return result;
}
rrbtree()
rrbtree() noexcept
: size{0}
, shift{BL}
, root{empty_root()}
@ -99,7 +107,7 @@ struct rrbtree
assert(check_tree());
}
rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t)
rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) noexcept
: size{sz}
, shift{sh}
, root{r}
@ -108,13 +116,13 @@ struct rrbtree
assert(check_tree());
}
rrbtree(const rrbtree& other)
rrbtree(const rrbtree& other) noexcept
: rrbtree{other.size, other.shift, other.root, other.tail}
{
inc();
}
rrbtree(rrbtree&& other)
rrbtree(rrbtree&& other) noexcept
: rrbtree{}
{
swap(*this, other);
@ -127,13 +135,13 @@ struct rrbtree
return *this;
}
rrbtree& operator=(rrbtree&& other)
rrbtree& operator=(rrbtree&& other) noexcept
{
swap(*this, other);
return *this;
}
friend void swap(rrbtree& x, rrbtree& y)
friend void swap(rrbtree& x, rrbtree& y) noexcept
{
using std::swap;
swap(x.size, y.size);
@ -574,7 +582,7 @@ struct rrbtree
auto ts = size - tail_off;
auto newts = new_size - tail_off;
if (tail->can_mutate(e)) {
destroy_n(tail->leaf() + newts, ts - newts);
detail::destroy_n(tail->leaf() + newts, ts - newts);
} else {
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
dec_leaf(tail, ts);
@ -793,17 +801,17 @@ struct rrbtree
return;
} else if (tail_size + r.size <= branches<BL>) {
l.ensure_mutable_tail(el, tail_size);
std::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + r.size,
l.tail->leaf() + tail_size);
detail::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + r.size,
l.tail->leaf() + tail_size);
l.size += r.size;
return;
} else {
auto remaining = branches<BL> - tail_size;
l.ensure_mutable_tail(el, tail_size);
std::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + remaining,
l.tail->leaf() + tail_size);
detail::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + remaining,
l.tail->leaf() + tail_size);
IMMER_TRY {
auto new_tail =
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
@ -819,7 +827,7 @@ struct rrbtree
}
}
IMMER_CATCH (...) {
destroy_n(r.tail->leaf() + tail_size, remaining);
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
IMMER_RETHROW;
}
}
@ -1066,9 +1074,9 @@ struct rrbtree
r.tail->leaf() + r.size,
l.tail->leaf() + tail_size);
else
std::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + r.size,
l.tail->leaf() + tail_size);
detail::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + r.size,
l.tail->leaf() + tail_size);
l.size += r.size;
return;
} else {
@ -1079,9 +1087,9 @@ struct rrbtree
r.tail->leaf() + remaining,
l.tail->leaf() + tail_size);
else
std::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + remaining,
l.tail->leaf() + tail_size);
detail::uninitialized_copy(r.tail->leaf(),
r.tail->leaf() + remaining,
l.tail->leaf() + tail_size);
IMMER_TRY {
auto new_tail =
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
@ -1097,7 +1105,7 @@ struct rrbtree
}
}
IMMER_CATCH (...) {
destroy_n(r.tail->leaf() + tail_size, remaining);
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
IMMER_RETHROW;
}
}

View File

@ -201,23 +201,5 @@ struct std_distance_supports<
template <typename T, typename U>
constexpr bool std_distance_supports_v = std_distance_supports<T, U>::value;
template <typename T, typename U, typename V, typename = void>
struct std_uninitialized_copy_supports : std::false_type
{};
template <typename T, typename U, typename V>
struct std_uninitialized_copy_supports<
T,
U,
V,
void_t<decltype(std::uninitialized_copy(
std::declval<T>(), std::declval<U>(), std::declval<V>()))>>
: std::true_type
{};
template <typename T, typename U, typename V>
constexpr bool std_uninitialized_copy_supports_v =
std_uninitialized_copy_supports<T, U, V>::value;
} // namespace detail
} // namespace immer

View File

@ -24,6 +24,18 @@
namespace immer {
namespace detail {
template <typename T>
const T* as_const(T* x)
{
return x;
}
template <typename T>
const T& as_const(T& x)
{
return x;
}
template <typename T>
using aligned_storage_for =
typename std::aligned_storage<sizeof(T), alignof(T)>::type;
@ -39,26 +51,110 @@ T&& auto_const_cast(const T&& x)
return const_cast<T&&>(std::move(x));
}
template <typename Iter1, typename Iter2>
auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out)
template <class T>
inline auto destroy_at(T* p) noexcept
-> std::enable_if_t<std::is_trivially_destructible<T>::value>
{
return std::uninitialized_copy(
std::make_move_iterator(in1), std::make_move_iterator(in2), out);
p->~T();
}
template <class T>
void destroy(T* first, T* last)
inline auto destroy_at(T* p) noexcept
-> std::enable_if_t<!std::is_trivially_destructible<T>::value>
{
for (; first != last; ++first)
first->~T();
p->~T();
}
template <class T, class Size>
void destroy_n(T* p, Size n)
template <typename Iter1>
constexpr bool can_trivially_detroy = std::is_trivially_destructible<
typename std::iterator_traits<Iter1>::value_type>::value;
template <class Iter>
auto destroy(Iter, Iter last) noexcept
-> std::enable_if_t<can_trivially_detroy<Iter>, Iter>
{
auto e = p + n;
for (; p != e; ++p)
p->~T();
return last;
}
template <class Iter>
auto destroy(Iter first, Iter last) noexcept
-> std::enable_if_t<!can_trivially_detroy<Iter>, Iter>
{
for (; first != last; ++first)
detail::destroy_at(std::addressof(*first));
return first;
}
template <class Iter, class Size>
auto destroy_n(Iter first, Size n) noexcept
-> std::enable_if_t<can_trivially_detroy<Iter>, Iter>
{
return first + n;
}
template <class Iter, class Size>
auto destroy_n(Iter first, Size n) noexcept
-> std::enable_if_t<!can_trivially_detroy<Iter>, Iter>
{
for (; n > 0; (void) ++first, --n)
detail::destroy_at(std::addressof(*first));
return first;
}
template <typename Iter1, typename Iter2>
constexpr bool can_trivially_copy =
std::is_same<typename std::iterator_traits<Iter1>::value_type,
typename std::iterator_traits<Iter2>::value_type>::value&&
std::is_trivially_copyable<
typename std::iterator_traits<Iter1>::value_type>::value;
template <typename Iter1, typename Iter2>
auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) noexcept
-> std::enable_if_t<can_trivially_copy<Iter1, Iter2>, Iter2>
{
return std::copy(first, last, out);
}
template <typename Iter1, typename Iter2>
auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out)
-> std::enable_if_t<!can_trivially_copy<Iter1, Iter2>, Iter2>
{
using value_t = typename std::iterator_traits<Iter2>::value_type;
auto current = out;
IMMER_TRY {
for (; first != last; ++first, (void) ++current) {
::new (const_cast<void*>(static_cast<const volatile void*>(
std::addressof(*current)))) value_t(std::move(*first));
}
return current;
}
IMMER_CATCH (...) {
detail::destroy(out, current);
IMMER_RETHROW;
}
}
template <typename SourceIter, typename Sent, typename SinkIter>
auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) noexcept
-> std::enable_if_t<can_trivially_copy<SourceIter, SinkIter>, SinkIter>
{
return std::copy(first, last, out);
}
template <typename SourceIter, typename Sent, typename SinkIter>
auto uninitialized_copy(SourceIter first, Sent last, SinkIter out)
-> std::enable_if_t<!can_trivially_copy<SourceIter, SinkIter>, SinkIter>
{
using value_t = typename std::iterator_traits<SinkIter>::value_type;
auto current = out;
IMMER_TRY {
for (; first != last; ++first, (void) ++current) {
::new (const_cast<void*>(static_cast<const volatile void*>(
std::addressof(*current)))) value_t(*first);
}
return current;
}
IMMER_CATCH (...) {
detail::destroy(out, current);
IMMER_RETHROW;
}
}
template <typename Heap, typename T, typename... Args>
@ -214,53 +310,5 @@ distance(Iterator first, Sentinel last)
return last - first;
}
/*!
* An alias to `std::uninitialized_copy`
*/
template <
typename Iterator,
typename Sentinel,
typename SinkIter,
std::enable_if_t<
detail::std_uninitialized_copy_supports_v<Iterator, Sentinel, SinkIter>,
bool> = true>
SinkIter uninitialized_copy(Iterator first, Sentinel last, SinkIter d_first)
{
return std::uninitialized_copy(first, last, d_first);
}
/*!
* Equivalent of the `std::uninitialized_copy` applied to the
* sentinel-delimited forward range @f$ [first, last) @f$
*/
template <typename SourceIter,
typename Sent,
typename SinkIter,
std::enable_if_t<
(!detail::std_uninitialized_copy_supports_v<SourceIter,
Sent,
SinkIter>) &&detail::
compatible_sentinel_v<SourceIter, Sent> &&
detail::is_forward_iterator_v<SinkIter>,
bool> = true>
SinkIter uninitialized_copy(SourceIter first, Sent last, SinkIter d_first)
{
auto current = d_first;
IMMER_TRY {
while (first != last) {
*current++ = *first;
++first;
}
}
IMMER_CATCH (...) {
using Value = typename std::iterator_traits<SinkIter>::value_type;
for (; d_first != current; ++d_first) {
d_first->~Value();
}
IMMER_RETHROW;
}
return current;
}
} // namespace detail
} // namespace immer

View File

@ -249,7 +249,7 @@ public:
}
/*!
* Returns a flex_vector with `value` inserted at the frony. It may
* Returns a flex_vector with `value` inserted at the front. It may
* allocate memory and its complexity is @f$ O(log(size)) @f$.
*
* @rst
@ -503,6 +503,17 @@ public:
IMMER_NODISCARD transient_type transient() const& { return impl_; }
IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
/*!
* Returns a value that can be used as identity for the container. If two
* values have the same identity, they are guaranteed to be equal and to
* contain the same objects. However, two equal containers are not
* guaranteed to have the same identity.
*/
std::pair<void*, void*> identity() const
{
return {impl_.root, impl_.tail};
}
// Semi-private
const impl_t& impl() const { return impl_; }
@ -605,4 +616,9 @@ private:
impl_t impl_ = {};
};
static_assert(std::is_nothrow_move_constructible<flex_vector<int>>::value,
"flex_vector is not nothrow move constructible");
static_assert(std::is_nothrow_move_assignable<flex_vector<int>>::value,
"flex_vector is not nothrow move assignable");
} // namespace immer

View File

@ -33,7 +33,7 @@ struct cpp_heap
* `allocate`. One must not use nor deallocate again a memory
* region that once it has been deallocated.
*/
static void deallocate(std::size_t size, void* data)
static void deallocate(std::size_t, void* data)
{
::operator delete(data);
}

View File

@ -85,8 +85,7 @@ struct enable_optimized_heap_policy
* @rst
*
* .. tip:: For many applications that use immutable data structures
* significantly, this is actually the best heap policy, and it
* might become the default in the future.
* significantly, this is actually the best heap policy.
*
* Note that most our data structures internally use trees with the
* same big branching factors. This means that all *vectors*,

View File

@ -14,8 +14,8 @@
#include <immer/memory_policy.hpp>
#include <cassert>
#include <stdexcept>
#include <functional>
#include <stdexcept>
namespace immer {
@ -41,7 +41,7 @@ class map_transient;
*
* @rst
*
* This cotainer provides a good trade-off between cache locality,
* This container provides a good trade-off between cache locality,
* search, update performance and structural sharing. It does so by
* storing the data in contiguous chunks of :math:`2^{B}` elements.
* When storing big objects, the size of these contiguous chunks can
@ -68,12 +68,19 @@ class map
{
using value_t = std::pair<K, T>;
using move_t =
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
struct project_value
{
const T& operator()(const value_t& v) const noexcept
{
return v.second;
}
T&& operator()(value_t&& v) const noexcept
{
return std::move(v.second);
}
};
struct project_value_ptr
@ -114,8 +121,11 @@ class map
{
auto operator()(const value_t& v) { return Hash{}(v.first); }
template<typename Key>
auto operator()(const Key& v) { return Hash{}(v); }
template <typename Key>
auto operator()(const Key& v)
{
return Hash{}(v);
}
};
struct equal_key
@ -125,7 +135,7 @@ class map
return Equal{}(a.first, b.first);
}
template<typename Key>
template <typename Key>
auto operator()(const value_t& a, const Key& b)
{
return Equal{}(a.first, b);
@ -160,8 +170,29 @@ public:
using transient_type = map_transient<K, T, Hash, Equal, MemoryPolicy, B>;
using memory_policy_type = MemoryPolicy;
/*!
* Default constructor. It creates a set of `size() == 0`. It
* Constructs a map containing the elements in `values`.
*/
map(std::initializer_list<value_type> values)
: impl_{impl_t::from_initializer_list(values)}
{}
/*!
* Constructs a map containing the elements in the range
* defined by the input iterator `first` and range sentinel `last`.
*/
template <typename Iter,
typename Sent,
std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
bool> = true>
map(Iter first, Sent last)
: impl_{impl_t::from_range(first, last)}
{}
/*!
* Default constructor. It creates a map of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
map() = default;
@ -202,7 +233,9 @@ public:
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const Key& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
@ -229,7 +262,9 @@ public:
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T& operator[](const Key& k) const
{
return impl_.template get<project_value, default_value>(k);
@ -252,7 +287,9 @@ public:
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
const T& at(const Key& k) const
{
return impl_.template get<project_value, error_value>(k);
@ -297,7 +334,7 @@ public:
* ``std::optional<const T&>`` but this construction is not valid
* in any current standard. As a compromise we return a
* pointer, which has similar syntactic properties yet it is
* unfortunatelly unnecessarily unrestricted.
* unfortunately unnecessarily unrestricted.
*
* @endrst
*/
@ -307,7 +344,6 @@ public:
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`. If
* the key is not contained in the map, a `nullptr` is returned.
@ -317,7 +353,9 @@ public:
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const Key& k) const
{
return impl_.template get<project_value_ptr,
@ -325,7 +363,7 @@ public:
}
/*!
* Returns whether the sets are equal.
* Returns whether the maps are equal.
*/
IMMER_NODISCARD bool operator==(const map& other) const
{
@ -342,10 +380,14 @@ public:
* It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
IMMER_NODISCARD map insert(value_type value) const
IMMER_NODISCARD map insert(value_type value) const&
{
return impl_.add(std::move(value));
}
IMMER_NODISCARD decltype(auto) insert(value_type value) &&
{
return insert_move(move_t{}, std::move(value));
}
/*!
* Returns a map containing the association `(k, v)`. If the key
@ -353,10 +395,14 @@ public:
* It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
IMMER_NODISCARD map set(key_type k, mapped_type v) const
IMMER_NODISCARD map set(key_type k, mapped_type v) const&
{
return impl_.add({std::move(k), std::move(v)});
}
IMMER_NODISCARD decltype(auto) set(key_type k, mapped_type v) &&
{
return set_move(move_t{}, std::move(k), std::move(v));
}
/*!
* Returns a map replacing the association `(k, v)` by the
@ -366,22 +412,50 @@ public:
* and its complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
IMMER_NODISCARD map update(key_type k, Fn&& fn) const
IMMER_NODISCARD map update(key_type k, Fn&& fn) const&
{
return impl_
.template update<project_value, default_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) &&
{
return update_move(move_t{}, std::move(k), std::forward<Fn>(fn));
}
/*!
* Returns a map replacing the association `(k, v)` by the association new
* association `(k, fn(v))`, where `v` is the currently associated value for
* `k` in the map. It does nothing if `k` is not present in the map. It
* may allocate memory and its complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const&
{
return impl_.template update_if_exists<project_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) &&
{
return update_if_exists_move(
move_t{}, std::move(k), std::forward<Fn>(fn));
}
/*!
* Returns a map without the key `k`. If the key is not
* associated in the map it returns the same map. It may allocate
* memory and its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD map erase(const K& k) const { return impl_.sub(k); }
IMMER_NODISCARD map erase(const K& k) const& { return impl_.sub(k); }
IMMER_NODISCARD decltype(auto) erase(const K& k) &&
{
return erase_move(move_t{}, k);
}
/*!
* Returns an @a transient form of this container, a
* Returns a @a transient form of this container, an
* `immer::map_transient`.
*/
IMMER_NODISCARD transient_type transient() const&
@ -393,12 +467,79 @@ public:
return transient_type{std::move(impl_)};
}
/*!
* Returns a value that can be used as identity for the container. If two
* values have the same identity, they are guaranteed to be equal and to
* contain the same objects. However, two equal containers are not
* guaranteed to have the same identity.
*/
void* identity() const { return impl_.root; }
// Semi-private
const impl_t& impl() const { return impl_; }
private:
friend transient_type;
map&& insert_move(std::true_type, value_type value)
{
impl_.add_mut({}, std::move(value));
return std::move(*this);
}
map insert_move(std::false_type, value_type value)
{
return impl_.add(std::move(value));
}
map&& set_move(std::true_type, key_type k, mapped_type m)
{
impl_.add_mut({}, {std::move(k), std::move(m)});
return std::move(*this);
}
map set_move(std::false_type, key_type k, mapped_type m)
{
return impl_.add({std::move(k), std::move(m)});
}
template <typename Fn>
map&& update_move(std::true_type, key_type k, Fn&& fn)
{
impl_.template update_mut<project_value, default_value, combine_value>(
{}, std::move(k), std::forward<Fn>(fn));
return std::move(*this);
}
template <typename Fn>
map update_move(std::false_type, key_type k, Fn&& fn)
{
return impl_
.template update<project_value, default_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn)
{
impl_.template update_if_exists_mut<project_value, combine_value>(
{}, std::move(k), std::forward<Fn>(fn));
return std::move(*this);
}
template <typename Fn>
map update_if_exists_move(std::false_type, key_type k, Fn&& fn)
{
return impl_.template update_if_exists<project_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
map&& erase_move(std::true_type, const key_type& value)
{
impl_.sub_mut({}, value);
return std::move(*this);
}
map erase_move(std::false_type, const key_type& value)
{
return impl_.sub(value);
}
map(impl_t impl)
: impl_(std::move(impl))
{}

View File

@ -15,18 +15,21 @@
namespace immer {
template <typename K,
typename T,
typename Hash,
typename Equal,
typename MemoryPolicy,
detail::hamts::bits_t B>
class map;
/*!
* Mutable version of `immer::map`.
*
* @rst
*
* .. admonition:: Become a sponsor!
* :class: danger
*
* This component is planned but it has **not been implemented yet**.
*
* Transiens can critically improve the performance of applications
* intensively using ``set`` and ``map``. If you are working for an
* organization using the library in a commercial project, please consider
* **sponsoring this work**: juanpe@sinusoid.al
* Refer to :doc:`transients` to learn more about when and how to use
* the mutable versions of immutable containers.
*
* @endrst
*/
@ -36,6 +39,280 @@ template <typename K,
typename Equal = std::equal_to<K>,
typename MemoryPolicy = default_memory_policy,
detail::hamts::bits_t B = default_bits>
class map_transient;
class map_transient : MemoryPolicy::transience_t::owner
{
using base_t = typename MemoryPolicy::transience_t::owner;
using owner_t = base_t;
public:
using persistent_type = map<K, T, Hash, Equal, MemoryPolicy, B>;
using key_type = K;
using mapped_type = T;
using value_type = std::pair<K, T>;
using size_type = detail::hamts::size_t;
using diference_type = std::ptrdiff_t;
using hasher = Hash;
using key_equal = Equal;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = typename persistent_type::iterator;
using const_iterator = iterator;
/*!
* Default constructor. It creates a map of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
map_transient() = default;
/*!
* Returns an iterator pointing at the first element of the
* collection. It does not allocate memory and its complexity is
* @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator begin() const { return {impl_}; }
/*!
* Returns an iterator pointing just after the last element of the
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator end() const
{
return {impl_, typename iterator::end_t{}};
}
/*!
* Returns the number of elements in the container. It does
* not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type size() const { return impl_.size; }
/*!
* Returns `true` if there are no elements in the container. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
/*!
* Returns `1` when the key `k` is contained in the map or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const Key& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns `1` when the key `k` is contained in the map or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type count(const K& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If the key is not contained in the map, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T& operator[](const Key& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If the key is not contained in the map, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T& operator[](const K& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If the key is not contained in the map, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
const T& at(const Key& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::error_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If the key is not contained in the map, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
const T& at(const K& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::error_value>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`. If
* the key is not contained in the map, a `nullptr` is returned.
* It does not allocate memory and its complexity is *effectively*
* @f$ O(1) @f$.
*
* @rst
*
* .. admonition:: Why doesn't this function return an iterator?
*
* Associative containers from the C++ standard library provide a
* ``find`` method that returns an iterator pointing to the
* element in the container or ``end()`` when the key is missing.
* In the case of an unordered container, the only meaningful
* thing one may do with it is to compare it with the end, to
* test if the find was succesfull, and dereference it. This
* comparison is cumbersome compared to testing for a non-empty
* optional value. Furthermore, for an immutable container,
* returning an iterator would have some additional performance
* cost, with no benefits otherwise.
*
* In our opinion, this function should return a
* ``std::optional<const T&>`` but this construction is not valid
* in any current standard. As a compromise we return a
* pointer, which has similar syntactic properties yet it is
* unfortunately unnecessarily unrestricted.
*
* @endrst
*/
IMMER_NODISCARD const T* find(const K& k) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`. If
* the key is not contained in the map, a `nullptr` is returned.
* It does not allocate memory and its complexity is *effectively*
* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const Key& k) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Inserts the association `value`. If the key is already in the map, it
* replaces its association in the map. It may allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
void insert(value_type value) { impl_.add_mut(*this, std::move(value)); }
/*!
* Inserts the association `(k, v)`. If the key is already in the map, it
* replaces its association in the map. It may allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
void set(key_type k, mapped_type v)
{
impl_.add_mut(*this, {std::move(k), std::move(v)});
}
/*!
* Replaces the association `(k, v)` by the association new association `(k,
* fn(v))`, where `v` is the currently associated value for `k` in the map
* or a default constructed value otherwise. It may allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
void update(key_type k, Fn&& fn)
{
impl_.template update_mut<typename persistent_type::project_value,
typename persistent_type::default_value,
typename persistent_type::combine_value>(
*this, std::move(k), std::forward<Fn>(fn));
}
/*!
* Replaces the association `(k, v)` by the association new association `(k,
* fn(v))`, where `v` is the currently associated value for `k` in the map
* or does nothing if `k` is not present in the map. It may allocate memory
* and its complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
void update_if_exists(key_type k, Fn&& fn)
{
impl_.template update_if_exists_mut<
typename persistent_type::project_value,
typename persistent_type::combine_value>(
*this, std::move(k), std::forward<Fn>(fn));
}
/*!
* Removes the key `k` from the k. Does nothing if the key is not
* associated in the map. It may allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
void erase(const K& k) { impl_.sub_mut(*this, k); }
/*!
* Returns an @a immutable form of this container, an
* `immer::map`.
*/
IMMER_NODISCARD persistent_type persistent() &
{
this->owner_t::operator=(owner_t{});
return impl_;
}
IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); }
private:
friend persistent_type;
using impl_t = typename persistent_type::impl_t;
map_transient(impl_t impl)
: impl_(std::move(impl))
{}
impl_t impl_ = impl_t::empty();
public:
// Semi-private
const impl_t& impl() const { return impl_; }
};
} // namespace immer

View File

@ -24,7 +24,6 @@ struct no_refcount_policy
void inc() {}
bool dec() { return false; }
void dec_unsafe() {}
bool unique() { return false; }
};

View File

@ -34,12 +34,6 @@ struct refcount_policy
bool dec() { return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); }
void dec_unsafe()
{
assert(refcount.load() > 1);
refcount.fetch_sub(1, std::memory_order_relaxed);
}
bool unique() { return refcount == 1; }
};

View File

@ -31,7 +31,6 @@ struct unsafe_refcount_policy
void inc() { ++refcount; }
bool dec() { return --refcount == 0; }
void dec_unsafe() { --refcount; }
bool unique() { return refcount == 1; }
};

View File

@ -62,13 +62,15 @@ class set
{
using impl_t = detail::hamts::champ<T, Hash, Equal, MemoryPolicy, B>;
using move_t =
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
struct project_value_ptr
{
const T* operator()(const T& v) const noexcept { return &v; }
};
public:
using key_type = T;
using value_type = T;
using size_type = detail::hamts::size_t;
using diference_type = std::ptrdiff_t;
@ -83,12 +85,33 @@ public:
using transient_type = set_transient<T, Hash, Equal, MemoryPolicy, B>;
using memory_policy_type = MemoryPolicy;
/*!
* Default constructor. It creates a set of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
set() = default;
/*!
* Constructs a set containing the elements in `values`.
*/
set(std::initializer_list<value_type> values)
: impl_{impl_t::from_initializer_list(values)}
{}
/*!
* Constructs a set containing the elements in the range
* defined by the input iterator `first` and range sentinel `last`.
*/
template <typename Iter,
typename Sent,
std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
bool> = true>
set(Iter first, Sent last)
: impl_{impl_t::from_range(first, last)}
{}
/*!
* Returns an iterator pointing at the first element of the
* collection. It does not allocate memory and its complexity is
@ -125,7 +148,9 @@ public:
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template<typename K, typename U = Hash, typename = typename U::is_transparent>
template <typename K,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const K& value) const
{
return impl_.template get<detail::constantly<size_type, 1>,
@ -164,7 +189,9 @@ public:
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template<typename K, typename U = Hash, typename = typename U::is_transparent>
template <typename K,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const K& value) const
{
return impl_.template get<project_value_ptr,
@ -188,17 +215,28 @@ public:
* the set, it returns the same set. It may allocate memory and
* its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD set insert(T value) const
IMMER_NODISCARD set insert(T value) const&
{
return impl_.add(std::move(value));
}
IMMER_NODISCARD decltype(auto) insert(T value) &&
{
return insert_move(move_t{}, std::move(value));
}
/*!
* Returns a set without `value`. If the `value` is not in the
* set it returns the same set. It may allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD set erase(const T& value) const { return impl_.sub(value); }
IMMER_NODISCARD set erase(const T& value) const&
{
return impl_.sub(value);
}
IMMER_NODISCARD decltype(auto) erase(const T& value) &&
{
return erase_move(move_t{}, value);
}
/*!
* Returns an @a transient form of this container, a
@ -213,12 +251,40 @@ public:
return transient_type{std::move(impl_)};
}
/*!
* Returns a value that can be used as identity for the container. If two
* values have the same identity, they are guaranteed to be equal and to
* contain the same objects. However, two equal containers are not
* guaranteed to have the same identity.
*/
void* identity() const { return impl_.root; }
// Semi-private
const impl_t& impl() const { return impl_; }
private:
friend transient_type;
set&& insert_move(std::true_type, value_type value)
{
impl_.add_mut({}, std::move(value));
return std::move(*this);
}
set insert_move(std::false_type, value_type value)
{
return impl_.add(std::move(value));
}
set&& erase_move(std::true_type, const value_type& value)
{
impl_.sub_mut({}, value);
return std::move(*this);
}
set erase_move(std::false_type, const value_type& value)
{
return impl_.sub(value);
}
set(impl_t impl)
: impl_(std::move(impl))
{}

View File

@ -16,17 +16,12 @@
namespace immer {
/*!
* Mutable version of `immer::set`.
*
* @rst
*
* .. admonition:: Become a sponsor!
* :class: danger
*
* This component is planned but it has **not been implemented yet**.
*
* Transiens can critically improve the performance of applications
* intensively using ``set`` and ``map``. If you are working for an
* organization using the library in a commercial project, please consider
* **sponsoring this work**: juanpe@sinusoid.al
* Refer to :doc:`transients` to learn more about when and how to use
* the mutable versions of immutable containers.
*
* @endrst
*/
@ -35,6 +30,155 @@ template <typename T,
typename Equal = std::equal_to<T>,
typename MemoryPolicy = default_memory_policy,
detail::hamts::bits_t B = default_bits>
class set_transient;
class set_transient : MemoryPolicy::transience_t::owner
{
using base_t = typename MemoryPolicy::transience_t::owner;
using owner_t = base_t;
public:
using persistent_type = set<T, Hash, Equal, MemoryPolicy, B>;
using value_type = T;
using size_type = detail::hamts::size_t;
using diference_type = std::ptrdiff_t;
using hasher = Hash;
using key_equal = Equal;
using reference = const T&;
using const_reference = const T&;
using iterator = typename persistent_type::iterator;
using const_iterator = iterator;
/*!
* Default constructor. It creates a set of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
set_transient() = default;
/*!
* Returns an iterator pointing at the first element of the
* collection. It does not allocate memory and its complexity is
* @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator begin() const { return {impl_}; }
/*!
* Returns an iterator pointing just after the last element of the
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator end() const
{
return {impl_, typename iterator::end_t{}};
}
/*!
* Returns the number of elements in the container. It does
* not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type size() const { return impl_.size; }
/*!
* Returns `true` if there are no elements in the container. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
/*!
* Returns `1` when `value` is contained in the set or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename K,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const K& value) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(value);
}
/*!
* Returns `1` when `value` is contained in the set or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type count(const T& value) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(value);
}
/*!
* Returns a pointer to the value if `value` is contained in the
* set, or nullptr otherwise.
* It does not allocate memory and its complexity is *effectively*
* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T* find(const T& value) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(value);
}
/*!
* Returns a pointer to the value if `value` is contained in the
* set, or nullptr otherwise.
* It does not allocate memory and its complexity is *effectively*
* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename K,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const K& value) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(value);
}
/*!
* Inserts `value` into the set, and does nothing if the value is already
* there It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
void insert(T value) { impl_.add_mut(*this, std::move(value)); }
/*!
* Removes the `value` from the set, doing nothing if the value is not in
* the set. It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
void erase(const T& value) { impl_.sub_mut(*this, value); }
/*!
* Returns an @a immutable form of this container, an
* `immer::set`.
*/
IMMER_NODISCARD persistent_type persistent() &
{
this->owner_t::operator=(owner_t{});
return impl_;
}
IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); }
private:
friend persistent_type;
using impl_t = typename persistent_type::impl_t;
set_transient(impl_t impl)
: impl_(std::move(impl))
{}
impl_t impl_ = impl_t::empty();
public:
// Semi-private
const impl_t& impl() const { return impl_; }
};
} // namespace immer

547
immer/table.hpp Normal file
View File

@ -0,0 +1,547 @@
#pragma once
#include <immer/config.hpp>
#include <immer/detail/hamts/champ.hpp>
#include <immer/detail/hamts/champ_iterator.hpp>
#include <immer/memory_policy.hpp>
#include <type_traits>
namespace immer {
template <typename T,
typename KeyFn,
typename Hash,
typename Equal,
typename MemoryPolicy,
detail::hamts::bits_t B>
class table_transient;
/*!
* Function template to get the key in `immer::table_key_fn`.
* It assumes the key is `id` class member.
*/
template <typename T>
auto get_table_key(T const& x) -> decltype(x.id)
{
return x.id;
}
/*!
* Function template to set the key in `immer::table_key_fn`.
* It assumes the key is `id` class member.
*/
template <typename T, typename K>
auto set_table_key(T x, K&& k) -> T
{
x.id = std::forward<K>(k);
return x;
}
/*!
* Default value for `KeyFn` in `immer::table`.
* It assumes the key is `id` class member.
*/
struct table_key_fn
{
template <typename T>
decltype(auto) operator()(T&& x) const
{
return get_table_key(std::forward<T>(x));
}
template <typename T, typename K>
auto operator()(T&& x, K&& k) const
{
return set_table_key(std::forward<T>(x), std::forward<K>(k));
}
};
template <typename KeyFn, typename T>
using table_key_t = std::decay_t<decltype(KeyFn{}(std::declval<T>()))>;
/*!
* Immutable unordered set of values of type `T`. Values are indexed via
* `operator()(const T&)` from `KeyFn` template parameter.
* By default, key is `&T::id`.
*
* @tparam T The type of the values to be stored in the container.
* @tparam KeyFn Type which implements `operator()(const T&)`
* @tparam Hash The type of a function object capable of hashing
* values of type `T`.
* @tparam Equal The type of a function object capable of comparing
* values of type `T`.
* @tparam MemoryPolicy Memory management policy. See @ref
* memory_policy.
*
* @rst
*
* This container is based on the `immer::map` underlying data structure.
*
* This container provides a good trade-off between cache locality,
* search, update performance and structural sharing. It does so by
* storing the data in contiguous chunks of :math:`2^{B}` elements.
* When storing big objects, the size of these contiguous chunks can
* become too big, damaging performance. If this is measured to be
* problematic for a specific use-case, it can be solved by using a
* `immer::box` to wrap the type `T`.
*
* **Example**
* .. literalinclude:: ../example/table/intro.cpp
* :language: c++
* :start-after: intro/start
* :end-before: intro/end
*
* @endrst
*
*/
template <typename T,
typename KeyFn = table_key_fn,
typename Hash = std::hash<table_key_t<KeyFn, T>>,
typename Equal = std::equal_to<table_key_t<KeyFn, T>>,
typename MemoryPolicy = default_memory_policy,
detail::hamts::bits_t B = default_bits>
class table
{
using K = table_key_t<KeyFn, T>;
using value_t = T;
using move_t =
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
struct project_value
{
const T& operator()(const value_t& v) const noexcept { return v; }
T&& operator()(value_t&& v) const noexcept { return std::move(v); }
};
struct project_value_ptr
{
const T* operator()(const value_t& v) const noexcept
{
return std::addressof(v);
}
};
struct combine_value
{
template <typename Kf, typename Tf>
auto operator()(Kf&& k, Tf&& v) const
{
return KeyFn{}(std::forward<Tf>(v), std::forward<Kf>(k));
}
};
struct default_value
{
const T& operator()() const
{
static T v{};
return v;
}
};
struct error_value
{
const T& operator()() const
{
IMMER_THROW(std::out_of_range{"key not found"});
}
};
struct hash_key
{
std::size_t operator()(const value_t& v) const
{
return Hash{}(KeyFn{}(v));
}
template <typename Key>
std::size_t operator()(const Key& v) const
{
return Hash{}(v);
}
};
struct equal_key
{
bool operator()(const value_t& a, const value_t& b) const
{
auto ke = KeyFn{};
return Equal{}(ke(a), ke(b));
}
template <typename Key>
bool operator()(const value_t& a, const Key& b) const
{
return Equal{}(KeyFn{}(a), b);
}
};
struct equal_value
{
bool operator()(const value_t& a, const value_t& b) const
{
return a == b;
}
};
using impl_t =
detail::hamts::champ<value_t, hash_key, equal_key, MemoryPolicy, B>;
public:
using key_type = K;
using mapped_type = T;
using value_type = T;
using size_type = detail::hamts::size_t;
using diference_type = std::ptrdiff_t;
using hasher = Hash;
using key_equal = Equal;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = detail::hamts::
champ_iterator<value_t, hash_key, equal_key, MemoryPolicy, B>;
using const_iterator = iterator;
using transient_type =
table_transient<T, KeyFn, Hash, Equal, MemoryPolicy, B>;
using memory_policy_type = MemoryPolicy;
/*!
* Constructs a table containing the elements in `values`.
*/
table(std::initializer_list<value_type> values)
: impl_{impl_t::from_initializer_list(values)}
{}
/*!
* Constructs a table containing the elements in the range
* defined by the input iterator `first` and range sentinel `last`.
*/
template <typename Iter,
typename Sent,
std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
bool> = true>
table(Iter first, Sent last)
: impl_{impl_t::from_range(first, last)}
{}
/*!
* Default constructor. It creates a table of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
table() = default;
/*!
* Returns an iterator pointing at the first element of the
* collection. It does not allocate memory and its complexity is
* @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator begin() const { return {impl_}; }
/*!
* Returns an iterator pointing just after the last element of the
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator end() const
{
return {impl_, typename iterator::end_t{}};
}
/*!
* Returns the number of elements in the container. It does
* not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type size() const { return impl_.size; }
/*!
* Returns `true` if there are no elements in the container. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
/*!
* Returns `1` when the key `k` is contained in the table or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const Key& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns `1` when the key `k` is contained in the table or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type count(const K& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T& operator[](const Key& k) const
{
return impl_.template get<project_value, default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T& operator[](const K& k) const
{
return impl_.template get<project_value, default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
const T& at(const Key& k) const
{
return impl_.template get<project_value, error_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
const T& at(const K& k) const
{
return impl_.template get<project_value, error_value>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`.
* If there is no entry with such a key in the table,
* a `nullptr` is returned. It does not allocate memory and
* its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T* find(const K& k) const
{
return impl_.template get<project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`.
* If there is no entry with such a key in the table,
* a `nullptr` is returned. It does not allocate memory and
* its complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const Key& k) const
{
return impl_.template get<project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
IMMER_NODISCARD bool operator==(const table& other) const
{
return impl_.template equals<equal_value>(other.impl_);
}
IMMER_NODISCARD bool operator!=(const table& other) const
{
return !(*this == other);
}
/*!
* Returns a table containing the `value`.
* If there is an entry with its key is already,
* it replaces this entry by `value`.
* It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
IMMER_NODISCARD table insert(value_type value) const&
{
return impl_.add(std::move(value));
}
/*!
* Returns a table containing the `value`.
* If there is an entry with its key is already,
* it replaces this entry by `value`.
* It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
IMMER_NODISCARD decltype(auto) insert(value_type value) &&
{
return insert_move(move_t{}, std::move(value));
}
/*!
* Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps
* `T` to `T`. The key `k` will be replaced inside the value returned by
* `fn`. It may allocate memory and its complexity is *effectively* @f$ O(1)
* @f$.
*/
template <typename Fn>
IMMER_NODISCARD table update(key_type k, Fn&& fn) const&
{
return impl_
.template update<project_value, default_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) &&
{
return update_move(move_t{}, std::move(k), std::forward<Fn>(fn));
}
/*!
* Returns `this.count(k) ? this->insert(fn((*this)[k])) : *this`. In
* particular, `fn` maps `T` to `T`. The key `k` will be replaced inside the
* value returned by `fn`. It may allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
IMMER_NODISCARD table update_if_exists(key_type k, Fn&& fn) const&
{
return impl_.template update_if_exists<project_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) &&
{
return update_if_exists_move(
move_t{}, std::move(k), std::forward<Fn>(fn));
}
/*!
* Returns a table without entries with given key `k`. If the key is not
* present it returns `*this`. It may allocate
* memory and its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD table erase(const K& k) const& { return impl_.sub(k); }
/*!
* Returns a table without entries with given key `k`. If the key is not
* present it returns `*this`. It may allocate
* memory and its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD decltype(auto) erase(const K& k) &&
{
return erase_move(move_t{}, k);
}
/*!
* Returns a @a transient form of this container, an
* `immer::table_transient`.
*/
IMMER_NODISCARD transient_type transient() const&
{
return transient_type{impl_};
}
/*!
* Returns a @a transient form of this container, an
* `immer::table_transient`.
*/
IMMER_NODISCARD transient_type transient() &&
{
return transient_type{std::move(impl_)};
}
// Semi-private
const impl_t& impl() const { return impl_; }
private:
friend transient_type;
table&& insert_move(std::true_type, value_type value)
{
impl_.add_mut({}, std::move(value));
return std::move(*this);
}
table insert_move(std::false_type, value_type value)
{
return impl_.add(std::move(value));
}
template <typename Fn>
table&& update_move(std::true_type, key_type k, Fn&& fn)
{
impl_.template update_mut<project_value, default_value, combine_value>(
{}, std::move(k), std::forward<Fn>(fn));
return std::move(*this);
}
template <typename Fn>
table update_move(std::false_type, key_type k, Fn&& fn)
{
return impl_
.template update<project_value, default_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
template <typename Fn>
table&& update_if_exists_move(std::true_type, key_type k, Fn&& fn)
{
impl_.template update_if_exists_mut<project_value, combine_value>(
{}, std::move(k), std::forward<Fn>(fn));
return std::move(*this);
}
template <typename Fn>
table update_if_exists_move(std::false_type, key_type k, Fn&& fn)
{
return impl_.template update_if_exists<project_value, combine_value>(
std::move(k), std::forward<Fn>(fn));
}
table&& erase_move(std::true_type, const key_type& value)
{
impl_.sub_mut({}, value);
return std::move(*this);
}
table erase_move(std::false_type, const key_type& value)
{
return impl_.sub(value);
}
table(impl_t impl)
: impl_(std::move(impl))
{}
impl_t impl_ = impl_t::empty();
};
} // namespace immer

281
immer/table_transient.hpp Normal file
View File

@ -0,0 +1,281 @@
#pragma once
#include <immer/detail/hamts/champ.hpp>
#include <immer/memory_policy.hpp>
#include <type_traits>
namespace immer {
template <typename T,
typename KeyFn,
typename Hash,
typename Equal,
typename MemoryPolicy,
detail::hamts::bits_t B>
class table;
/*!
* Mutable version of `immer::table`.
*
* @rst
*
* Refer to :doc:`transients` to learn more about when and how to use
* the mutable versions of immutable containers.
*
* @endrst
*/
template <typename T,
typename KeyFn,
typename Hash,
typename Equal,
typename MemoryPolicy,
detail::hamts::bits_t B>
class table_transient : MemoryPolicy::transience_t::owner
{
using K = std::decay_t<decltype(KeyFn{}(std::declval<T>()))>;
using base_t = typename MemoryPolicy::transience_t::owner;
using owner_t = base_t;
public:
using persistent_type = table<T, KeyFn, Hash, Equal, MemoryPolicy, B>;
using key_type = K;
using mapped_type = T;
using value_type = T;
using size_type = detail::hamts::size_t;
using diference_type = std::ptrdiff_t;
using hasher = Hash;
using key_equal = Equal;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = typename persistent_type::iterator;
using const_iterator = iterator;
/*!
* Default constructor. It creates a table of `size() == 0`. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
table_transient() = default;
/*!
* Returns an iterator pointing at the first element of the
* collection. It does not allocate memory and its complexity is
* @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator begin() const { return {impl_}; }
/*!
* Returns an iterator pointing just after the last element of the
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD iterator end() const
{
return {impl_, typename iterator::end_t{}};
}
/*!
* Returns the number of elements in the container. It does
* not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type size() const { return impl_.size; }
/*!
* Returns `true` if there are no elements in the container. It
* does not allocate memory and its complexity is @f$ O(1) @f$.
*/
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
/*!
* Returns `1` when the key `k` is contained in the table or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD size_type count(const Key& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns `1` when the key `k` is contained in the table or `0`
* otherwise. It won't allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD size_type count(const K& k) const
{
return impl_.template get<detail::constantly<size_type, 1>,
detail::constantly<size_type, 0>>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T& operator[](const Key& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, it returns a
* default constructed value. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T& operator[](const K& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::default_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
const T& at(const Key& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::error_value>(k);
}
/*!
* Returns a `const` reference to the values associated to the key
* `k`. If there is no entry with such a key in the table, throws an
* `std::out_of_range` error. It does not allocate memory and its
* complexity is *effectively* @f$ O(1) @f$.
*/
const T& at(const K& k) const
{
return impl_.template get<typename persistent_type::project_value,
typename persistent_type::error_value>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`.
* If there is no entry with such a key in the table,
* a `nullptr` is returned. It does not allocate memory and
* its complexity is *effectively* @f$ O(1) @f$.
*/
IMMER_NODISCARD const T* find(const K& k) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Returns a pointer to the value associated with the key `k`.
* If there is no entry with such a key in the table,
* a `nullptr` is returned. It does not allocate memory and
* its complexity is *effectively* @f$ O(1) @f$.
*
* This overload participates in overload resolution only if
* `Hash::is_transparent` is valid and denotes a type.
*/
template <typename Key,
typename U = Hash,
typename = typename U::is_transparent>
IMMER_NODISCARD const T* find(const Key& k) const
{
return impl_.template get<typename persistent_type::project_value_ptr,
detail::constantly<const T*, nullptr>>(k);
}
/*!
* Inserts `value` to the table.
* If there is an entry with its key is already,
* it replaces this entry by `value`.
* It may allocate memory and its complexity is *effectively* @f$
* O(1) @f$.
*/
void insert(value_type value) { impl_.add_mut(*this, std::move(value)); }
/*!
* Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps `T` to
* `T`. The key `k` will be set into the value returned bu `fn`. It may
* allocate memory and its complexity is *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
void update(key_type k, Fn&& fn)
{
impl_.template update_mut<typename persistent_type::project_value,
typename persistent_type::default_value,
typename persistent_type::combine_value>(
*this, std::move(k), std::forward<Fn>(fn));
}
/*!
* Returns `this->insert(fn((*this)[k]))` when `this->count(k) > 0`. In
* particular, `fn` maps `T` to `T`. The key `k` will be replaced into the
* value returned by `fn`. It may allocate memory and its complexity is
* *effectively* @f$ O(1) @f$.
*/
template <typename Fn>
void update_if_exists(key_type k, Fn&& fn)
{
impl_.template update_if_exists_mut<
typename persistent_type::project_value,
typename persistent_type::combine_value>(
*this, std::move(k), std::forward<Fn>(fn));
}
/*!
* Removes table entry by given key `k` if there is any. It may allocate
* memory and its complexity is *effectively* @f$ O(1) @f$.
*/
void erase(const K& k) { impl_.sub_mut(*this, k); }
/*!
* Returns an @a immutable form of this container, an
* `immer::table`.
*/
IMMER_NODISCARD persistent_type persistent() &
{
this->owner_t::operator=(owner_t{});
return impl_;
}
/*!
* Returns an @a immutable form of this container, an
* `immer::table`.
*/
IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); }
private:
friend persistent_type;
using impl_t = typename persistent_type::impl_t;
table_transient(impl_t impl)
: impl_(std::move(impl))
{}
impl_t impl_ = impl_t::empty();
public:
// Semi-private
const impl_t& impl() const { return impl_; }
};
} // namespace immer

View File

@ -40,7 +40,7 @@ class vector_transient;
*
* @rst
*
* This cotainer provides a good trade-off between cache locality,
* This container provides a good trade-off between cache locality,
* random access, update performance and structural sharing. It does
* so by storing the data in contiguous chunks of :math:`2^{BL}`
* elements. By default, when ``sizeof(T) == sizeof(void*)`` then
@ -340,6 +340,17 @@ public:
IMMER_NODISCARD transient_type transient() const& { return impl_; }
IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
/*!
* Returns a value that can be used as identity for the container. If two
* values have the same identity, they are guaranteed to be equal and to
* contain the same objects. However, two equal containers are not
* guaranteed to have the same identity.
*/
std::pair<void*, void*> identity() const
{
return {impl_.root, impl_.tail};
}
// Semi-private
const impl_t& impl() const { return impl_; }

View File

@ -49,6 +49,7 @@ tc.stdenv.mkDerivation rec {
buildInputs = [
tc.cc
git
catch2
cmake
pkgconfig
ninja
@ -58,6 +59,7 @@ tc.stdenv.mkDerivation rec {
boost
boehmgc
fmt
valgrind
benchmarks.c_rrb
benchmarks.steady
benchmarks.chunkedseq

4
spm.cpp Normal file
View File

@ -0,0 +1,4 @@
#include <immer/vector.hpp>
// Just so we can compile with SPM.

View File

@ -16,8 +16,7 @@ foreach(_file IN LISTS immer_unit_tests)
add_dependencies(tests ${_target})
target_compile_definitions(${_target} PUBLIC
-DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data"
DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
CATCH_CONFIG_MAIN)
target_link_libraries(${_target} PUBLIC immer-dev)
target_link_libraries(${_target} PUBLIC immer-dev Catch2::Catch2)
add_test("test/${_output}" ${_output})
endforeach()

221
test/algorithm.cpp Normal file
View File

@ -0,0 +1,221 @@
#include <immer/array.hpp>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include <immer/set.hpp>
#include <immer/table.hpp>
#include <immer/vector.hpp>
#include <immer/algorithm.hpp>
#include <catch2/catch.hpp>
struct thing
{
int id = 0;
};
bool operator==(const thing& a, const thing& b) { return a.id == b.id; }
TEST_CASE("iteration exposes const")
{
auto do_check = [](auto v) {
using value_t = typename decltype(v)::value_type;
immer::for_each(v, [](auto&& x) {
static_assert(std::is_same<const value_t&, decltype(x)>::value, "");
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
do_check(immer::map<int, int>{});
do_check(immer::set<int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("chunked iteration exposes const")
{
auto do_check = [](auto v) {
using value_t = typename decltype(v)::value_type;
immer::for_each_chunk(v, [](auto a, auto b) {
static_assert(std::is_same<const value_t*, decltype(a)>::value, "");
static_assert(std::is_same<const value_t*, decltype(b)>::value, "");
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
do_check(immer::map<int, int>{});
do_check(immer::set<int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("accumulate")
{
auto do_check = [](auto v) {
using value_t = typename decltype(v)::value_type;
immer::accumulate(v, value_t{}, [](auto&& a, auto&& b) {
static_assert(std::is_same<value_t&&, decltype(a)>::value, "");
static_assert(std::is_same<const value_t&, decltype(b)>::value, "");
return std::move(a);
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
do_check(immer::map<int, int>{});
do_check(immer::set<int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("diffing exposes const")
{
auto do_check = [](auto v) {
using value_t = typename decltype(v)::value_type;
immer::diff(
v,
v,
[](auto&& x) {
static_assert(std::is_same<const value_t&, decltype(x)>::value,
"");
},
[](auto&& x) {
static_assert(std::is_same<const value_t&, decltype(x)>::value,
"");
},
[](auto&& x, auto&& y) {
static_assert(std::is_same<const value_t&, decltype(x)>::value,
"");
static_assert(std::is_same<const value_t&, decltype(y)>::value,
"");
});
};
do_check(immer::map<int, int>{});
do_check(immer::set<int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("all_of")
{
auto do_check = [](auto v) {
using value_t = typename decltype(v)::value_type;
immer::all_of(v, [](auto&& x) {
static_assert(std::is_same<const value_t&, decltype(x)>::value, "");
return true;
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
// not supported
// do_check(immer::map<int, int>{});
// do_check(immer::set<int>{});
// do_check(immer::table<thing>{});
}
TEST_CASE("update vectors")
{
auto do_check = [](auto v) {
if (false)
(void) v.update(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// vectors do copy first the whole array, and then move the
// copied value into the function
static_assert(std::is_same<type_t&&, decltype(x)>::value, "");
return x;
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
}
TEST_CASE("update maps")
{
auto do_check = [](auto v) {
(void) v.update(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// for maps, we actually do not make a copy at all but pase the
// original instance directly, as const..
static_assert(std::is_same<const type_t&, decltype(x)>::value, "");
return x;
});
};
do_check(immer::map<int, int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("update_if_exists maps")
{
auto do_check = [](auto v) {
(void) v.update_if_exists(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// for maps, we actually do not make a copy at all but pase the
// original instance directly, as const..
static_assert(std::is_same<const type_t&, decltype(x)>::value, "");
return x;
});
};
do_check(immer::map<int, int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("update vectors move")
{
auto do_check = [](auto v) {
if (false)
(void) std::move(v).update(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// vectors do copy first the whole array, and then move the
// copied value into the function
static_assert(std::is_same<type_t&&, decltype(x)>::value, "");
return x;
});
};
do_check(immer::vector<int>{});
do_check(immer::flex_vector<int>{});
do_check(immer::array<int>{});
}
TEST_CASE("update maps move")
{
auto do_check = [](auto v) {
(void) std::move(v).update(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// for maps, we actually do not make a copy at all but pase the
// original instance directly, as const..
static_assert(std::is_same<const type_t&, decltype(x)>::value ||
std::is_same<type_t&&, decltype(x)>::value,
"");
return x;
});
};
do_check(immer::map<int, int>{});
do_check(immer::table<thing>{});
}
TEST_CASE("update_if_exists maps move")
{
auto do_check = [](auto v) {
(void) std::move(v).update_if_exists(0, [](auto&& x) {
using type_t = std::decay_t<decltype(x)>;
// for maps, we actually do not make a copy at all but pase the
// original instance directly, as const..
static_assert(std::is_same<const type_t&, decltype(x)>::value ||
std::is_same<type_t&&, decltype(x)>::value,
"");
return x;
});
};
do_check(immer::map<int, int>{});
do_check(immer::table<thing>{});
}

View File

@ -14,6 +14,10 @@
#include "../vector_transient/generic.ipp"
IMMER_RANGES_CHECK(std::ranges::contiguous_range<immer::array<std::string>>);
IMMER_RANGES_CHECK(
std::ranges::contiguous_range<immer::array_transient<std::string>>);
TEST_CASE("array_transient default constructor compiles")
{
immer::array_transient<int> transient;

View File

@ -10,7 +10,7 @@
#error "define the box template to use in ATOM_T"
#endif
#include <catch.hpp>
#include <catch2/catch.hpp>
template <typename T>
using BOX_T = typename ATOM_T<T>::box_type;

View File

@ -10,7 +10,7 @@
#error "define the box template to use in BOX_T"
#endif
#include <catch.hpp>
#include <catch2/catch.hpp>
TEST_CASE("construction and copy")
{

View File

@ -13,7 +13,7 @@
#include <immer/set.hpp>
#include <immer/vector.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
struct rec_vec
{

View File

@ -10,7 +10,7 @@
#include <immer/vector.hpp>
#include <immer/vector_transient.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
TEST_CASE("issue-33")
{

View File

@ -6,7 +6,7 @@
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <forward_list>
#include <immer/detail/type_traits.hpp>
#include <list>

View File

@ -15,7 +15,7 @@
#include <vector>
#include <iostream>
#include <doctest.h>
#include <catch2/catch.hpp>
using namespace immer;
@ -27,7 +27,7 @@ TEST_CASE("instantiation")
TEST_CASE("push back one element")
{
SUBCASE("one element")
SECTION("one element")
{
const auto v1 = dvektor<int>{};
auto v2 = v1.push_back(42);
@ -36,7 +36,7 @@ TEST_CASE("push back one element")
CHECK(v2[0] == 42);
}
SUBCASE("many elements")
SECTION("many elements")
{
const auto n = 666u;
auto v = dvektor<unsigned>{};
@ -56,7 +56,7 @@ TEST_CASE("update")
for (auto i = 0u; i < n; ++i)
v = v.push_back(i);
SUBCASE("assoc")
SECTION("assoc")
{
const auto u = v.assoc(3u, 13u);
CHECK(u.size() == v.size());
@ -67,7 +67,7 @@ TEST_CASE("update")
CHECK(v[3u] == 3u);
}
SUBCASE("assoc further")
SECTION("assoc further")
{
for (auto i = n; i < 666; ++i)
v = v.push_back(i);
@ -88,7 +88,7 @@ TEST_CASE("update")
CHECK(v[200u] == 200u);
}
SUBCASE("assoc further more")
SECTION("assoc further more")
{
auto v = immer::dvektor<unsigned, 4>{};
@ -101,7 +101,7 @@ TEST_CASE("update")
}
}
SUBCASE("update")
SECTION("update")
{
const auto u = v.update(10u, [](auto x) { return x + 10; });
CHECK(u.size() == v.size());
@ -123,13 +123,13 @@ TEST_CASE("big")
for (auto i = 0u; i < n; ++i)
v = v.push_back(i);
SUBCASE("read")
SECTION("read")
{
for (auto i = 0u; i < n; ++i)
CHECK(v[i] == i);
}
SUBCASE("assoc")
SECTION("assoc")
{
for (auto i = 0u; i < n; ++i) {
v = v.assoc(i, i + 1);
@ -146,7 +146,7 @@ TEST_CASE("iterator")
for (auto i = 0u; i < n; ++i)
v = v.push_back(i);
SUBCASE("works with range loop")
SECTION("works with range loop")
{
auto i = 0u;
for (const auto& x : v)
@ -154,16 +154,16 @@ TEST_CASE("iterator")
CHECK(i == v.size());
}
SUBCASE("works with standard algorithms")
SECTION("works with standard algorithms")
{
auto s = std::vector<unsigned>(n);
std::iota(s.begin(), s.end(), 0u);
std::equal(v.begin(), v.end(), s.begin(), s.end());
}
SUBCASE("can go back from end") { CHECK(n - 1 == *--v.end()); }
SECTION("can go back from end") { CHECK(n - 1 == *--v.end()); }
SUBCASE("works with reversed range adaptor")
SECTION("works with reversed range adaptor")
{
auto r = v | boost::adaptors::reversed;
auto i = n;
@ -171,7 +171,7 @@ TEST_CASE("iterator")
CHECK(x == --i);
}
SUBCASE("works with strided range adaptor")
SECTION("works with strided range adaptor")
{
auto r = v | boost::adaptors::strided(5);
auto i = 0u;
@ -179,14 +179,14 @@ TEST_CASE("iterator")
CHECK(x == 5 * i++);
}
SUBCASE("works reversed")
SECTION("works reversed")
{
auto i = n;
for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter)
CHECK(*iter == --i);
}
SUBCASE("advance and distance")
SECTION("advance and distance")
{
auto i1 = v.begin();
auto i2 = i1 + 100;

View File

@ -8,7 +8,7 @@
#include "extra/fuzzer/fuzzer_input.hpp"
#include <array>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <immer/flex_vector.hpp>
#include <iostream>
@ -174,7 +174,7 @@ TEST_CASE("bug: concatenate too big vectors")
var4 = var4.push_back(42);
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
// Assertion `!p->relaxed()' failed
SECTION("")
{

View File

@ -8,7 +8,7 @@
#include "extra/fuzzer/fuzzer_input.hpp"
#include <array>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <immer/flex_vector.hpp>
#include <iostream>
@ -159,7 +159,7 @@ TEST_CASE("bug: memory leak because of move update")
var0 = std::move(var0).push_back(21);
}
#if __GNUC__ != 9 && __GNUC__ != 8
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {
@ -263,7 +263,8 @@ TEST_CASE("non-bug: crash")
var4 = var4 + var4;
var4 = var4.update(4, [](auto x) { return x + 1; });
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {

View File

@ -8,7 +8,7 @@
#include "extra/fuzzer/fuzzer_input.hpp"
#include <array>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <immer/flex_vector.hpp>
#include <iostream>
@ -172,7 +172,7 @@ TEST_CASE("bug: use after free on move-take")
var0 = std::move(var0).take(68);
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {

View File

@ -8,7 +8,7 @@
#include "extra/fuzzer/fuzzer_input.hpp"
#include <array>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <immer/flex_vector.hpp>
#include <iostream>
@ -205,7 +205,7 @@ TEST_CASE("bug: concat with moving the right side")
var0 = var0 + std::move(var1);
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("vm")
{
constexpr std::uint8_t input[] = {

View File

@ -8,7 +8,7 @@
#include "extra/fuzzer/fuzzer_input.hpp"
#include <array>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <immer/flex_vector.hpp>
#include <immer/flex_vector_transient.hpp>
#include <immer/heap/gc_heap.hpp>
@ -231,7 +231,7 @@ TEST_CASE("bug: concatenating transients")
t1.append(t0);
}
#if __GNUC__ != 9 && __GNUC__ != 8
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {
@ -271,7 +271,7 @@ TEST_CASE("bug: concatenating moved transients")
t2.append(std::move(t0));
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {
@ -309,7 +309,7 @@ TEST_CASE("bug: concatenating moved transients")
t0 = {};
}
#if __GNUC__ != 9 && __GNUC__ != 8
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
return;
@ -348,7 +348,7 @@ TEST_CASE("bug: aegsdas")
t1 = {};
}
#if __GNUC__ != 9 && __GNUC__ != 8
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
SECTION("")
{
constexpr std::uint8_t input[] = {

View File

@ -14,7 +14,7 @@
#include <boost/range/adaptors.hpp>
#include <boost/range/irange.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <algorithm>
#include <array>
@ -29,6 +29,9 @@
#error "define the vector template to use in VECTOR_T"
#endif
IMMER_RANGES_CHECK(
std::ranges::random_access_range<FLEX_VECTOR_T<std::string>>);
template <typename V = FLEX_VECTOR_T<unsigned>>
auto make_test_flex_vector(unsigned min, unsigned max)
{
@ -106,6 +109,18 @@ TEST_CASE("push_front")
}
}
TEST_CASE("random_access iteration")
{
auto v = make_test_flex_vector(0, 10);
auto iter = v.begin();
CHECK(*iter == 0);
CHECK(iter[0] == 0);
CHECK(iter[3] == 3);
CHECK(iter[9] == 9);
iter += 4;
CHECK(iter[-4] == 0);
}
TEST_CASE("concat")
{
#if IMMER_SLOW_TESTS

View File

@ -13,7 +13,7 @@
#include <immer/vector.hpp>
#include <immer/vector_transient.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
#if IMMER_CXX_STANDARD >= 17

View File

@ -14,7 +14,7 @@
#include <boost/range/adaptors.hpp>
#include <boost/range/irange.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <algorithm>
#include <array>
@ -33,6 +33,11 @@
#error "define the vector template to use in VECTOR_T"
#endif
IMMER_RANGES_CHECK(
std::ranges::random_access_range<FLEX_VECTOR_T<std::string>>);
IMMER_RANGES_CHECK(
std::ranges::random_access_range<FLEX_VECTOR_TRANSIENT_T<std::string>>);
template <typename V = VECTOR_T<unsigned>>
auto make_test_flex_vector(unsigned min, unsigned max)
{

View File

@ -6,6 +6,8 @@
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//
#define IMMER_DEBUG_STATS 1
#include <immer/map.hpp>
#define MAP_T ::immer::map

View File

@ -23,4 +23,5 @@ template <typename K,
using test_map_t = immer::map<K, T, Hash, Eq, gc_memory, 3u>;
#define MAP_T test_map_t
#define IMMER_IS_LIBGC_TEST 1
#include "generic.ipp"

View File

@ -13,15 +13,21 @@
#endif
#include <immer/algorithm.hpp>
#include <immer/box.hpp>
#include "test/dada.hpp"
#include "test/util.hpp"
#include <catch.hpp>
#include <catch2/catch.hpp>
#include <random>
#include <unordered_map>
#include <unordered_set>
IMMER_RANGES_CHECK(std::ranges::forward_range<MAP_T<std::string, std::string>>);
using memory_policy_t = MAP_T<unsigned, unsigned>::memory_policy_type;
template <typename T = unsigned>
auto make_generator()
{
@ -66,7 +72,7 @@ auto make_test_map(unsigned n)
{
auto s = MAP_T<unsigned, unsigned>{};
for (auto i = 0u; i < n; ++i)
s = s.insert({i, i});
s = std::move(s).insert({i, i});
return s;
}
@ -74,7 +80,7 @@ auto make_test_map(const std::vector<std::pair<conflictor, unsigned>>& vals)
{
auto s = MAP_T<conflictor, unsigned, hash_conflictor>{};
for (auto&& v : vals)
s = s.insert(v);
s = std::move(s).insert(v);
return s;
}
@ -84,6 +90,7 @@ TEST_CASE("instantiation")
{
auto v = MAP_T<int, int>{};
CHECK(v.size() == 0u);
CHECK(v.identity() == MAP_T<int, int>{}.identity());
}
}
@ -102,6 +109,19 @@ TEST_CASE("basic insertion")
CHECK(v3.count(42) == 1);
}
TEST_CASE("initializer list and range constructors")
{
auto v0 = std::unordered_map<std::string, int>{
{{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}};
auto v1 = MAP_T<std::string, int>{
{{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}};
auto v2 = MAP_T<std::string, int>{v0.begin(), v0.end()};
CHECK(v1.size() == 4);
CHECK(v1.count(std::string{"foo"}) == 1);
CHECK(v1.at(std::string{"bar"}) == 13);
CHECK(v1 == v2);
}
TEST_CASE("accessor")
{
const auto n = 666u;
@ -151,8 +171,39 @@ TEST_CASE("equals and setting")
CHECK(v.set(1234, 42) == v.insert({1234, 42}));
CHECK(v.update(1234, [](auto&& x) { return x + 1; }) == v.set(1234, 1));
CHECK(v.update(42, [](auto&& x) { return x + 1; }) == v.set(42, 43));
CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }) == v);
CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }) ==
v.set(42, 43));
CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }).identity() ==
v.identity());
CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }).identity() !=
v.set(42, 43).identity());
#if IMMER_DEBUG_STATS
std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats())
.get_summary();
#endif
}
#if IMMER_DEBUG_STATS
TEST_CASE("debug stats")
{
{
std::cout
<< immer::map<int, int>{}.impl().get_debug_stats().get_summary();
}
{
immer::map<int, int> map;
for (int i = 0; i <= 10; i++) {
map = std::move(map).set(i, i);
}
std::cout << map.impl().get_debug_stats().get_summary();
}
}
#endif
TEST_CASE("iterator")
{
const auto N = 666u;
@ -216,12 +267,158 @@ TEST_CASE("update a lot")
{
auto v = make_test_map(666u);
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = v.update(i, [](auto&& x) { return x + 1; });
CHECK(v[i] == i + 1);
SECTION("immutable")
{
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = v.update(i, [](auto&& x) { return x + 1; });
CHECK(v[i] == i + 1);
}
}
SECTION("move")
{
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = std::move(v).update(i, [](auto&& x) { return x + 1; });
CHECK(v[i] == i + 1);
}
}
SECTION("erase")
{
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = std::move(v).erase(i);
CHECK(v.count(i) == 0);
}
}
}
TEST_CASE("update_if_exists a lot")
{
auto v = make_test_map(666u);
SECTION("immutable")
{
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = v.update_if_exists(i, [](auto&& x) { return x + 1; });
CHECK(v[i] == i + 1);
}
}
SECTION("move")
{
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
v = std::move(v).update_if_exists(i,
[](auto&& x) { return x + 1; });
CHECK(v[i] == i + 1);
}
}
}
#if !IMMER_IS_LIBGC_TEST
TEST_CASE("update boxed move string")
{
constexpr auto N = 666u;
constexpr auto S = 7;
auto s = MAP_T<std::string, immer::box<std::string, memory_policy_t>>{};
SECTION("preserve immutability")
{
auto s0 = s;
auto i0 = 0u;
// insert
for (auto i = 0u; i < N; ++i) {
if (i % S == 0) {
s0 = s;
i0 = i;
}
s = std::move(s).update(std::to_string(i),
[&](auto&&) { return std::to_string(i); });
{
CHECK(s.size() == i + 1);
for (auto j : test_irange(0u, i + 1)) {
CHECK(s.count(std::to_string(j)) == 1);
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
}
for (auto j : test_irange(i + 1u, N))
CHECK(s.count(std::to_string(j)) == 0);
}
{
CHECK(s0.size() == i0);
for (auto j : test_irange(0u, i0)) {
CHECK(s0.count(std::to_string(j)) == 1);
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
}
for (auto j : test_irange(i0, N))
CHECK(s0.count(std::to_string(j)) == 0);
}
}
// update
for (auto i = 0u; i < N; ++i) {
if (i % S == 0) {
s0 = s;
i0 = i;
}
s = std::move(s).update(std::to_string(i), [&](auto&&) {
return std::to_string(i + 1);
});
{
CHECK(s.size() == N);
for (auto j : test_irange(0u, i + 1))
CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1));
for (auto j : test_irange(i + 1u, N))
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
}
{
CHECK(s0.size() == N);
for (auto j : test_irange(0u, i0))
CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1));
for (auto j : test_irange(i0, N))
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
}
}
}
}
#endif
#if !IMMER_IS_LIBGC_TEST
TEST_CASE("update_if_exists boxed move string")
{
constexpr auto N = 666u;
constexpr auto S = 7;
auto s = MAP_T<std::string, immer::box<std::string, memory_policy_t>>{};
SECTION("preserve immutability")
{
auto s0 = s;
auto i0 = 0u;
// insert
for (auto i = 0u; i < N; ++i) {
s = std::move(s).set(std::to_string(i), std::to_string(i));
}
// update
for (auto i = 0u; i < N; ++i) {
if (i % S == 0) {
s0 = s;
i0 = i;
}
s = std::move(s).update_if_exists(std::to_string(i), [&](auto&&) {
return std::to_string(i + 1);
});
{
CHECK(s.size() == N);
for (auto j : test_irange(0u, i + 1))
CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1));
for (auto j : test_irange(i + 1u, N))
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
}
{
CHECK(s0.size() == N);
for (auto j : test_irange(0u, i0))
CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1));
for (auto j : test_irange(i0, N))
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
}
}
}
}
#endif
TEST_CASE("exception safety")
{
constexpr auto n = 2666u;
@ -231,12 +428,12 @@ TEST_CASE("exception safety")
using dadaist_conflictor_map_t = typename dadaist_wrapper<
MAP_T<conflictor, unsigned, hash_conflictor>>::type;
SECTION("update collisions")
SECTION("update")
{
auto v = dadaist_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = v.set(i, i);
v = std::move(v).set(i, i);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
@ -252,6 +449,27 @@ TEST_CASE("exception safety")
IMMER_TRACE_E(d.happenings);
}
SECTION("update_if_exists")
{
auto v = dadaist_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = std::move(v).set(i, i);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
v = v.update_if_exists(i, [](auto x) { return x + 1; });
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(i) == i + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(i) == i);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("update collisisions")
{
auto vals = make_values_with_collisions(n);
@ -273,6 +491,143 @@ TEST_CASE("exception safety")
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("update_if_exists collisisions")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = v.insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
v = v.update_if_exists(vals[i].first,
[](auto x) { return x + 1; });
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(vals[i].first) == vals[i].second + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("set collisisions")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = v.insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
auto x = vals[i].second;
v = v.set(vals[i].first, x + 1);
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(vals[i].first) == vals[i].second + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("set collisisions move")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = v.insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
auto x = vals[i].second;
v = std::move(v).set(vals[i].first, x + 1);
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(vals[i].first) == vals[i].second + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("update collisisions move")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = std::move(v).insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
v = std::move(v).update(vals[i].first,
[](auto x) { return x + 1; });
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(vals[i].first) == vals[i].second + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("update_if_exists collisisions move")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = std::move(v).insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
auto s = d.next();
v = std::move(v).update_if_exists(vals[i].first,
[](auto x) { return x + 1; });
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.at(vals[i].first) == vals[i].second + 1);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings > 0);
IMMER_TRACE_E(d.happenings);
}
SECTION("erase collisisions move")
{
auto vals = make_values_with_collisions(n);
auto v = dadaist_conflictor_map_t{};
auto d = dadaism{};
for (auto i = 0u; i < n; ++i)
v = std::move(v).insert(vals[i]);
for (auto i = 0u; i < v.size();) {
try {
// auto s = d.next();
v = std::move(v).erase(vals[i].first);
++i;
} catch (dada_error) {}
for (auto i : test_irange(0u, i))
CHECK(v.count(vals[i].first) == 0);
for (auto i : test_irange(i, n))
CHECK(v.at(vals[i].first) == vals[i].second);
}
CHECK(d.happenings == 0);
IMMER_TRACE_E(d.happenings);
}
}
namespace {
@ -363,3 +718,75 @@ TEST_CASE("issue 134")
}
} // namespace
void test_diff(unsigned old_num,
unsigned add_num,
unsigned remove_num,
unsigned change_num)
{
auto values = make_values_with_collisions(old_num + add_num);
std::vector<std::pair<conflictor, unsigned>> initial_values(
values.begin(), values.begin() + old_num);
std::vector<std::pair<conflictor, unsigned>> new_values(
values.begin() + old_num, values.end());
auto map = make_test_map(initial_values);
std::vector<conflictor> old_keys;
for (auto const& val : map)
old_keys.push_back(val.first);
auto first_snapshot = map;
CHECK(old_num == first_snapshot.size());
// remove
auto shuffle = old_keys;
std::random_shuffle(shuffle.begin(), shuffle.end());
std::vector<conflictor> remove_keys(shuffle.begin(),
shuffle.begin() + remove_num);
std::vector<conflictor> rest_keys(shuffle.begin() + remove_num,
shuffle.end());
using key_set = std::unordered_set<conflictor, hash_conflictor>;
key_set removed_keys(remove_keys.begin(), remove_keys.end());
for (auto const& key : remove_keys)
map = map.erase(key);
CHECK(old_num - remove_num == map.size());
// add
key_set added_keys;
for (auto const& data : new_values) {
map = map.set(data.first, data.second);
added_keys.insert(data.first);
}
// change
key_set changed_keys;
for (auto i = 0u; i < change_num; i++) {
auto key = rest_keys[i];
map = map.update(key, [](auto val) { return ++val; });
changed_keys.insert(key);
}
diff(
first_snapshot,
map,
[&](auto const& data) { REQUIRE(added_keys.erase(data.first) > 0); },
[&](auto const& data) { REQUIRE(removed_keys.erase(data.first) > 0); },
[&](auto const& old_data, auto const& new_data) {
(void) old_data;
REQUIRE(changed_keys.erase(new_data.first) > 0);
});
CHECK(added_keys.empty());
CHECK(changed_keys.empty());
CHECK(removed_keys.empty());
}
TEST_CASE("diff")
{
test_diff(16, 10, 10, 3);
test_diff(100, 10, 10, 10);
test_diff(1500, 10, 1000, 100);
test_diff(16, 1500, 10, 3);
test_diff(100, 0, 0, 50);
}

View File

@ -13,7 +13,7 @@
#include <immer/map.hpp>
#include <immer/vector.hpp>
#include <catch.hpp>
#include <catch2/catch.hpp>
TEST_CASE("const map")
{

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