mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 11:32:46 +01:00
Merge #6377: depends: update 'src/immer' to arximboldi/immer@5875f773 as c0b716f2
f18e839b2f
build: drop symlinks in immer subtree (Kittywhiskers Van Gogh)d761111f6c
build: fix gitian builds (Kittywhiskers Van Gogh)a9f46b32c3
Squashed 'src/immer/' changes from 9cb6a5a845..5875f7739a (Kittywhiskers Van Gogh)e4ee302d6e
revert: fix gitian builds (Kittywhiskers Van Gogh)fb00300f30
revert: drop symlinks in immer subtree (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Dependency for https://github.com/dashpay/dash/pull/6380 https://github.com/dashpay/dash/pull/6380 will be using `std::ranges` as a replacement for [dash#4622](https://github.com/dashpay/dash/pull/4622) and currently, it will fail to compile due to the current version of `immer` (last updated two years ago in https://github.com/dashpay/dash/pull/4911) not meeting constraints ([source](https://en.cppreference.com/w/cpp/language/constraints)) set by `std::ranges` (see below). ``` ./evo/deterministicmns.h:243:16: error: no matching function for call to object of type 'const __count_if_fn' return ranges::count_if(mnMap, [](const auto& p) { return IsMNValid(*p.second); }); ^~~~~~~~~~~~~~~~ /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/ranges_algo.h:331:7: note: candidate template ignored: substitution failure [with _Range = const MnMap &, _Proj = identity, _Pred = (lambda at ./evo/deterministicmns.h:243:40)]: constraints not satisfied for alias template 'range_difference_t' [with _Range = const immer::map<uint256, std::shared_ptr<const CDeterministicMN>, CDeterministicMNList::ImmerHasher> &] operator()(_Range&& __r, _Pred __pred, _Proj __proj = {}) const ^ /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/ranges_algo.h:316:7: note: candidate function template not viable: requires at least 3 arguments, but 2 were provided operator()(_Iter __first, _Sent __last, ^ ``` This has been resolved by updating `immer` to the latest available release, [v0.8.1](https://github.com/arximboldi/immer/releases/tag/v0.8.1). Expected subtree hash **without changes** are `a5ded361aec714bc74149909c5e1984c10ab132c4c539c63fe57362dccd95556` (see [here](https://github.com/dashpay/dash/pull/6323#pullrequestreview-2357380766) for verification instructions). ## Breaking Changes None expected. ## Checklist - [x] I have performed a self-review of my own code **(note: N/A)** - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests **(note: N/A)** - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACKf18e839b2f
UdjinM6: utACKf18e839b2f
Tree-SHA512: 99d1b577eb4cf3fcc3ebfd28f19006700f085d23ccdabe802a2aa5844e4b39314347b0c0e49a352c5fc034d6584a0109edf08fa3c0846514967f1fb16e7f127a
This commit is contained in:
commit
a8e2316d6f
2
src/immer/.gdbinit
Normal file
2
src/immer/.gdbinit
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Load pretty printers
|
||||||
|
source tools/gdb_pretty_printers/autoload.py
|
26
src/immer/.github/workflows/cifuzz.yml
vendored
Normal file
26
src/immer/.github/workflows/cifuzz.yml
vendored
Normal 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
|
35
src/immer/.github/workflows/test.yml
vendored
35
src/immer/.github/workflows/test.yml
vendored
@ -7,27 +7,33 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # needed for fetchGit in default.nix
|
fetch-depth: 0 # needed for fetchGit in default.nix
|
||||||
- uses: cachix/install-nix-action@v12
|
- uses: cachix/install-nix-action@v20
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
- uses: cachix/cachix-action@v12
|
||||||
- uses: cachix/cachix-action@v8
|
|
||||||
with:
|
with:
|
||||||
name: arximboldi
|
name: arximboldi
|
||||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
||||||
- run: nix-build
|
- run: nix-build
|
||||||
|
|
||||||
|
build-spm:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: swift build
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: cachix/install-nix-action@v12
|
- uses: cachix/install-nix-action@v20
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
- uses: cachix/cachix-action@v12
|
||||||
- uses: cachix/cachix-action@v8
|
|
||||||
with:
|
with:
|
||||||
name: arximboldi
|
name: arximboldi
|
||||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
||||||
@ -35,12 +41,12 @@ jobs:
|
|||||||
- run: nix-shell --run "cd build && cmake .."
|
- run: nix-shell --run "cd build && cmake .."
|
||||||
- run: nix-shell --run "cd build && make docs"
|
- run: nix-shell --run "cd build && make docs"
|
||||||
- uses: shimataro/ssh-key-action@v2
|
- uses: shimataro/ssh-key-action@v2
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi'
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
||||||
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
||||||
- run: nix-shell --run "cd build && make upload-docs"
|
- 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:
|
check:
|
||||||
strategy:
|
strategy:
|
||||||
@ -74,7 +80,7 @@ jobs:
|
|||||||
- type: Debug
|
- type: Debug
|
||||||
toolchain: llvm-8
|
toolchain: llvm-8
|
||||||
std: 14
|
std: 14
|
||||||
opts: ['sanitizer']
|
opts: ['sanitize']
|
||||||
# benchmarks
|
# benchmarks
|
||||||
- type: Release
|
- type: Release
|
||||||
toolchain: gnu-9
|
toolchain: gnu-9
|
||||||
@ -87,11 +93,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: cachix/install-nix-action@v12
|
- uses: cachix/install-nix-action@v20
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
- uses: cachix/cachix-action@v12
|
||||||
- uses: cachix/cachix-action@v8
|
|
||||||
with:
|
with:
|
||||||
name: arximboldi
|
name: arximboldi
|
||||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
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 "cd build && make check -j`nproc`"
|
||||||
- run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "bash <(curl -s https://codecov.io/bash)"
|
- 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
|
- uses: shimataro/ssh-key-action@v2
|
||||||
if: ${{ contains(matrix.opts, 'benchmark') }}
|
if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi'
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
||||||
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
||||||
- run: nix-shell --run "cd build && make upload-benchmark-reports"
|
- 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
src/immer/.gitignore
vendored
5
src/immer/.gitignore
vendored
@ -22,3 +22,8 @@ __pycache__
|
|||||||
tools/clojure/.lein*
|
tools/clojure/.lein*
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
/result*
|
||||||
|
|
||||||
|
.build
|
||||||
|
.swiftpm
|
||||||
|
@ -4,7 +4,7 @@ cmake_policy(SET CMP0048 NEW) # enable project VERSION
|
|||||||
cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile()
|
cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile()
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
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)
|
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")
|
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(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer")
|
||||||
install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
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...
|
# development target to be used in tests, examples, benchmarks...
|
||||||
immer_canonicalize_cmake_booleans(
|
immer_canonicalize_cmake_booleans(
|
||||||
DISABLE_FREE_LIST
|
DISABLE_FREE_LIST
|
||||||
@ -131,6 +139,8 @@ endif()
|
|||||||
if (immer_BUILD_TESTS)
|
if (immer_BUILD_TESTS)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
find_package(Catch2 REQUIRED)
|
||||||
|
|
||||||
add_custom_target(check
|
add_custom_target(check
|
||||||
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
|
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
29
src/immer/Package.swift
Normal file
29
src/immer/Package.swift
Normal 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
|
||||||
|
)
|
@ -1,6 +1,6 @@
|
|||||||
.. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg
|
.. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg
|
||||||
:target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster
|
: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
|
.. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg
|
||||||
:target: https://codecov.io/gh/arximboldi/immer
|
:target: https://codecov.io/gh/arximboldi/immer
|
||||||
@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
.. raw:: html
|
.. 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
|
.. include:introduction/start
|
||||||
|
|
||||||
@ -74,7 +77,7 @@ Example
|
|||||||
For a **complete example** check `Ewig, a simple didactic
|
For a **complete example** check `Ewig, a simple didactic
|
||||||
text-editor <https://github.com/arximboldi/ewig>`_ built with this
|
text-editor <https://github.com/arximboldi/ewig>`_ built with this
|
||||||
library. You may also wanna check `Lager, a Redux-like library
|
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.
|
software in C++ using a value-oriented design.
|
||||||
|
|
||||||
|
|
||||||
|
85
src/immer/benchmark/set/erase.hpp
Normal file
85
src/immer/benchmark/set/erase.hpp
Normal 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
src/immer/benchmark/set/erase.ipp
Normal file
44
src/immer/benchmark/set/erase.ipp
Normal 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
|
@ -10,9 +10,10 @@
|
|||||||
|
|
||||||
#include "benchmark/config.hpp"
|
#include "benchmark/config.hpp"
|
||||||
|
|
||||||
#include <immer/set.hpp>
|
|
||||||
#include <hash_trie.hpp> // Phil Nash
|
|
||||||
#include <boost/container/flat_set.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 <set>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@ -21,8 +22,7 @@ namespace {
|
|||||||
template <typename Generator, typename Set>
|
template <typename Generator, typename Set>
|
||||||
auto benchmark_insert_mut_std()
|
auto benchmark_insert_mut_std()
|
||||||
{
|
{
|
||||||
return [] (nonius::chronometer meter)
|
return [](nonius::chronometer meter) {
|
||||||
{
|
|
||||||
auto n = meter.param<N>();
|
auto n = meter.param<N>();
|
||||||
auto g = Generator{}(n);
|
auto g = Generator{}(n);
|
||||||
|
|
||||||
@ -38,8 +38,7 @@ auto benchmark_insert_mut_std()
|
|||||||
template <typename Generator, typename Set>
|
template <typename Generator, typename Set>
|
||||||
auto benchmark_insert()
|
auto benchmark_insert()
|
||||||
{
|
{
|
||||||
return [] (nonius::chronometer meter)
|
return [](nonius::chronometer meter) {
|
||||||
{
|
|
||||||
auto n = meter.param<N>();
|
auto n = meter.param<N>();
|
||||||
auto g = Generator{}(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
|
} // namespace
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
using generator__ = GENERATOR_T;
|
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::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("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__>>())
|
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>>())
|
NONIUS_BENCHMARK("immer::set/GC", benchmark_insert<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
|
||||||
#endif
|
#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/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
|
||||||
|
5
src/immer/benchmark/set/memory/basic-string-long.cpp
Normal file
5
src/immer/benchmark/set/memory/basic-string-long.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_basic(); }
|
5
src/immer/benchmark/set/memory/basic-string-short.cpp
Normal file
5
src/immer/benchmark/set/memory/basic-string-short.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_basic(); }
|
5
src/immer/benchmark/set/memory/basic-unsigned.cpp
Normal file
5
src/immer/benchmark/set/memory/basic-unsigned.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_basic(); }
|
5
src/immer/benchmark/set/memory/exp-string-long.cpp
Normal file
5
src/immer/benchmark/set/memory/exp-string-long.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_exp(); }
|
5
src/immer/benchmark/set/memory/exp-string-short.cpp
Normal file
5
src/immer/benchmark/set/memory/exp-string-short.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_exp(); }
|
5
src/immer/benchmark/set/memory/exp-unsigned.cpp
Normal file
5
src/immer/benchmark/set/memory/exp-unsigned.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_exp(); }
|
5
src/immer/benchmark/set/memory/lin-string-long.cpp
Normal file
5
src/immer/benchmark/set/memory/lin-string-long.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_lin(); }
|
5
src/immer/benchmark/set/memory/lin-string-short.cpp
Normal file
5
src/immer/benchmark/set/memory/lin-string-short.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_lin(); }
|
5
src/immer/benchmark/set/memory/lin-unsigned.cpp
Normal file
5
src/immer/benchmark/set/memory/lin-unsigned.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
int main() { return main_lin(); }
|
451
src/immer/benchmark/set/memory/memory.hpp
Normal file
451
src/immer/benchmark/set/memory/memory.hpp
Normal 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;
|
||||||
|
}
|
11
src/immer/benchmark/set/string-box/erase.cpp
Normal file
11
src/immer/benchmark/set/string-box/erase.cpp
Normal 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"
|
11
src/immer/benchmark/set/string-long/erase.cpp
Normal file
11
src/immer/benchmark/set/string-long/erase.cpp
Normal 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"
|
11
src/immer/benchmark/set/string-short/erase.cpp
Normal file
11
src/immer/benchmark/set/string-short/erase.cpp
Normal 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"
|
11
src/immer/benchmark/set/unsigned/erase.cpp
Normal file
11
src/immer/benchmark/set/unsigned/erase.cpp
Normal 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"
|
@ -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)
|
|
@ -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 {
|
stdenv.mkDerivation rec {
|
||||||
name = "immer-git";
|
name = "immer-git";
|
||||||
version = "git";
|
version = "git";
|
||||||
src = fetchGit ./.;
|
src = srcFilter ./.;
|
||||||
nativeBuildInputs = [ cmake ];
|
nativeBuildInputs = [ cmake ];
|
||||||
dontBuild = true;
|
dontBuild = true;
|
||||||
|
dontUseCmakeBuildDir = true;
|
||||||
|
cmakeFlags = [
|
||||||
|
"-Dimmer_BUILD_TESTS=OFF"
|
||||||
|
"-Dimmer_BUILD_EXAMPLES=OFF"
|
||||||
|
];
|
||||||
meta = {
|
meta = {
|
||||||
homepage = "https://github.com/arximboldi/immer";
|
homepage = "https://github.com/arximboldi/immer";
|
||||||
description = "Postmodern immutable data structures for C++";
|
description = "Postmodern immutable data structures for C++";
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
add_custom_target(doxygen
|
add_custom_target(doxygen
|
||||||
COMMAND doxygen doxygen.config
|
COMMAND doxygen doxygen.config
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
add_custom_target(docs
|
add_custom_target(docs
|
||||||
COMMAND make html
|
COMMAND make html
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
add_dependencies(docs doxygen)
|
add_dependencies(docs doxygen)
|
||||||
|
|
||||||
set(immer_ssh_method
|
set(immer_ssh_method
|
||||||
@ -18,5 +18,5 @@ set(immer_ssh_method
|
|||||||
add_custom_target(upload-docs
|
add_custom_target(upload-docs
|
||||||
COMMAND
|
COMMAND
|
||||||
rsync -av -e \"${immer_ssh_method}\"
|
rsync -av -e \"${immer_ssh_method}\"
|
||||||
${CMAKE_SOURCE_DIR}/doc/_build/html/*
|
${CMAKE_CURRENT_SOURCE_DIR}/_build/html/*
|
||||||
raskolnikov@sinusoid.es:public/immer/)
|
raskolnikov@sinusoid.es:public/immer/)
|
||||||
|
@ -48,3 +48,10 @@ map
|
|||||||
.. doxygenclass:: immer::map
|
.. doxygenclass:: immer::map
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
|
table
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. doxygenclass:: immer::table
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
@ -18,7 +18,7 @@ internally, common data with other objects. We sometimes refer to
|
|||||||
this property as **structural sharing**. This behaviour is
|
this property as **structural sharing**. This behaviour is
|
||||||
transparent to the user.
|
transparent to the user.
|
||||||
|
|
||||||
Assigment
|
Assignment
|
||||||
---------
|
---------
|
||||||
|
|
||||||
We are sorry, we lied. These containers provide *one mutating
|
We are sorry, we lied. These containers provide *one mutating
|
||||||
@ -79,14 +79,14 @@ removing it, as in:
|
|||||||
:end-before: move-good/end
|
:end-before: move-good/end
|
||||||
|
|
||||||
So, is it bad style then to use ``const`` as much as possible? I
|
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.
|
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
|
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
|
(*Almost Always use Auto*). This way, it is easy to look for
|
||||||
mutations by looking for lines that contain ``=`` but no ``auto``.
|
mutations by looking for lines that contain ``=`` but no ``auto``.
|
||||||
Remember that when using our immutable containers ``operator=`` is the
|
Remember that when using our immutable containers ``operator=`` is the
|
||||||
only way to mutate a variable.
|
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?
|
.. 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
|
If you don't like this syntax, :doc:`transients<transients>` may be
|
||||||
used to obtain similar performance benefits.
|
used to obtain similar performance benefits.
|
||||||
|
|
||||||
.. admonition:: Assigment guarantees
|
.. admonition:: Assignment guarantees
|
||||||
|
|
||||||
From the language point of view, the only requirement on moved from
|
From the language point of view, the only requirement on moved from
|
||||||
values is that they should still be destructible. We provide the
|
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
|
Sometimes you may write a function that needs to do multiple changes
|
||||||
to a container. Like most code you write with this library, this
|
to a container. Like most code you write with this library, this
|
||||||
function is *pure*: it takes one container value in, and produces a
|
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
|
Let's say we want to write a function that inserts all integers in the
|
||||||
range :math:`[first, last)` into an immutable vector:
|
range :math:`[first, last)` into an immutable vector:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Memory management
|
Memory management
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Memory management is specially important for immutable data
|
Memory management is especially important for immutable data
|
||||||
structures. This is mainly due to:
|
structures. This is mainly due to:
|
||||||
|
|
||||||
#. In order to preserve the old value, new memory has to be allocated
|
#. In order to preserve the old value, new memory has to be allocated
|
||||||
@ -39,7 +39,7 @@ Memory policy
|
|||||||
Example: tracing garbage collection
|
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.
|
:cpp:class:`immer::memory_policy` are not completely orthogonal.
|
||||||
|
|
||||||
Let's say you want to use a `tracing garbage collector`_. Actually, we
|
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
|
.. _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.
|
and ``void deallocate(void*)`` that return and release raw memory.
|
||||||
For a canonical model of this concept check the
|
For a canonical model of this concept check the
|
||||||
:cpp:class:`immer::cpp_heap`.
|
: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
|
On the other hand, having some **scoped state** does make
|
||||||
sense for some use-cases of immutable data structures. For
|
sense for some use-cases of immutable data structures. For
|
||||||
example, we might want to support variations of `region
|
example, we might want to support variations of `region
|
||||||
based allocation`_. This interface might evolve to evolve
|
based allocation`_. This interface might evolve to support
|
||||||
to support some kind of non-global state to accommodate
|
some kind of non-global state to accommodate
|
||||||
these use cases.
|
these use cases.
|
||||||
|
|
||||||
.. _region based allocation: https://en.wikipedia.org/wiki/Region-based_memory_management
|
.. _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
|
Inspired by `Andrei Alexandrescu's talk on allocators <allocation
|
||||||
vexation>`_ and `Emery Berger's heap layers <heap layers>`_ we provide
|
vexation>`_ and `Emery Berger's heap layers <heap layers>`_ we provide
|
||||||
allocator adaptors that can be combined using C++ mixins. These
|
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
|
provide application specific optimizations on top of general
|
||||||
allocators.
|
allocators.
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ Transients
|
|||||||
==========
|
==========
|
||||||
|
|
||||||
*Transients* is a concept borrowed `from Clojure
|
*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
|
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.
|
hood.
|
||||||
|
|
||||||
These can be useful for :ref:`performing efficient batch
|
These can be useful for :ref:`performing efficient batch
|
||||||
@ -49,3 +49,10 @@ map_transient
|
|||||||
.. doxygenclass:: immer::map_transient
|
.. doxygenclass:: immer::map_transient
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
|
table_transient
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. doxygenclass:: immer::table_transient
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
29
src/immer/example/table/intro.cpp
Normal file
29
src/immer/example/table/intro.cpp
Normal 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
|
@ -12,6 +12,10 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#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
|
struct no_more_input : std::exception
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#include <immer/map.hpp>
|
#include <immer/map.hpp>
|
||||||
#include <immer/refcount/no_refcount_policy.hpp>
|
#include <immer/refcount/no_refcount_policy.hpp>
|
||||||
|
|
||||||
|
#include <immer/algorithm.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
using gc_memory = immer::memory_policy<immer::heap_policy<immer::gc_heap>,
|
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_erase_move,
|
||||||
op_iterate,
|
op_iterate,
|
||||||
op_find,
|
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 src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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; });
|
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
136
src/immer/extra/fuzzer/map-st-str-conflict.cpp
Normal file
136
src/immer/extra/fuzzer/map-st-str-conflict.cpp
Normal 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
src/immer/extra/fuzzer/map-st-str.cpp
Normal file
135
src/immer/extra/fuzzer/map-st-str.cpp
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <immer/map.hpp>
|
#include <immer/map.hpp>
|
||||||
|
|
||||||
|
#include <immer/algorithm.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
|
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_erase_move,
|
||||||
op_iterate,
|
op_iterate,
|
||||||
op_find,
|
op_find,
|
||||||
op_update
|
op_update,
|
||||||
|
op_update_move,
|
||||||
|
op_diff
|
||||||
};
|
};
|
||||||
auto src = read<char>(in, is_valid_var);
|
auto src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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; });
|
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <immer/map.hpp>
|
#include <immer/map.hpp>
|
||||||
|
|
||||||
|
#include <immer/algorithm.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
struct colliding_hash_t
|
struct colliding_hash_t
|
||||||
@ -37,7 +39,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
|||||||
op_erase_move,
|
op_erase_move,
|
||||||
op_iterate,
|
op_iterate,
|
||||||
op_find,
|
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 src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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; });
|
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
|||||||
op_erase,
|
op_erase,
|
||||||
op_insert_move,
|
op_insert_move,
|
||||||
op_erase_move,
|
op_erase_move,
|
||||||
op_iterate
|
op_iterate,
|
||||||
|
op_diff
|
||||||
};
|
};
|
||||||
auto src = read<char>(in, is_valid_var);
|
auto src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
108
src/immer/extra/fuzzer/set-st-str-conflict.cpp
Normal file
108
src/immer/extra/fuzzer/set-st-str-conflict.cpp
Normal 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
src/immer/extra/fuzzer/set-st-str.cpp
Normal file
108
src/immer/extra/fuzzer/set-st-str.cpp
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
@ -12,6 +12,8 @@
|
|||||||
#include <immer/refcount/no_refcount_policy.hpp>
|
#include <immer/refcount/no_refcount_policy.hpp>
|
||||||
#include <immer/set.hpp>
|
#include <immer/set.hpp>
|
||||||
|
|
||||||
|
#include <immer/algorithm.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
|
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_erase,
|
||||||
op_insert_move,
|
op_insert_move,
|
||||||
op_erase_move,
|
op_erase_move,
|
||||||
op_iterate
|
op_iterate,
|
||||||
|
op_diff
|
||||||
};
|
};
|
||||||
auto src = read<char>(in, is_valid_var);
|
auto src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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: {
|
case op_erase_move: {
|
||||||
auto value = read<size_t>(in);
|
auto value = read<size_t>(in);
|
||||||
vars[dst] = vars[src].erase(value);
|
vars[dst] = std::move(vars[src]).erase(value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case op_iterate: {
|
case op_iterate: {
|
||||||
@ -76,6 +79,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "fuzzer_input.hpp"
|
#include "fuzzer_input.hpp"
|
||||||
|
|
||||||
|
#include <immer/algorithm.hpp>
|
||||||
#include <immer/set.hpp>
|
#include <immer/set.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@ -35,7 +36,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
|||||||
op_erase,
|
op_erase,
|
||||||
op_insert_move,
|
op_insert_move,
|
||||||
op_erase_move,
|
op_erase_move,
|
||||||
op_iterate
|
op_iterate,
|
||||||
|
op_diff
|
||||||
};
|
};
|
||||||
auto src = read<char>(in, is_valid_var);
|
auto src = read<char>(in, is_valid_var);
|
||||||
auto dst = 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;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Guile bindings
|
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
|
vectors for the `GNU Guile`_ Scheme implementation. The interface is
|
||||||
somewhat **incomplete**, but you can already do something interesting
|
somewhat **incomplete**, but you can already do something interesting
|
||||||
things like:
|
things like:
|
||||||
@ -15,7 +15,7 @@ things like:
|
|||||||
|
|
||||||
**Do you want to help** making these bindings complete and production
|
**Do you want to help** making these bindings complete and production
|
||||||
ready? Drop a line at `immer@sinusoid.al
|
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>`_
|
<https://github.com/arximboldi/immer>`_
|
||||||
|
|
||||||
.. _GNU Guile: https://www.gnu.org/software/guile/
|
.. _GNU Guile: https://www.gnu.org/software/guile/
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Python bindings
|
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
|
vectors for the Python language. They were developed as part of the
|
||||||
research for the `ICFP'17 paper`_. The interface is quite
|
research for the `ICFP'17 paper`_. The interface is quite
|
||||||
**incomplete**, yet you can already do some things like:
|
**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
|
**Do you want to help** making these bindings complete and production
|
||||||
ready? Drop a line at `immer@sinusoid.al
|
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>`_
|
<https://github.com/arximboldi/immer>`_
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <functional>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <type_traits>
|
#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);
|
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`.
|
* Equivalent of `std::accumulate` applied to the range `r`.
|
||||||
*/
|
*/
|
||||||
@ -99,7 +120,7 @@ template <typename Range, typename T>
|
|||||||
T accumulate(Range&& r, T init)
|
T accumulate(Range&& r, T init)
|
||||||
{
|
{
|
||||||
for_each_chunk(r, [&](auto first, auto last) {
|
for_each_chunk(r, [&](auto first, auto last) {
|
||||||
init = std::accumulate(first, last, init);
|
init = detail::accumulate_move(first, last, init);
|
||||||
});
|
});
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
@ -108,7 +129,7 @@ template <typename Range, typename T, typename Fn>
|
|||||||
T accumulate(Range&& r, T init, Fn fn)
|
T accumulate(Range&& r, T init, Fn fn)
|
||||||
{
|
{
|
||||||
for_each_chunk(r, [&](auto first, auto last) {
|
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;
|
return init;
|
||||||
}
|
}
|
||||||
@ -121,7 +142,7 @@ template <typename Iterator, typename T>
|
|||||||
T accumulate(Iterator first, Iterator last, T init)
|
T accumulate(Iterator first, Iterator last, T init)
|
||||||
{
|
{
|
||||||
for_each_chunk(first, last, [&](auto first, auto last) {
|
for_each_chunk(first, last, [&](auto first, auto last) {
|
||||||
init = std::accumulate(first, last, init);
|
init = detail::accumulate_move(first, last, init);
|
||||||
});
|
});
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
@ -130,7 +151,7 @@ template <typename Iterator, typename T, typename Fn>
|
|||||||
T accumulate(Iterator first, Iterator last, T init, Fn fn)
|
T accumulate(Iterator first, Iterator last, T init, Fn fn)
|
||||||
{
|
{
|
||||||
for_each_chunk(first, last, [&](auto first, auto last) {
|
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;
|
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
|
/** @} */ // group: algorithm
|
||||||
|
|
||||||
} // namespace immer
|
} // namespace immer
|
||||||
|
@ -33,7 +33,7 @@ class array_transient;
|
|||||||
* .. tip:: Don't be fooled by the bad complexity of this data
|
* .. tip:: Don't be fooled by the bad complexity of this data
|
||||||
* structure. It is a great choice for short sequence or when it
|
* structure. It is a great choice for short sequence or when it
|
||||||
* is seldom or never changed. This depends on the ``sizeof(T)``
|
* 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
|
* of doubt, measure. For basic types, using an `array` when
|
||||||
* :math:`n < 100` is a good heuristic.
|
* :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.
|
* times.
|
||||||
*/
|
*/
|
||||||
array(size_type n, T v = {})
|
array(size_type n, T v = {})
|
||||||
@ -107,7 +107,8 @@ public:
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Returns an iterator pointing just after the last element of the
|
* 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; }
|
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`.
|
* 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)
|
* allocate memory and its complexity is *effectively* @f$ O(1)
|
||||||
* @f$.
|
* @f$.
|
||||||
*/
|
*/
|
||||||
@ -308,6 +309,14 @@ public:
|
|||||||
return transient_type{std::move(impl_)};
|
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
|
// Semi-private
|
||||||
const impl_t& impl() const { return impl_; }
|
const impl_t& impl() const { return impl_; }
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ private:
|
|||||||
* @rst
|
* @rst
|
||||||
*
|
*
|
||||||
* .. warning:: If memory policy used includes thread unsafe reference counting,
|
* .. 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
|
* .. 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
|
* 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
|
* ``std::atomic`` interface closely, since it attempts to be a higher level
|
||||||
* construction, most similar to Clojure's ``(atom)``. It is remarkable in
|
* construction, most similar to Clojure's ``(atom)``. It is remarkable in
|
||||||
* particular that, since ``box<T>`` underlying object is immutable, using
|
* 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
|
* not. This is so because dereferencing the underlying pointer in a
|
||||||
* ``std::atomic_share_ptr`` may require further synchronization, in
|
* ``std::atomic_share_ptr`` may require further synchronization, in
|
||||||
* particular when invoking non-const methods.
|
* particular when invoking non-const methods.
|
||||||
|
@ -219,9 +219,9 @@ IMMER_NODISCARD auto operator!=(T2&& b, const box<T, MP>& a)
|
|||||||
template <typename T2, typename T, typename MP>
|
template <typename T2, typename T, typename MP>
|
||||||
IMMER_NODISCARD auto operator<(T2&& b, const box<T, MP>& a)
|
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,
|
-> 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
|
} // namespace immer
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||||
|
#define IMMER_HAS_CPP17 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(__has_cpp_attribute)
|
#if defined(__has_cpp_attribute)
|
||||||
#if __has_cpp_attribute(nodiscard)
|
#if __has_cpp_attribute(nodiscard)
|
||||||
#define IMMER_NODISCARD [[nodiscard]]
|
#define IMMER_NODISCARD [[nodiscard]]
|
||||||
@ -66,6 +70,10 @@
|
|||||||
#define IMMER_DEBUG_PRINT 0
|
#define IMMER_DEBUG_PRINT 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef IMMER_DEBUG_STATS
|
||||||
|
#define IMMER_DEBUG_STATS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef IMMER_DEBUG_DEEP_CHECK
|
#ifndef IMMER_DEBUG_DEEP_CHECK
|
||||||
#define IMMER_DEBUG_DEEP_CHECK 0
|
#define IMMER_DEBUG_DEEP_CHECK 0
|
||||||
#endif
|
#endif
|
||||||
@ -75,6 +83,10 @@
|
|||||||
#include <prettyprint.hpp>
|
#include <prettyprint.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if IMMER_DEBUG_STATS
|
||||||
|
#include <iostream>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if IMMER_DEBUG_TRACES
|
#if IMMER_DEBUG_TRACES
|
||||||
#define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl
|
#define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl
|
||||||
#else
|
#else
|
||||||
|
@ -61,7 +61,7 @@ struct node
|
|||||||
|
|
||||||
static void delete_n(node_t* p, size_t sz, size_t cap)
|
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);
|
heap::deallocate(sizeof_n(cap), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ struct node
|
|||||||
{
|
{
|
||||||
auto p = make_n(n);
|
auto p = make_n(n);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
uninitialized_copy(first, last, p->data());
|
detail::uninitialized_copy(first, last, p->data());
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
|
@ -178,17 +178,18 @@ struct with_capacity
|
|||||||
static size_t recommend_up(size_t sz, size_t cap)
|
static size_t recommend_up(size_t sz, size_t cap)
|
||||||
{
|
{
|
||||||
auto max = std::numeric_limits<size_t>::max();
|
auto max = std::numeric_limits<size_t>::max();
|
||||||
return sz <= cap ? cap
|
return sz <= cap ? cap
|
||||||
: cap >= max / 2 ? max
|
: cap >= max / 2 ? max
|
||||||
/* otherwise */
|
/* otherwise */
|
||||||
: std::max(2 * cap, sz);
|
: std::max(2 * cap, sz);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t recommend_down(size_t sz, size_t cap)
|
static size_t recommend_down(size_t sz, size_t cap)
|
||||||
{
|
{
|
||||||
return sz == 0 ? 1
|
return sz == 0 ? 1
|
||||||
: sz < cap / 2 ? sz * 2 :
|
: sz < cap / 2 ? sz * 2
|
||||||
/* otherwise */ cap;
|
:
|
||||||
|
/* otherwise */ cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
with_capacity push_back(T value) const
|
with_capacity push_back(T value) const
|
||||||
@ -291,6 +292,7 @@ struct with_capacity
|
|||||||
|
|
||||||
with_capacity take(std::size_t sz) const
|
with_capacity take(std::size_t sz) const
|
||||||
{
|
{
|
||||||
|
assert(sz <= size);
|
||||||
auto cap = recommend_down(sz, capacity);
|
auto cap = recommend_down(sz, capacity);
|
||||||
auto p = node_t::copy_n(cap, ptr, sz);
|
auto p = node_t::copy_n(cap, ptr, sz);
|
||||||
return {p, sz, cap};
|
return {p, sz, cap};
|
||||||
@ -298,8 +300,9 @@ struct with_capacity
|
|||||||
|
|
||||||
void take_mut(edit_t e, std::size_t sz)
|
void take_mut(edit_t e, std::size_t sz)
|
||||||
{
|
{
|
||||||
|
assert(sz <= size);
|
||||||
if (ptr->can_mutate(e)) {
|
if (ptr->can_mutate(e)) {
|
||||||
destroy_n(data() + size, size - sz);
|
detail::destroy_n(data() + size, size - sz);
|
||||||
size = sz;
|
size = sz;
|
||||||
} else {
|
} else {
|
||||||
auto cap = recommend_down(sz, capacity);
|
auto cap = recommend_down(sz, capacity);
|
||||||
|
@ -125,6 +125,51 @@ inline count_t popcount(std::uint8_t x)
|
|||||||
return popcount(static_cast<std::uint32_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 hamts
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
} // namespace immer
|
} // namespace immer
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,8 @@ struct champ_iterator
|
|||||||
using tree_t = champ<T, Hash, Eq, MP, B>;
|
using tree_t = champ<T, Hash, Eq, MP, B>;
|
||||||
using node_t = typename tree_t::node_t;
|
using node_t = typename tree_t::node_t;
|
||||||
|
|
||||||
|
champ_iterator() = default;
|
||||||
|
|
||||||
struct end_t
|
struct end_t
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ namespace immer {
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
namespace hamts {
|
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,
|
template <typename T,
|
||||||
typename Hash,
|
typename Hash,
|
||||||
typename Equal,
|
typename Equal,
|
||||||
@ -56,7 +61,7 @@ struct node
|
|||||||
aligned_storage_for<T> buffer;
|
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
|
struct inner_t
|
||||||
{
|
{
|
||||||
@ -80,7 +85,7 @@ struct node
|
|||||||
data_t data;
|
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;
|
impl_t impl;
|
||||||
|
|
||||||
@ -193,6 +198,10 @@ struct node
|
|||||||
}
|
}
|
||||||
static const ownee_t& ownee(const values_t* x) { return get<ownee_t>(*x); }
|
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 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)
|
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); }
|
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)
|
static node_t* make_inner_n(count_t n)
|
||||||
{
|
{
|
||||||
assert(n <= branches<B>);
|
assert(n <= branches<B>);
|
||||||
@ -284,7 +302,7 @@ struct node
|
|||||||
new (vp + 1) T{std::move(x2)};
|
new (vp + 1) T{std::move(x2)};
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
vp->~T();
|
immer::detail::hamts::destroy_at(vp);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,6 +355,32 @@ struct node
|
|||||||
return p;
|
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)
|
static node_t* copy_collision_insert(node_t* src, T v)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||||
@ -347,7 +391,7 @@ struct node
|
|||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
new (dstp) T{std::move(v)};
|
new (dstp) T{std::move(v)};
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(srcp, srcp + n, dstp + 1);
|
detail::uninitialized_copy(srcp, srcp + n, dstp + 1);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
dstp->~T();
|
dstp->~T();
|
||||||
@ -361,6 +405,31 @@ struct node
|
|||||||
return dst;
|
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)
|
static node_t* copy_collision_remove(node_t* src, T* v)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||||
@ -370,12 +439,12 @@ struct node
|
|||||||
auto srcp = src->collisions();
|
auto srcp = src->collisions();
|
||||||
auto dstp = dst->collisions();
|
auto dstp = dst->collisions();
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
dstp = std::uninitialized_copy(srcp, v, dstp);
|
dstp = detail::uninitialized_copy(srcp, v, dstp);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(v + 1, srcp + n, dstp);
|
detail::uninitialized_copy(v + 1, srcp + n, dstp);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy(dst->collisions(), dstp);
|
detail::destroy(dst->collisions(), dstp);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,6 +455,32 @@ struct node
|
|||||||
return dst;
|
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)
|
static node_t* copy_collision_replace(node_t* src, T* pos, T v)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||||
@ -397,12 +492,12 @@ struct node
|
|||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
new (dstp) T{std::move(v)};
|
new (dstp) T{std::move(v)};
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
dstp = std::uninitialized_copy(srcp, pos, dstp + 1);
|
dstp = detail::uninitialized_copy(srcp, pos, dstp + 1);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(pos + 1, srcp + n, dstp);
|
detail::uninitialized_copy(pos + 1, srcp + n, dstp);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy(dst->collisions(), dstp);
|
detail::destroy(dst->collisions(), dstp);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,13 +523,34 @@ struct node
|
|||||||
auto dstp = dst->children();
|
auto dstp = dst->children();
|
||||||
dst->impl.d.data.inner.datamap = src->datamap();
|
dst->impl.d.data.inner.datamap = src->datamap();
|
||||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||||
std::uninitialized_copy(srcp, srcp + n, dstp);
|
std::copy(srcp, srcp + n, dstp);
|
||||||
inc_nodes(srcp, n);
|
inc_nodes(srcp, offset);
|
||||||
srcp[offset]->dec_unsafe();
|
inc_nodes(srcp + offset + 1, n - offset - 1);
|
||||||
dstp[offset] = child;
|
dstp[offset] = child;
|
||||||
return dst;
|
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)
|
static node_t* copy_inner_replace_value(node_t* src, count_t offset, T v)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
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.datamap = src->datamap();
|
||||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->values(), src->values() + nv, dst->values());
|
src->values(), src->values() + nv, dst->values());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
dst->values()[offset] = std::move(v);
|
dst->values()[offset] = std::move(v);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->values(), nv);
|
detail::destroy_n(dst->values(), nv);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,8 +576,7 @@ struct node
|
|||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
inc_nodes(src->children(), n);
|
inc_nodes(src->children(), n);
|
||||||
std::uninitialized_copy(
|
std::copy(src->children(), src->children() + n, dst->children());
|
||||||
src->children(), src->children() + n, dst->children());
|
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,15 +597,15 @@ struct node
|
|||||||
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
|
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
|
||||||
if (nv > 1) {
|
if (nv > 1) {
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->values(), src->values() + voffset, dst->values());
|
src->values(), src->values() + voffset, dst->values());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(src->values() + voffset + 1,
|
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||||
src->values() + nv,
|
src->values() + nv,
|
||||||
dst->values() + voffset);
|
dst->values() + voffset);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->values(), voffset);
|
detail::destroy_n(dst->values(), voffset);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,15 +615,66 @@ struct node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inc_nodes(src->children(), n);
|
inc_nodes(src->children(), n);
|
||||||
std::uninitialized_copy(
|
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||||
src->children(), src->children() + noffset, dst->children());
|
std::copy(src->children() + noffset,
|
||||||
std::uninitialized_copy(src->children() + noffset,
|
src->children() + n,
|
||||||
src->children() + n,
|
dst->children() + noffset + 1);
|
||||||
dst->children() + noffset + 1);
|
|
||||||
dst->children()[noffset] = node;
|
dst->children()[noffset] = node;
|
||||||
return dst;
|
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,
|
static node_t* copy_inner_replace_inline(node_t* src,
|
||||||
bitmap_t bit,
|
bitmap_t bit,
|
||||||
count_t noffset,
|
count_t noffset,
|
||||||
@ -526,15 +692,15 @@ struct node
|
|||||||
dst->impl.d.data.inner.datamap = src->datamap() | bit;
|
dst->impl.d.data.inner.datamap = src->datamap() | bit;
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
if (nv)
|
if (nv)
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->values(), src->values() + voffset, dst->values());
|
src->values(), src->values() + voffset, dst->values());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
new (dst->values() + voffset) T{std::move(value)};
|
new (dst->values() + voffset) T{std::move(value)};
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
if (nv)
|
if (nv)
|
||||||
std::uninitialized_copy(src->values() + voffset,
|
detail::uninitialized_copy(src->values() + voffset,
|
||||||
src->values() + nv,
|
src->values() + nv,
|
||||||
dst->values() + voffset + 1);
|
dst->values() + voffset + 1);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
dst->values()[voffset].~T();
|
dst->values()[voffset].~T();
|
||||||
@ -542,7 +708,7 @@ struct node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->values(), voffset);
|
detail::destroy_n(dst->values(), voffset);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -550,13 +716,74 @@ struct node
|
|||||||
deallocate_inner(dst, n - 1, nv + 1);
|
deallocate_inner(dst, n - 1, nv + 1);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
inc_nodes(src->children(), n);
|
inc_nodes(src->children(), noffset);
|
||||||
src->children()[noffset]->dec_unsafe();
|
inc_nodes(src->children() + noffset + 1, n - noffset - 1);
|
||||||
std::uninitialized_copy(
|
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||||
src->children(), src->children() + noffset, dst->children());
|
std::copy(src->children() + noffset + 1,
|
||||||
std::uninitialized_copy(src->children() + noffset + 1,
|
src->children() + n,
|
||||||
src->children() + n,
|
dst->children() + noffset);
|
||||||
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;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,15 +801,15 @@ struct node
|
|||||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||||
if (nv > 1) {
|
if (nv > 1) {
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->values(), src->values() + voffset, dst->values());
|
src->values(), src->values() + voffset, dst->values());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(src->values() + voffset + 1,
|
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||||
src->values() + nv,
|
src->values() + nv,
|
||||||
dst->values() + voffset);
|
dst->values() + voffset);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->values(), voffset);
|
detail::destroy_n(dst->values(), voffset);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,8 +819,66 @@ struct node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inc_nodes(src->children(), n);
|
inc_nodes(src->children(), n);
|
||||||
std::uninitialized_copy(
|
std::copy(src->children(), src->children() + n, dst->children());
|
||||||
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;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,15 +893,15 @@ struct node
|
|||||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
if (nv)
|
if (nv)
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->values(), src->values() + offset, dst->values());
|
src->values(), src->values() + offset, dst->values());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
new (dst->values() + offset) T{std::move(v)};
|
new (dst->values() + offset) T{std::move(v)};
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
if (nv)
|
if (nv)
|
||||||
std::uninitialized_copy(src->values() + offset,
|
detail::uninitialized_copy(src->values() + offset,
|
||||||
src->values() + nv,
|
src->values() + nv,
|
||||||
dst->values() + offset + 1);
|
dst->values() + offset + 1);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
dst->values()[offset].~T();
|
dst->values()[offset].~T();
|
||||||
@ -624,7 +909,7 @@ struct node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->values(), offset);
|
detail::destroy_n(dst->values(), offset);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,8 +918,63 @@ struct node
|
|||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
inc_nodes(src->children(), n);
|
inc_nodes(src->children(), n);
|
||||||
std::uninitialized_copy(
|
std::copy(src->children(), src->children() + n, dst->children());
|
||||||
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;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,7 +988,8 @@ struct node
|
|||||||
auto merged = make_merged(
|
auto merged = make_merged(
|
||||||
shift + B, std::move(v1), hash1, std::move(v2), hash2);
|
shift + B, std::move(v1), hash1, std::move(v2), hash2);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
return make_inner_n(1, idx1 >> shift, merged);
|
return make_inner_n(
|
||||||
|
1, static_cast<count_t>(idx1 >> shift), merged);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
delete_deep_shift(merged, shift + B);
|
delete_deep_shift(merged, shift + B);
|
||||||
@ -656,9 +997,9 @@ struct node
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return make_inner_n(0,
|
return make_inner_n(0,
|
||||||
idx1 >> shift,
|
static_cast<count_t>(idx1 >> shift),
|
||||||
std::move(v1),
|
std::move(v1),
|
||||||
idx2 >> shift,
|
static_cast<count_t>(idx2 >> shift),
|
||||||
std::move(v2));
|
std::move(v2));
|
||||||
}
|
}
|
||||||
} else {
|
} 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()
|
node_t* inc()
|
||||||
{
|
{
|
||||||
refs(this).inc();
|
refs(this).inc();
|
||||||
@ -679,7 +1052,6 @@ struct node
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool dec() const { return refs(this).dec(); }
|
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)
|
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)
|
static void delete_values(values_t* p, count_t n)
|
||||||
{
|
{
|
||||||
assert(p);
|
assert(p);
|
||||||
|
detail::destroy_n((T*) &p->d.buffer, n);
|
||||||
deallocate_values(p, n);
|
deallocate_values(p, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,6 +1081,7 @@ struct node
|
|||||||
assert(p);
|
assert(p);
|
||||||
IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision);
|
IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision);
|
||||||
auto n = p->collision_count();
|
auto n = p->collision_count();
|
||||||
|
detail::destroy_n(p->collisions(), n);
|
||||||
deallocate_collision(p, n);
|
deallocate_collision(p, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,13 +1115,11 @@ struct node
|
|||||||
|
|
||||||
static void deallocate_values(values_t* p, count_t n)
|
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);
|
heap::deallocate(node_t::sizeof_values_n(n), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deallocate_collision(node_t* p, count_t n)
|
static void deallocate_collision(node_t* p, count_t n)
|
||||||
{
|
{
|
||||||
destroy_n(p->collisions(), n);
|
|
||||||
heap::deallocate(node_t::sizeof_collision_n(n), p);
|
heap::deallocate(node_t::sizeof_collision_n(n), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,19 +82,6 @@ protected:
|
|||||||
std::is_base_of<std::bidirectional_iterator_tag,
|
std::is_base_of<std::bidirectional_iterator_tag,
|
||||||
IteratorCategoryT>::value;
|
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
|
const DerivedT& derived() const
|
||||||
{
|
{
|
||||||
static_assert(std::is_base_of<iterator_facade, DerivedT>::value,
|
static_assert(std::is_base_of<iterator_facade, DerivedT>::value,
|
||||||
@ -111,10 +98,10 @@ protected:
|
|||||||
public:
|
public:
|
||||||
ReferenceT operator*() const { return access_t::dereference(derived()); }
|
ReferenceT operator*() const { return access_t::dereference(derived()); }
|
||||||
PointerT 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, "");
|
static_assert(is_random_access, "");
|
||||||
return derived() + n;
|
return *(derived() + n);
|
||||||
}
|
}
|
||||||
|
|
||||||
friend bool operator==(const DerivedT& a, const DerivedT& b)
|
friend bool operator==(const DerivedT& a, const DerivedT& b)
|
||||||
|
@ -199,17 +199,23 @@ struct node
|
|||||||
}
|
}
|
||||||
static ownee_t& ownee(node_t* x) { return get<ownee_t>(x->impl); }
|
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>);
|
assert(n <= branches<B>);
|
||||||
auto m = heap::allocate(sizeof_inner_n(n));
|
assert(size >= sizeof_inner_n(n));
|
||||||
auto p = new (m) node_t;
|
auto p = new (buffer) node_t;
|
||||||
p->impl.d.data.inner.relaxed = nullptr;
|
p->impl.d.data.inner.relaxed = nullptr;
|
||||||
#if IMMER_TAGGED_NODE
|
#if IMMER_TAGGED_NODE
|
||||||
p->impl.d.kind = node_t::kind_t::inner;
|
p->impl.d.kind = node_t::kind_t::inner;
|
||||||
#endif
|
#endif
|
||||||
return p;
|
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)
|
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>);
|
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
|
#if IMMER_TAGGED_NODE
|
||||||
p->impl.d.kind = node_t::kind_t::leaf;
|
p->impl.d.kind = node_t::kind_t::leaf;
|
||||||
#endif
|
#endif
|
||||||
return p;
|
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)
|
static node_t* make_leaf_e(edit_t e)
|
||||||
{
|
{
|
||||||
auto p = new (heap::allocate(max_sizeof_leaf)) node_t;
|
auto p = new (heap::allocate(max_sizeof_leaf)) node_t;
|
||||||
@ -511,7 +525,7 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||||
auto dst = make_inner_n(n);
|
auto dst = make_inner_n(n);
|
||||||
inc_nodes(src->inner(), 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;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,7 +550,20 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||||
auto p = src->inner();
|
auto p = src->inner();
|
||||||
inc_nodes(p, n);
|
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;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,6 +609,23 @@ struct node
|
|||||||
return dst;
|
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)
|
static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n)
|
||||||
{
|
{
|
||||||
if (embed_relaxed)
|
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)
|
static node_t* copy_leaf(node_t* src, count_t n)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_n(n);
|
auto dst = make_leaf_n(n);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
detail::uninitialized_copy(
|
||||||
|
src->leaf(), src->leaf() + n, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
heap::deallocate(node_t::sizeof_leaf_n(n), dst);
|
heap::deallocate(node_t::sizeof_leaf_n(n), dst);
|
||||||
@ -612,7 +672,8 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_e(e);
|
auto dst = make_leaf_e(e);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
detail::uninitialized_copy(
|
||||||
|
src->leaf(), src->leaf() + n, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
heap::deallocate(node_t::max_sizeof_leaf, dst);
|
heap::deallocate(node_t::max_sizeof_leaf, dst);
|
||||||
@ -627,7 +688,8 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_n(allocn);
|
auto dst = make_leaf_n(allocn);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
detail::uninitialized_copy(
|
||||||
|
src->leaf(), src->leaf() + n, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
heap::deallocate(node_t::sizeof_leaf_n(allocn), dst);
|
heap::deallocate(node_t::sizeof_leaf_n(allocn), dst);
|
||||||
@ -642,7 +704,7 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_n(n1 + n2);
|
auto dst = make_leaf_n(n1 + n2);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -650,11 +712,11 @@ struct node
|
|||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->leaf(), n1);
|
detail::destroy_n(dst->leaf(), n1);
|
||||||
heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst);
|
heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
@ -668,7 +730,7 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_e(e);
|
auto dst = make_leaf_e(e);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -676,11 +738,11 @@ struct node
|
|||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->leaf(), n1);
|
detail::destroy_n(dst->leaf(), n1);
|
||||||
heap::deallocate(max_sizeof_leaf, dst);
|
heap::deallocate(max_sizeof_leaf, dst);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
@ -692,7 +754,7 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_e(e);
|
auto dst = make_leaf_e(e);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -707,7 +769,7 @@ struct node
|
|||||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||||
auto dst = make_leaf_n(last - idx);
|
auto dst = make_leaf_n(last - idx);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
std::uninitialized_copy(
|
detail::uninitialized_copy(
|
||||||
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -725,7 +787,7 @@ struct node
|
|||||||
new (dst->leaf() + n) T{std::forward<U>(x)};
|
new (dst->leaf() + n) T{std::forward<U>(x)};
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(dst->leaf(), n);
|
detail::destroy_n(dst->leaf(), n);
|
||||||
heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst);
|
heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
@ -788,7 +850,7 @@ struct node
|
|||||||
static void delete_leaf(node_t* p, count_t n)
|
static void delete_leaf(node_t* p, count_t n)
|
||||||
{
|
{
|
||||||
IMMER_ASSERT_TAGGED(p->kind() == kind_t::leaf);
|
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
|
heap::deallocate(ownee(p).owned() ? node_t::max_sizeof_leaf
|
||||||
: node_t::sizeof_leaf_n(n),
|
: node_t::sizeof_leaf_n(n),
|
||||||
p);
|
p);
|
||||||
@ -814,10 +876,12 @@ struct node
|
|||||||
auto dst_r = impl.d.data.inner.relaxed =
|
auto dst_r = impl.d.data.inner.relaxed =
|
||||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||||
if (src_r) {
|
if (src_r) {
|
||||||
node_t::refs(src_r).dec_unsafe();
|
|
||||||
auto n = dst_r->d.count = src_r->d.count;
|
auto n = dst_r->d.count = src_r->d.count;
|
||||||
std::copy(
|
std::copy(
|
||||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
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;
|
node_t::ownee(dst_r) = e;
|
||||||
return dst_r;
|
return dst_r;
|
||||||
@ -839,10 +903,12 @@ struct node
|
|||||||
auto dst_r = impl.d.data.inner.relaxed =
|
auto dst_r = impl.d.data.inner.relaxed =
|
||||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||||
if (src_r) {
|
if (src_r) {
|
||||||
node_t::refs(src_r).dec_unsafe();
|
|
||||||
auto n = dst_r->d.count = src_r->d.count;
|
auto n = dst_r->d.count = src_r->d.count;
|
||||||
std::copy(
|
std::copy(
|
||||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
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;
|
node_t::ownee(dst_r) = ec;
|
||||||
return dst_r;
|
return dst_r;
|
||||||
@ -863,9 +929,11 @@ struct node
|
|||||||
auto dst_r =
|
auto dst_r =
|
||||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||||
if (src_r) {
|
if (src_r) {
|
||||||
node_t::refs(src_r).dec_unsafe();
|
|
||||||
std::copy(
|
std::copy(
|
||||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
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;
|
dst_r->d.count = n;
|
||||||
node_t::ownee(dst_r) = e;
|
node_t::ownee(dst_r) = e;
|
||||||
@ -887,7 +955,6 @@ struct node
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool dec() const { return refs(this).dec(); }
|
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)
|
static void inc_nodes(node_t** p, count_t n)
|
||||||
{
|
{
|
||||||
|
@ -92,7 +92,7 @@ struct for_each_chunk_visitor : visitor_base<for_each_chunk_visitor>
|
|||||||
static void visit_leaf(Pos&& pos, Fn&& fn)
|
static void visit_leaf(Pos&& pos, Fn&& fn)
|
||||||
{
|
{
|
||||||
auto data = pos.node()->leaf();
|
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>
|
template <typename Pos, typename Fn>
|
||||||
static bool visit_leaf(Pos&& pos, Fn&& 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());
|
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());
|
auto node = node_t::make_inner_sr_n(count, pos.relaxed());
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
auto child = pos.towards_oh(this_t{}, idx, offset, fn);
|
auto child = pos.towards_oh(this_t{}, idx, offset, fn);
|
||||||
node_t::do_copy_inner_sr(node, pos.node(), count);
|
node_t::do_copy_inner_replace_sr(
|
||||||
node->inner()[offset]->dec_unsafe();
|
node, pos.node(), count, offset, child);
|
||||||
node->inner()[offset] = child;
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -487,9 +486,8 @@ struct update_visitor : visitor_base<update_visitor<NodeT>>
|
|||||||
auto node = node_t::make_inner_n(count);
|
auto node = node_t::make_inner_n(count);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn);
|
auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn);
|
||||||
node_t::do_copy_inner(node, pos.node(), count);
|
node_t::do_copy_inner_replace(
|
||||||
node->inner()[offset]->dec_unsafe();
|
node, pos.node(), count, offset, child);
|
||||||
node->inner()[offset] = child;
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
@ -1067,8 +1065,8 @@ struct slice_right_mut_visitor
|
|||||||
node->inc();
|
node->inc();
|
||||||
return std::make_tuple(0, nullptr, new_tail_size, node);
|
return std::make_tuple(0, nullptr, new_tail_size, node);
|
||||||
} else if (mutate) {
|
} else if (mutate) {
|
||||||
destroy_n(node->leaf() + new_tail_size,
|
detail::destroy_n(node->leaf() + new_tail_size,
|
||||||
old_tail_size - new_tail_size);
|
old_tail_size - new_tail_size);
|
||||||
return std::make_tuple(0, nullptr, new_tail_size, node);
|
return std::make_tuple(0, nullptr, new_tail_size, node);
|
||||||
} else {
|
} else {
|
||||||
auto new_tail = node_t::copy_leaf_e(e, node, new_tail_size);
|
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 old_tail_size = pos.count();
|
||||||
auto new_tail_size = pos.index(last) + 1;
|
auto new_tail_size = pos.index(last) + 1;
|
||||||
auto new_tail = new_tail_size == old_tail_size
|
auto new_tail = new_tail_size == old_tail_size
|
||||||
? pos.node()->inc()
|
? pos.node()->inc()
|
||||||
: node_t::copy_leaf(pos.node(), new_tail_size);
|
: node_t::copy_leaf(pos.node(), new_tail_size);
|
||||||
return std::make_tuple(0, nullptr, new_tail_size, new_tail);
|
return std::make_tuple(0, nullptr, new_tail_size, new_tail);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1263,10 +1261,10 @@ struct slice_left_mut_visitor
|
|||||||
return r;
|
return r;
|
||||||
} else {
|
} else {
|
||||||
using std::get;
|
using std::get;
|
||||||
auto newn = mutate ? (node->ensure_mutable_relaxed(e), node)
|
auto newn = mutate ? (node->ensure_mutable_relaxed(e), node)
|
||||||
: node_t::make_inner_r_e(e);
|
: node_t::make_inner_r_e(e);
|
||||||
auto newr = newn->relaxed();
|
auto newr = newn->relaxed();
|
||||||
auto newcount = count - idx;
|
auto newcount = count - idx;
|
||||||
auto new_child_size = child_size - child_dropped_size;
|
auto new_child_size = child_size - child_dropped_size;
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
auto subs =
|
auto subs =
|
||||||
@ -1277,9 +1275,9 @@ struct slice_left_mut_visitor
|
|||||||
pos.each_left(dec_visitor{}, idx);
|
pos.each_left(dec_visitor{}, idx);
|
||||||
pos.copy_sizes(
|
pos.copy_sizes(
|
||||||
idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1);
|
idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1);
|
||||||
std::uninitialized_copy(node->inner() + idx + 1,
|
std::copy(node->inner() + idx + 1,
|
||||||
node->inner() + count,
|
node->inner() + count,
|
||||||
newn->inner() + 1);
|
newn->inner() + 1);
|
||||||
newn->inner()[0] = get<1>(subs);
|
newn->inner()[0] = get<1>(subs);
|
||||||
newr->d.sizes[0] = new_child_size;
|
newr->d.sizes[0] = new_child_size;
|
||||||
newr->d.count = newcount;
|
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);
|
idx + 1, newcount - 1, newr->d.sizes[0], newr->d.sizes + 1);
|
||||||
newr->d.count = newcount;
|
newr->d.count = newcount;
|
||||||
newn->inner()[0] = get<1>(subs);
|
newn->inner()[0] = get<1>(subs);
|
||||||
std::uninitialized_copy(node->inner() + idx + 1,
|
std::copy(node->inner() + idx + 1,
|
||||||
node->inner() + count,
|
node->inner() + count,
|
||||||
newn->inner() + 1);
|
newn->inner() + 1);
|
||||||
if (!mutate) {
|
if (!mutate) {
|
||||||
node_t::inc_nodes(newn->inner() + 1, newcount - 1);
|
node_t::inc_nodes(newn->inner() + 1, newcount - 1);
|
||||||
if (Mutating)
|
if (Mutating)
|
||||||
@ -1386,7 +1384,7 @@ struct slice_left_mut_visitor
|
|||||||
auto data = node->leaf();
|
auto data = node->leaf();
|
||||||
auto newcount = count - idx;
|
auto newcount = count - idx;
|
||||||
std::move(data + idx, data + count, data);
|
std::move(data + idx, data + count, data);
|
||||||
destroy_n(data + newcount, idx);
|
detail::destroy_n(data + newcount, idx);
|
||||||
return std::make_tuple(0, node);
|
return std::make_tuple(0, node);
|
||||||
} else {
|
} else {
|
||||||
auto newn = node_t::copy_leaf_e(e, node, idx, count);
|
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] ==
|
assert(newr->d.sizes[newr->d.count - 1] ==
|
||||||
pos.size() - dropped_size);
|
pos.size() - dropped_size);
|
||||||
newn->inner()[0] = get<1>(subs);
|
newn->inner()[0] = get<1>(subs);
|
||||||
std::uninitialized_copy(n->inner() + idx + 1,
|
std::copy(n->inner() + idx + 1,
|
||||||
n->inner() + count,
|
n->inner() + count,
|
||||||
newn->inner() + 1);
|
newn->inner() + 1);
|
||||||
node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1);
|
node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1);
|
||||||
return std::make_tuple(pos.shift(), newn);
|
return std::make_tuple(pos.shift(), newn);
|
||||||
}
|
}
|
||||||
@ -1627,9 +1625,9 @@ struct concat_merger
|
|||||||
auto data = to_->leaf();
|
auto data = to_->leaf();
|
||||||
auto to_copy =
|
auto to_copy =
|
||||||
std::min(from_count - from_offset, *curr_ - to_offset_);
|
std::min(from_count - from_offset, *curr_ - to_offset_);
|
||||||
std::uninitialized_copy(from_data + from_offset,
|
detail::uninitialized_copy(from_data + from_offset,
|
||||||
from_data + from_offset + to_copy,
|
from_data + from_offset + to_copy,
|
||||||
data + to_offset_);
|
data + to_offset_);
|
||||||
to_offset_ += to_copy;
|
to_offset_ += to_copy;
|
||||||
from_offset += to_copy;
|
from_offset += to_copy;
|
||||||
if (*curr_ == to_offset_) {
|
if (*curr_ == to_offset_) {
|
||||||
@ -1662,9 +1660,9 @@ struct concat_merger
|
|||||||
auto data = to_->inner();
|
auto data = to_->inner();
|
||||||
auto to_copy =
|
auto to_copy =
|
||||||
std::min(from_count - from_offset, *curr_ - to_offset_);
|
std::min(from_count - from_offset, *curr_ - to_offset_);
|
||||||
std::uninitialized_copy(from_data + from_offset,
|
std::copy(from_data + from_offset,
|
||||||
from_data + from_offset + to_copy,
|
from_data + from_offset + to_copy,
|
||||||
data + to_offset_);
|
data + to_offset_);
|
||||||
node_t::inc_nodes(from_data + from_offset, to_copy);
|
node_t::inc_nodes(from_data + from_offset, to_copy);
|
||||||
auto sizes = to_->relaxed()->d.sizes;
|
auto sizes = to_->relaxed()->d.sizes;
|
||||||
p.copy_sizes(
|
p.copy_sizes(
|
||||||
@ -2124,10 +2122,10 @@ struct concat_merger_mut
|
|||||||
data + to_offset_);
|
data + to_offset_);
|
||||||
} else {
|
} else {
|
||||||
if (!from_mutate)
|
if (!from_mutate)
|
||||||
std::uninitialized_copy(from_data + from_offset,
|
detail::uninitialized_copy(from_data + from_offset,
|
||||||
from_data + from_offset +
|
from_data + from_offset +
|
||||||
to_copy,
|
to_copy,
|
||||||
data + to_offset_);
|
data + to_offset_);
|
||||||
else
|
else
|
||||||
detail::uninitialized_move(from_data + from_offset,
|
detail::uninitialized_move(from_data + from_offset,
|
||||||
from_data + from_offset +
|
from_data + from_offset +
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
#include <immer/detail/type_traits.hpp>
|
#include <immer/detail/type_traits.hpp>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <stdexcept>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace immer {
|
namespace immer {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -465,7 +465,7 @@ struct rbtree
|
|||||||
auto ts = size - tail_off;
|
auto ts = size - tail_off;
|
||||||
auto newts = new_size - tail_off;
|
auto newts = new_size - tail_off;
|
||||||
if (tail->can_mutate(e)) {
|
if (tail->can_mutate(e)) {
|
||||||
destroy_n(tail->leaf() + newts, ts - newts);
|
detail::destroy_n(tail->leaf() + newts, ts - newts);
|
||||||
} else {
|
} else {
|
||||||
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
||||||
dec_leaf(tail, ts);
|
dec_leaf(tail, ts);
|
||||||
|
@ -49,13 +49,21 @@ struct rrbtree
|
|||||||
|
|
||||||
static node_t* empty_root()
|
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();
|
return empty_->inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
static node_t* empty_tail()
|
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();
|
return empty_->inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +98,7 @@ struct rrbtree
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
rrbtree()
|
rrbtree() noexcept
|
||||||
: size{0}
|
: size{0}
|
||||||
, shift{BL}
|
, shift{BL}
|
||||||
, root{empty_root()}
|
, root{empty_root()}
|
||||||
@ -99,7 +107,7 @@ struct rrbtree
|
|||||||
assert(check_tree());
|
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}
|
: size{sz}
|
||||||
, shift{sh}
|
, shift{sh}
|
||||||
, root{r}
|
, root{r}
|
||||||
@ -108,13 +116,13 @@ struct rrbtree
|
|||||||
assert(check_tree());
|
assert(check_tree());
|
||||||
}
|
}
|
||||||
|
|
||||||
rrbtree(const rrbtree& other)
|
rrbtree(const rrbtree& other) noexcept
|
||||||
: rrbtree{other.size, other.shift, other.root, other.tail}
|
: rrbtree{other.size, other.shift, other.root, other.tail}
|
||||||
{
|
{
|
||||||
inc();
|
inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
rrbtree(rrbtree&& other)
|
rrbtree(rrbtree&& other) noexcept
|
||||||
: rrbtree{}
|
: rrbtree{}
|
||||||
{
|
{
|
||||||
swap(*this, other);
|
swap(*this, other);
|
||||||
@ -127,13 +135,13 @@ struct rrbtree
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
rrbtree& operator=(rrbtree&& other)
|
rrbtree& operator=(rrbtree&& other) noexcept
|
||||||
{
|
{
|
||||||
swap(*this, other);
|
swap(*this, other);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend void swap(rrbtree& x, rrbtree& y)
|
friend void swap(rrbtree& x, rrbtree& y) noexcept
|
||||||
{
|
{
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(x.size, y.size);
|
swap(x.size, y.size);
|
||||||
@ -574,7 +582,7 @@ struct rrbtree
|
|||||||
auto ts = size - tail_off;
|
auto ts = size - tail_off;
|
||||||
auto newts = new_size - tail_off;
|
auto newts = new_size - tail_off;
|
||||||
if (tail->can_mutate(e)) {
|
if (tail->can_mutate(e)) {
|
||||||
destroy_n(tail->leaf() + newts, ts - newts);
|
detail::destroy_n(tail->leaf() + newts, ts - newts);
|
||||||
} else {
|
} else {
|
||||||
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
||||||
dec_leaf(tail, ts);
|
dec_leaf(tail, ts);
|
||||||
@ -793,17 +801,17 @@ struct rrbtree
|
|||||||
return;
|
return;
|
||||||
} else if (tail_size + r.size <= branches<BL>) {
|
} else if (tail_size + r.size <= branches<BL>) {
|
||||||
l.ensure_mutable_tail(el, tail_size);
|
l.ensure_mutable_tail(el, tail_size);
|
||||||
std::uninitialized_copy(r.tail->leaf(),
|
detail::uninitialized_copy(r.tail->leaf(),
|
||||||
r.tail->leaf() + r.size,
|
r.tail->leaf() + r.size,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
l.size += r.size;
|
l.size += r.size;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
auto remaining = branches<BL> - tail_size;
|
auto remaining = branches<BL> - tail_size;
|
||||||
l.ensure_mutable_tail(el, tail_size);
|
l.ensure_mutable_tail(el, tail_size);
|
||||||
std::uninitialized_copy(r.tail->leaf(),
|
detail::uninitialized_copy(r.tail->leaf(),
|
||||||
r.tail->leaf() + remaining,
|
r.tail->leaf() + remaining,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
auto new_tail =
|
auto new_tail =
|
||||||
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
|
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
|
||||||
@ -819,7 +827,7 @@ struct rrbtree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(r.tail->leaf() + tail_size, remaining);
|
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1066,9 +1074,9 @@ struct rrbtree
|
|||||||
r.tail->leaf() + r.size,
|
r.tail->leaf() + r.size,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
else
|
else
|
||||||
std::uninitialized_copy(r.tail->leaf(),
|
detail::uninitialized_copy(r.tail->leaf(),
|
||||||
r.tail->leaf() + r.size,
|
r.tail->leaf() + r.size,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
l.size += r.size;
|
l.size += r.size;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -1079,9 +1087,9 @@ struct rrbtree
|
|||||||
r.tail->leaf() + remaining,
|
r.tail->leaf() + remaining,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
else
|
else
|
||||||
std::uninitialized_copy(r.tail->leaf(),
|
detail::uninitialized_copy(r.tail->leaf(),
|
||||||
r.tail->leaf() + remaining,
|
r.tail->leaf() + remaining,
|
||||||
l.tail->leaf() + tail_size);
|
l.tail->leaf() + tail_size);
|
||||||
IMMER_TRY {
|
IMMER_TRY {
|
||||||
auto new_tail =
|
auto new_tail =
|
||||||
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
|
node_t::copy_leaf_e(el, r.tail, remaining, r.size);
|
||||||
@ -1097,7 +1105,7 @@ struct rrbtree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IMMER_CATCH (...) {
|
IMMER_CATCH (...) {
|
||||||
destroy_n(r.tail->leaf() + tail_size, remaining);
|
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||||
IMMER_RETHROW;
|
IMMER_RETHROW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,23 +201,5 @@ struct std_distance_supports<
|
|||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
constexpr bool std_distance_supports_v = std_distance_supports<T, U>::value;
|
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 detail
|
||||||
} // namespace immer
|
} // namespace immer
|
||||||
|
@ -24,6 +24,18 @@
|
|||||||
namespace immer {
|
namespace immer {
|
||||||
namespace detail {
|
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>
|
template <typename T>
|
||||||
using aligned_storage_for =
|
using aligned_storage_for =
|
||||||
typename std::aligned_storage<sizeof(T), alignof(T)>::type;
|
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));
|
return const_cast<T&&>(std::move(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iter1, typename Iter2>
|
template <class T>
|
||||||
auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out)
|
inline auto destroy_at(T* p) noexcept
|
||||||
|
-> std::enable_if_t<std::is_trivially_destructible<T>::value>
|
||||||
{
|
{
|
||||||
return std::uninitialized_copy(
|
p->~T();
|
||||||
std::make_move_iterator(in1), std::make_move_iterator(in2), out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class 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)
|
p->~T();
|
||||||
first->~T();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T, class Size>
|
template <typename Iter1>
|
||||||
void destroy_n(T* p, Size n)
|
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;
|
return last;
|
||||||
for (; p != e; ++p)
|
}
|
||||||
p->~T();
|
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>
|
template <typename Heap, typename T, typename... Args>
|
||||||
@ -214,53 +310,5 @@ distance(Iterator first, Sentinel last)
|
|||||||
return last - first;
|
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 detail
|
||||||
} // namespace immer
|
} // namespace immer
|
||||||
|
@ -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$.
|
* allocate memory and its complexity is @f$ O(log(size)) @f$.
|
||||||
*
|
*
|
||||||
* @rst
|
* @rst
|
||||||
@ -503,6 +503,17 @@ public:
|
|||||||
IMMER_NODISCARD transient_type transient() const& { return impl_; }
|
IMMER_NODISCARD transient_type transient() const& { return impl_; }
|
||||||
IMMER_NODISCARD transient_type transient() && { return std::move(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
|
// Semi-private
|
||||||
const impl_t& impl() const { return impl_; }
|
const impl_t& impl() const { return impl_; }
|
||||||
|
|
||||||
@ -605,4 +616,9 @@ private:
|
|||||||
impl_t impl_ = {};
|
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
|
} // namespace immer
|
||||||
|
@ -33,7 +33,7 @@ struct cpp_heap
|
|||||||
* `allocate`. One must not use nor deallocate again a memory
|
* `allocate`. One must not use nor deallocate again a memory
|
||||||
* region that once it has been deallocated.
|
* 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);
|
::operator delete(data);
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,7 @@ struct enable_optimized_heap_policy
|
|||||||
* @rst
|
* @rst
|
||||||
*
|
*
|
||||||
* .. tip:: For many applications that use immutable data structures
|
* .. tip:: For many applications that use immutable data structures
|
||||||
* significantly, this is actually the best heap policy, and it
|
* significantly, this is actually the best heap policy.
|
||||||
* might become the default in the future.
|
|
||||||
*
|
*
|
||||||
* Note that most our data structures internally use trees with the
|
* Note that most our data structures internally use trees with the
|
||||||
* same big branching factors. This means that all *vectors*,
|
* same big branching factors. This means that all *vectors*,
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
#include <immer/memory_policy.hpp>
|
#include <immer/memory_policy.hpp>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <stdexcept>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace immer {
|
namespace immer {
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class map_transient;
|
|||||||
*
|
*
|
||||||
* @rst
|
* @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
|
* search, update performance and structural sharing. It does so by
|
||||||
* storing the data in contiguous chunks of :math:`2^{B}` elements.
|
* storing the data in contiguous chunks of :math:`2^{B}` elements.
|
||||||
* When storing big objects, the size of these contiguous chunks can
|
* 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 value_t = std::pair<K, T>;
|
||||||
|
|
||||||
|
using move_t =
|
||||||
|
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
|
||||||
|
|
||||||
struct project_value
|
struct project_value
|
||||||
{
|
{
|
||||||
const T& operator()(const value_t& v) const noexcept
|
const T& operator()(const value_t& v) const noexcept
|
||||||
{
|
{
|
||||||
return v.second;
|
return v.second;
|
||||||
}
|
}
|
||||||
|
T&& operator()(value_t&& v) const noexcept
|
||||||
|
{
|
||||||
|
return std::move(v.second);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct project_value_ptr
|
struct project_value_ptr
|
||||||
@ -114,8 +121,11 @@ class map
|
|||||||
{
|
{
|
||||||
auto operator()(const value_t& v) { return Hash{}(v.first); }
|
auto operator()(const value_t& v) { return Hash{}(v.first); }
|
||||||
|
|
||||||
template<typename Key>
|
template <typename Key>
|
||||||
auto operator()(const Key& v) { return Hash{}(v); }
|
auto operator()(const Key& v)
|
||||||
|
{
|
||||||
|
return Hash{}(v);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct equal_key
|
struct equal_key
|
||||||
@ -125,7 +135,7 @@ class map
|
|||||||
return Equal{}(a.first, b.first);
|
return Equal{}(a.first, b.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Key>
|
template <typename Key>
|
||||||
auto operator()(const value_t& a, const Key& b)
|
auto operator()(const value_t& a, const Key& b)
|
||||||
{
|
{
|
||||||
return Equal{}(a.first, b);
|
return Equal{}(a.first, b);
|
||||||
@ -160,8 +170,29 @@ public:
|
|||||||
|
|
||||||
using transient_type = map_transient<K, T, Hash, Equal, MemoryPolicy, B>;
|
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$.
|
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||||
*/
|
*/
|
||||||
map() = default;
|
map() = default;
|
||||||
@ -202,7 +233,9 @@ public:
|
|||||||
* This overload participates in overload resolution only if
|
* This overload participates in overload resolution only if
|
||||||
* `Hash::is_transparent` is valid and denotes a type.
|
* `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
|
IMMER_NODISCARD size_type count(const Key& k) const
|
||||||
{
|
{
|
||||||
return impl_.template get<detail::constantly<size_type, 1>,
|
return impl_.template get<detail::constantly<size_type, 1>,
|
||||||
@ -229,7 +262,9 @@ public:
|
|||||||
* This overload participates in overload resolution only if
|
* This overload participates in overload resolution only if
|
||||||
* `Hash::is_transparent` is valid and denotes a type.
|
* `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
|
IMMER_NODISCARD const T& operator[](const Key& k) const
|
||||||
{
|
{
|
||||||
return impl_.template get<project_value, default_value>(k);
|
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
|
* `std::out_of_range` error. It does not allocate memory and its
|
||||||
* complexity is *effectively* @f$ O(1) @f$.
|
* 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
|
const T& at(const Key& k) const
|
||||||
{
|
{
|
||||||
return impl_.template get<project_value, error_value>(k);
|
return impl_.template get<project_value, error_value>(k);
|
||||||
@ -297,7 +334,7 @@ public:
|
|||||||
* ``std::optional<const T&>`` but this construction is not valid
|
* ``std::optional<const T&>`` but this construction is not valid
|
||||||
* in any current standard. As a compromise we return a
|
* in any current standard. As a compromise we return a
|
||||||
* pointer, which has similar syntactic properties yet it is
|
* pointer, which has similar syntactic properties yet it is
|
||||||
* unfortunatelly unnecessarily unrestricted.
|
* unfortunately unnecessarily unrestricted.
|
||||||
*
|
*
|
||||||
* @endrst
|
* @endrst
|
||||||
*/
|
*/
|
||||||
@ -307,7 +344,6 @@ public:
|
|||||||
detail::constantly<const T*, nullptr>>(k);
|
detail::constantly<const T*, nullptr>>(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Returns a pointer to the value associated with the key `k`. If
|
* Returns a pointer to the value associated with the key `k`. If
|
||||||
* the key is not contained in the map, a `nullptr` is returned.
|
* 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
|
* This overload participates in overload resolution only if
|
||||||
* `Hash::is_transparent` is valid and denotes a type.
|
* `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
|
IMMER_NODISCARD const T* find(const Key& k) const
|
||||||
{
|
{
|
||||||
return impl_.template get<project_value_ptr,
|
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
|
IMMER_NODISCARD bool operator==(const map& other) const
|
||||||
{
|
{
|
||||||
@ -342,10 +380,14 @@ public:
|
|||||||
* It may allocate memory and its complexity is *effectively* @f$
|
* It may allocate memory and its complexity is *effectively* @f$
|
||||||
* O(1) @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));
|
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
|
* 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$
|
* It may allocate memory and its complexity is *effectively* @f$
|
||||||
* O(1) @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)});
|
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
|
* Returns a map replacing the association `(k, v)` by the
|
||||||
@ -366,22 +412,50 @@ public:
|
|||||||
* and its complexity is *effectively* @f$ O(1) @f$.
|
* and its complexity is *effectively* @f$ O(1) @f$.
|
||||||
*/
|
*/
|
||||||
template <typename Fn>
|
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_
|
return impl_
|
||||||
.template update<project_value, default_value, combine_value>(
|
.template update<project_value, default_value, combine_value>(
|
||||||
std::move(k), std::forward<Fn>(fn));
|
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
|
* Returns a map without the key `k`. If the key is not
|
||||||
* associated in the map it returns the same map. It may allocate
|
* associated in the map it returns the same map. It may allocate
|
||||||
* memory and its complexity is *effectively* @f$ O(1) @f$.
|
* 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::map_transient`.
|
||||||
*/
|
*/
|
||||||
IMMER_NODISCARD transient_type transient() const&
|
IMMER_NODISCARD transient_type transient() const&
|
||||||
@ -393,12 +467,79 @@ public:
|
|||||||
return transient_type{std::move(impl_)};
|
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
|
// Semi-private
|
||||||
const impl_t& impl() const { return impl_; }
|
const impl_t& impl() const { return impl_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend transient_type;
|
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)
|
map(impl_t impl)
|
||||||
: impl_(std::move(impl))
|
: impl_(std::move(impl))
|
||||||
{}
|
{}
|
||||||
|
@ -15,18 +15,21 @@
|
|||||||
|
|
||||||
namespace immer {
|
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
|
* @rst
|
||||||
*
|
*
|
||||||
* .. admonition:: Become a sponsor!
|
* Refer to :doc:`transients` to learn more about when and how to use
|
||||||
* :class: danger
|
* the mutable versions of immutable containers.
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
*
|
||||||
* @endrst
|
* @endrst
|
||||||
*/
|
*/
|
||||||
@ -36,6 +39,280 @@ template <typename K,
|
|||||||
typename Equal = std::equal_to<K>,
|
typename Equal = std::equal_to<K>,
|
||||||
typename MemoryPolicy = default_memory_policy,
|
typename MemoryPolicy = default_memory_policy,
|
||||||
detail::hamts::bits_t B = default_bits>
|
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
|
} // namespace immer
|
||||||
|
@ -24,7 +24,6 @@ struct no_refcount_policy
|
|||||||
|
|
||||||
void inc() {}
|
void inc() {}
|
||||||
bool dec() { return false; }
|
bool dec() { return false; }
|
||||||
void dec_unsafe() {}
|
|
||||||
bool unique() { return false; }
|
bool unique() { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,12 +34,6 @@ struct refcount_policy
|
|||||||
|
|
||||||
bool dec() { return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); }
|
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; }
|
bool unique() { return refcount == 1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ struct unsafe_refcount_policy
|
|||||||
|
|
||||||
void inc() { ++refcount; }
|
void inc() { ++refcount; }
|
||||||
bool dec() { return --refcount == 0; }
|
bool dec() { return --refcount == 0; }
|
||||||
void dec_unsafe() { --refcount; }
|
|
||||||
bool unique() { return refcount == 1; }
|
bool unique() { return refcount == 1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,13 +62,15 @@ class set
|
|||||||
{
|
{
|
||||||
using impl_t = detail::hamts::champ<T, Hash, Equal, MemoryPolicy, B>;
|
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
|
struct project_value_ptr
|
||||||
{
|
{
|
||||||
const T* operator()(const T& v) const noexcept { return &v; }
|
const T* operator()(const T& v) const noexcept { return &v; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using key_type = T;
|
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
using size_type = detail::hamts::size_t;
|
using size_type = detail::hamts::size_t;
|
||||||
using diference_type = std::ptrdiff_t;
|
using diference_type = std::ptrdiff_t;
|
||||||
@ -83,12 +85,33 @@ public:
|
|||||||
|
|
||||||
using transient_type = set_transient<T, Hash, Equal, MemoryPolicy, B>;
|
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
|
* Default constructor. It creates a set of `size() == 0`. It
|
||||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||||
*/
|
*/
|
||||||
set() = default;
|
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
|
* Returns an iterator pointing at the first element of the
|
||||||
* collection. It does not allocate memory and its complexity is
|
* collection. It does not allocate memory and its complexity is
|
||||||
@ -125,7 +148,9 @@ public:
|
|||||||
* This overload participates in overload resolution only if
|
* This overload participates in overload resolution only if
|
||||||
* `Hash::is_transparent` is valid and denotes a type.
|
* `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
|
IMMER_NODISCARD size_type count(const K& value) const
|
||||||
{
|
{
|
||||||
return impl_.template get<detail::constantly<size_type, 1>,
|
return impl_.template get<detail::constantly<size_type, 1>,
|
||||||
@ -164,7 +189,9 @@ public:
|
|||||||
* This overload participates in overload resolution only if
|
* This overload participates in overload resolution only if
|
||||||
* `Hash::is_transparent` is valid and denotes a type.
|
* `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
|
IMMER_NODISCARD const T* find(const K& value) const
|
||||||
{
|
{
|
||||||
return impl_.template get<project_value_ptr,
|
return impl_.template get<project_value_ptr,
|
||||||
@ -188,17 +215,28 @@ public:
|
|||||||
* the set, it returns the same set. It may allocate memory and
|
* the set, it returns the same set. It may allocate memory and
|
||||||
* its complexity is *effectively* @f$ O(1) @f$.
|
* 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));
|
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
|
* Returns a set without `value`. If the `value` is not in the
|
||||||
* set it returns the same set. It may allocate memory and its
|
* set it returns the same set. It may allocate memory and its
|
||||||
* complexity is *effectively* @f$ O(1) @f$.
|
* 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
|
* Returns an @a transient form of this container, a
|
||||||
@ -213,12 +251,40 @@ public:
|
|||||||
return transient_type{std::move(impl_)};
|
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
|
// Semi-private
|
||||||
const impl_t& impl() const { return impl_; }
|
const impl_t& impl() const { return impl_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend transient_type;
|
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)
|
set(impl_t impl)
|
||||||
: impl_(std::move(impl))
|
: impl_(std::move(impl))
|
||||||
{}
|
{}
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
namespace immer {
|
namespace immer {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
* Mutable version of `immer::set`.
|
||||||
|
*
|
||||||
* @rst
|
* @rst
|
||||||
*
|
*
|
||||||
* .. admonition:: Become a sponsor!
|
* Refer to :doc:`transients` to learn more about when and how to use
|
||||||
* :class: danger
|
* the mutable versions of immutable containers.
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
*
|
||||||
* @endrst
|
* @endrst
|
||||||
*/
|
*/
|
||||||
@ -35,6 +30,155 @@ template <typename T,
|
|||||||
typename Equal = std::equal_to<T>,
|
typename Equal = std::equal_to<T>,
|
||||||
typename MemoryPolicy = default_memory_policy,
|
typename MemoryPolicy = default_memory_policy,
|
||||||
detail::hamts::bits_t B = default_bits>
|
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
|
} // namespace immer
|
||||||
|
547
src/immer/immer/table.hpp
Normal file
547
src/immer/immer/table.hpp
Normal 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
src/immer/immer/table_transient.hpp
Normal file
281
src/immer/immer/table_transient.hpp
Normal 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
|
@ -40,7 +40,7 @@ class vector_transient;
|
|||||||
*
|
*
|
||||||
* @rst
|
* @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
|
* random access, update performance and structural sharing. It does
|
||||||
* so by storing the data in contiguous chunks of :math:`2^{BL}`
|
* so by storing the data in contiguous chunks of :math:`2^{BL}`
|
||||||
* elements. By default, when ``sizeof(T) == sizeof(void*)`` then
|
* 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() const& { return impl_; }
|
||||||
IMMER_NODISCARD transient_type transient() && { return std::move(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
|
// Semi-private
|
||||||
const impl_t& impl() const { return impl_; }
|
const impl_t& impl() const { return impl_; }
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ tc.stdenv.mkDerivation rec {
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
tc.cc
|
tc.cc
|
||||||
git
|
git
|
||||||
|
catch2
|
||||||
cmake
|
cmake
|
||||||
pkgconfig
|
pkgconfig
|
||||||
ninja
|
ninja
|
||||||
@ -58,6 +59,7 @@ tc.stdenv.mkDerivation rec {
|
|||||||
boost
|
boost
|
||||||
boehmgc
|
boehmgc
|
||||||
fmt
|
fmt
|
||||||
|
valgrind
|
||||||
benchmarks.c_rrb
|
benchmarks.c_rrb
|
||||||
benchmarks.steady
|
benchmarks.steady
|
||||||
benchmarks.chunkedseq
|
benchmarks.chunkedseq
|
||||||
|
4
src/immer/spm.cpp
Normal file
4
src/immer/spm.cpp
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
#include <immer/vector.hpp>
|
||||||
|
|
||||||
|
// Just so we can compile with SPM.
|
@ -16,8 +16,7 @@ foreach(_file IN LISTS immer_unit_tests)
|
|||||||
add_dependencies(tests ${_target})
|
add_dependencies(tests ${_target})
|
||||||
target_compile_definitions(${_target} PUBLIC
|
target_compile_definitions(${_target} PUBLIC
|
||||||
-DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data"
|
-DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data"
|
||||||
DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
||||||
CATCH_CONFIG_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})
|
add_test("test/${_output}" ${_output})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
221
src/immer/test/algorithm.cpp
Normal file
221
src/immer/test/algorithm.cpp
Normal 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>{});
|
||||||
|
}
|
@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
#include "../vector_transient/generic.ipp"
|
#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")
|
TEST_CASE("array_transient default constructor compiles")
|
||||||
{
|
{
|
||||||
immer::array_transient<int> transient;
|
immer::array_transient<int> transient;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#error "define the box template to use in ATOM_T"
|
#error "define the box template to use in ATOM_T"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using BOX_T = typename ATOM_T<T>::box_type;
|
using BOX_T = typename ATOM_T<T>::box_type;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#error "define the box template to use in BOX_T"
|
#error "define the box template to use in BOX_T"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
TEST_CASE("construction and copy")
|
TEST_CASE("construction and copy")
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <immer/set.hpp>
|
#include <immer/set.hpp>
|
||||||
#include <immer/vector.hpp>
|
#include <immer/vector.hpp>
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
struct rec_vec
|
struct rec_vec
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#include <immer/vector.hpp>
|
#include <immer/vector.hpp>
|
||||||
#include <immer/vector_transient.hpp>
|
#include <immer/vector_transient.hpp>
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
TEST_CASE("issue-33")
|
TEST_CASE("issue-33")
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
// 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 <forward_list>
|
||||||
#include <immer/detail/type_traits.hpp>
|
#include <immer/detail/type_traits.hpp>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <doctest.h>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
using namespace immer;
|
using namespace immer;
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ TEST_CASE("instantiation")
|
|||||||
|
|
||||||
TEST_CASE("push back one element")
|
TEST_CASE("push back one element")
|
||||||
{
|
{
|
||||||
SUBCASE("one element")
|
SECTION("one element")
|
||||||
{
|
{
|
||||||
const auto v1 = dvektor<int>{};
|
const auto v1 = dvektor<int>{};
|
||||||
auto v2 = v1.push_back(42);
|
auto v2 = v1.push_back(42);
|
||||||
@ -36,7 +36,7 @@ TEST_CASE("push back one element")
|
|||||||
CHECK(v2[0] == 42);
|
CHECK(v2[0] == 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("many elements")
|
SECTION("many elements")
|
||||||
{
|
{
|
||||||
const auto n = 666u;
|
const auto n = 666u;
|
||||||
auto v = dvektor<unsigned>{};
|
auto v = dvektor<unsigned>{};
|
||||||
@ -56,7 +56,7 @@ TEST_CASE("update")
|
|||||||
for (auto i = 0u; i < n; ++i)
|
for (auto i = 0u; i < n; ++i)
|
||||||
v = v.push_back(i);
|
v = v.push_back(i);
|
||||||
|
|
||||||
SUBCASE("assoc")
|
SECTION("assoc")
|
||||||
{
|
{
|
||||||
const auto u = v.assoc(3u, 13u);
|
const auto u = v.assoc(3u, 13u);
|
||||||
CHECK(u.size() == v.size());
|
CHECK(u.size() == v.size());
|
||||||
@ -67,7 +67,7 @@ TEST_CASE("update")
|
|||||||
CHECK(v[3u] == 3u);
|
CHECK(v[3u] == 3u);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("assoc further")
|
SECTION("assoc further")
|
||||||
{
|
{
|
||||||
for (auto i = n; i < 666; ++i)
|
for (auto i = n; i < 666; ++i)
|
||||||
v = v.push_back(i);
|
v = v.push_back(i);
|
||||||
@ -88,7 +88,7 @@ TEST_CASE("update")
|
|||||||
CHECK(v[200u] == 200u);
|
CHECK(v[200u] == 200u);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("assoc further more")
|
SECTION("assoc further more")
|
||||||
{
|
{
|
||||||
auto v = immer::dvektor<unsigned, 4>{};
|
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; });
|
const auto u = v.update(10u, [](auto x) { return x + 10; });
|
||||||
CHECK(u.size() == v.size());
|
CHECK(u.size() == v.size());
|
||||||
@ -123,13 +123,13 @@ TEST_CASE("big")
|
|||||||
for (auto i = 0u; i < n; ++i)
|
for (auto i = 0u; i < n; ++i)
|
||||||
v = v.push_back(i);
|
v = v.push_back(i);
|
||||||
|
|
||||||
SUBCASE("read")
|
SECTION("read")
|
||||||
{
|
{
|
||||||
for (auto i = 0u; i < n; ++i)
|
for (auto i = 0u; i < n; ++i)
|
||||||
CHECK(v[i] == i);
|
CHECK(v[i] == i);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("assoc")
|
SECTION("assoc")
|
||||||
{
|
{
|
||||||
for (auto i = 0u; i < n; ++i) {
|
for (auto i = 0u; i < n; ++i) {
|
||||||
v = v.assoc(i, i + 1);
|
v = v.assoc(i, i + 1);
|
||||||
@ -146,7 +146,7 @@ TEST_CASE("iterator")
|
|||||||
for (auto i = 0u; i < n; ++i)
|
for (auto i = 0u; i < n; ++i)
|
||||||
v = v.push_back(i);
|
v = v.push_back(i);
|
||||||
|
|
||||||
SUBCASE("works with range loop")
|
SECTION("works with range loop")
|
||||||
{
|
{
|
||||||
auto i = 0u;
|
auto i = 0u;
|
||||||
for (const auto& x : v)
|
for (const auto& x : v)
|
||||||
@ -154,16 +154,16 @@ TEST_CASE("iterator")
|
|||||||
CHECK(i == v.size());
|
CHECK(i == v.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("works with standard algorithms")
|
SECTION("works with standard algorithms")
|
||||||
{
|
{
|
||||||
auto s = std::vector<unsigned>(n);
|
auto s = std::vector<unsigned>(n);
|
||||||
std::iota(s.begin(), s.end(), 0u);
|
std::iota(s.begin(), s.end(), 0u);
|
||||||
std::equal(v.begin(), v.end(), s.begin(), s.end());
|
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 r = v | boost::adaptors::reversed;
|
||||||
auto i = n;
|
auto i = n;
|
||||||
@ -171,7 +171,7 @@ TEST_CASE("iterator")
|
|||||||
CHECK(x == --i);
|
CHECK(x == --i);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("works with strided range adaptor")
|
SECTION("works with strided range adaptor")
|
||||||
{
|
{
|
||||||
auto r = v | boost::adaptors::strided(5);
|
auto r = v | boost::adaptors::strided(5);
|
||||||
auto i = 0u;
|
auto i = 0u;
|
||||||
@ -179,14 +179,14 @@ TEST_CASE("iterator")
|
|||||||
CHECK(x == 5 * i++);
|
CHECK(x == 5 * i++);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("works reversed")
|
SECTION("works reversed")
|
||||||
{
|
{
|
||||||
auto i = n;
|
auto i = n;
|
||||||
for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter)
|
for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter)
|
||||||
CHECK(*iter == --i);
|
CHECK(*iter == --i);
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("advance and distance")
|
SECTION("advance and distance")
|
||||||
{
|
{
|
||||||
auto i1 = v.begin();
|
auto i1 = v.begin();
|
||||||
auto i2 = i1 + 100;
|
auto i2 = i1 + 100;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <immer/flex_vector.hpp>
|
#include <immer/flex_vector.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ TEST_CASE("bug: concatenate too big vectors")
|
|||||||
var4 = var4.push_back(42);
|
var4 = var4.push_back(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
// Assertion `!p->relaxed()' failed
|
// Assertion `!p->relaxed()' failed
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <immer/flex_vector.hpp>
|
#include <immer/flex_vector.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ TEST_CASE("bug: memory leak because of move update")
|
|||||||
var0 = std::move(var0).push_back(21);
|
var0 = std::move(var0).push_back(21);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
@ -263,7 +263,8 @@ TEST_CASE("non-bug: crash")
|
|||||||
var4 = var4 + var4;
|
var4 = var4 + var4;
|
||||||
var4 = var4.update(4, [](auto x) { return x + 1; });
|
var4 = var4.update(4, [](auto x) { return x + 1; });
|
||||||
}
|
}
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
|
||||||
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <immer/flex_vector.hpp>
|
#include <immer/flex_vector.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ TEST_CASE("bug: use after free on move-take")
|
|||||||
var0 = std::move(var0).take(68);
|
var0 = std::move(var0).take(68);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <immer/flex_vector.hpp>
|
#include <immer/flex_vector.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ TEST_CASE("bug: concat with moving the right side")
|
|||||||
var0 = var0 + std::move(var1);
|
var0 = var0 + std::move(var1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("vm")
|
SECTION("vm")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <immer/flex_vector.hpp>
|
#include <immer/flex_vector.hpp>
|
||||||
#include <immer/flex_vector_transient.hpp>
|
#include <immer/flex_vector_transient.hpp>
|
||||||
#include <immer/heap/gc_heap.hpp>
|
#include <immer/heap/gc_heap.hpp>
|
||||||
@ -231,7 +231,7 @@ TEST_CASE("bug: concatenating transients")
|
|||||||
t1.append(t0);
|
t1.append(t0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
@ -271,7 +271,7 @@ TEST_CASE("bug: concatenating moved transients")
|
|||||||
t2.append(std::move(t0));
|
t2.append(std::move(t0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
@ -309,7 +309,7 @@ TEST_CASE("bug: concatenating moved transients")
|
|||||||
t0 = {};
|
t0 = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -348,7 +348,7 @@ TEST_CASE("bug: aegsdas")
|
|||||||
t1 = {};
|
t1 = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||||
SECTION("")
|
SECTION("")
|
||||||
{
|
{
|
||||||
constexpr std::uint8_t input[] = {
|
constexpr std::uint8_t input[] = {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include <boost/range/adaptors.hpp>
|
#include <boost/range/adaptors.hpp>
|
||||||
#include <boost/range/irange.hpp>
|
#include <boost/range/irange.hpp>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@ -29,6 +29,9 @@
|
|||||||
#error "define the vector template to use in VECTOR_T"
|
#error "define the vector template to use in VECTOR_T"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
IMMER_RANGES_CHECK(
|
||||||
|
std::ranges::random_access_range<FLEX_VECTOR_T<std::string>>);
|
||||||
|
|
||||||
template <typename V = FLEX_VECTOR_T<unsigned>>
|
template <typename V = FLEX_VECTOR_T<unsigned>>
|
||||||
auto make_test_flex_vector(unsigned min, unsigned max)
|
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")
|
TEST_CASE("concat")
|
||||||
{
|
{
|
||||||
#if IMMER_SLOW_TESTS
|
#if IMMER_SLOW_TESTS
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <immer/vector.hpp>
|
#include <immer/vector.hpp>
|
||||||
#include <immer/vector_transient.hpp>
|
#include <immer/vector_transient.hpp>
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
#if IMMER_CXX_STANDARD >= 17
|
#if IMMER_CXX_STANDARD >= 17
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include <boost/range/adaptors.hpp>
|
#include <boost/range/adaptors.hpp>
|
||||||
#include <boost/range/irange.hpp>
|
#include <boost/range/irange.hpp>
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@ -33,6 +33,11 @@
|
|||||||
#error "define the vector template to use in VECTOR_T"
|
#error "define the vector template to use in VECTOR_T"
|
||||||
#endif
|
#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>>
|
template <typename V = VECTOR_T<unsigned>>
|
||||||
auto make_test_flex_vector(unsigned min, unsigned max)
|
auto make_test_flex_vector(unsigned min, unsigned max)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#define IMMER_DEBUG_STATS 1
|
||||||
|
|
||||||
#include <immer/map.hpp>
|
#include <immer/map.hpp>
|
||||||
|
|
||||||
#define MAP_T ::immer::map
|
#define MAP_T ::immer::map
|
||||||
|
@ -23,4 +23,5 @@ template <typename K,
|
|||||||
using test_map_t = immer::map<K, T, Hash, Eq, gc_memory, 3u>;
|
using test_map_t = immer::map<K, T, Hash, Eq, gc_memory, 3u>;
|
||||||
|
|
||||||
#define MAP_T test_map_t
|
#define MAP_T test_map_t
|
||||||
|
#define IMMER_IS_LIBGC_TEST 1
|
||||||
#include "generic.ipp"
|
#include "generic.ipp"
|
||||||
|
@ -13,15 +13,21 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <immer/algorithm.hpp>
|
#include <immer/algorithm.hpp>
|
||||||
|
#include <immer/box.hpp>
|
||||||
|
|
||||||
#include "test/dada.hpp"
|
#include "test/dada.hpp"
|
||||||
#include "test/util.hpp"
|
#include "test/util.hpp"
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#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>
|
template <typename T = unsigned>
|
||||||
auto make_generator()
|
auto make_generator()
|
||||||
{
|
{
|
||||||
@ -66,7 +72,7 @@ auto make_test_map(unsigned n)
|
|||||||
{
|
{
|
||||||
auto s = MAP_T<unsigned, unsigned>{};
|
auto s = MAP_T<unsigned, unsigned>{};
|
||||||
for (auto i = 0u; i < n; ++i)
|
for (auto i = 0u; i < n; ++i)
|
||||||
s = s.insert({i, i});
|
s = std::move(s).insert({i, i});
|
||||||
return s;
|
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>{};
|
auto s = MAP_T<conflictor, unsigned, hash_conflictor>{};
|
||||||
for (auto&& v : vals)
|
for (auto&& v : vals)
|
||||||
s = s.insert(v);
|
s = std::move(s).insert(v);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +90,7 @@ TEST_CASE("instantiation")
|
|||||||
{
|
{
|
||||||
auto v = MAP_T<int, int>{};
|
auto v = MAP_T<int, int>{};
|
||||||
CHECK(v.size() == 0u);
|
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);
|
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")
|
TEST_CASE("accessor")
|
||||||
{
|
{
|
||||||
const auto n = 666u;
|
const auto n = 666u;
|
||||||
@ -151,8 +171,39 @@ TEST_CASE("equals and setting")
|
|||||||
CHECK(v.set(1234, 42) == v.insert({1234, 42}));
|
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(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(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")
|
TEST_CASE("iterator")
|
||||||
{
|
{
|
||||||
const auto N = 666u;
|
const auto N = 666u;
|
||||||
@ -216,12 +267,158 @@ TEST_CASE("update a lot")
|
|||||||
{
|
{
|
||||||
auto v = make_test_map(666u);
|
auto v = make_test_map(666u);
|
||||||
|
|
||||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
SECTION("immutable")
|
||||||
v = v.update(i, [](auto&& x) { return x + 1; });
|
{
|
||||||
CHECK(v[i] == i + 1);
|
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")
|
TEST_CASE("exception safety")
|
||||||
{
|
{
|
||||||
constexpr auto n = 2666u;
|
constexpr auto n = 2666u;
|
||||||
@ -231,12 +428,12 @@ TEST_CASE("exception safety")
|
|||||||
using dadaist_conflictor_map_t = typename dadaist_wrapper<
|
using dadaist_conflictor_map_t = typename dadaist_wrapper<
|
||||||
MAP_T<conflictor, unsigned, hash_conflictor>>::type;
|
MAP_T<conflictor, unsigned, hash_conflictor>>::type;
|
||||||
|
|
||||||
SECTION("update collisions")
|
SECTION("update")
|
||||||
{
|
{
|
||||||
auto v = dadaist_map_t{};
|
auto v = dadaist_map_t{};
|
||||||
auto d = dadaism{};
|
auto d = dadaism{};
|
||||||
for (auto i = 0u; i < n; ++i)
|
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();) {
|
for (auto i = 0u; i < v.size();) {
|
||||||
try {
|
try {
|
||||||
auto s = d.next();
|
auto s = d.next();
|
||||||
@ -252,6 +449,27 @@ TEST_CASE("exception safety")
|
|||||||
IMMER_TRACE_E(d.happenings);
|
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")
|
SECTION("update collisisions")
|
||||||
{
|
{
|
||||||
auto vals = make_values_with_collisions(n);
|
auto vals = make_values_with_collisions(n);
|
||||||
@ -273,6 +491,143 @@ TEST_CASE("exception safety")
|
|||||||
CHECK(d.happenings > 0);
|
CHECK(d.happenings > 0);
|
||||||
IMMER_TRACE_E(d.happenings);
|
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 {
|
namespace {
|
||||||
@ -363,3 +718,75 @@ TEST_CASE("issue 134")
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // 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);
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <immer/map.hpp>
|
#include <immer/map.hpp>
|
||||||
#include <immer/vector.hpp>
|
#include <immer/vector.hpp>
|
||||||
|
|
||||||
#include <catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
TEST_CASE("const map")
|
TEST_CASE("const map")
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user