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
|
||||
with:
|
||||
fetch-depth: 0 # needed for fetchGit in default.nix
|
||||
- uses: cachix/install-nix-action@v12
|
||||
- uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
||||
- uses: cachix/cachix-action@v8
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: arximboldi
|
||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
||||
- run: nix-build
|
||||
|
||||
build-spm:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- run: swift build
|
||||
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: cachix/install-nix-action@v12
|
||||
- uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
||||
- uses: cachix/cachix-action@v8
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: arximboldi
|
||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
||||
@ -35,12 +41,12 @@ jobs:
|
||||
- run: nix-shell --run "cd build && cmake .."
|
||||
- run: nix-shell --run "cd build && make docs"
|
||||
- uses: shimataro/ssh-key-action@v2
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi'
|
||||
with:
|
||||
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
||||
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
||||
- run: nix-shell --run "cd build && make upload-docs"
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi'
|
||||
|
||||
check:
|
||||
strategy:
|
||||
@ -74,7 +80,7 @@ jobs:
|
||||
- type: Debug
|
||||
toolchain: llvm-8
|
||||
std: 14
|
||||
opts: ['sanitizer']
|
||||
opts: ['sanitize']
|
||||
# benchmarks
|
||||
- type: Release
|
||||
toolchain: gnu-9
|
||||
@ -87,11 +93,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: cachix/install-nix-action@v12
|
||||
- uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
install_url: "https://releases.nixos.org/nix/nix-2.3.16/install"
|
||||
- uses: cachix/cachix-action@v8
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: arximboldi
|
||||
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
|
||||
@ -113,11 +118,11 @@ jobs:
|
||||
"
|
||||
- run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "cd build && make check -j`nproc`"
|
||||
- run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "bash <(curl -s https://codecov.io/bash)"
|
||||
if: ${{ contains(matrix.opts, 'coverage') }}
|
||||
if: contains(matrix.opts, 'coverage')
|
||||
- uses: shimataro/ssh-key-action@v2
|
||||
if: ${{ contains(matrix.opts, 'benchmark') }}
|
||||
if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi'
|
||||
with:
|
||||
key: ${{ secrets.SINUSOIDES_SSH_KEY }}
|
||||
known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }}
|
||||
- run: nix-shell --run "cd build && make upload-benchmark-reports"
|
||||
if: ${{ contains(matrix.opts, 'benchmark') }}
|
||||
if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi'
|
||||
|
5
src/immer/.gitignore
vendored
5
src/immer/.gitignore
vendored
@ -22,3 +22,8 @@ __pycache__
|
||||
tools/clojure/.lein*
|
||||
|
||||
*.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()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
project(immer VERSION 0.7.0)
|
||||
project(immer VERSION 0.8.0)
|
||||
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-extended-offsetof -Wno-c++17-extensions -Wno-c++1z-extensions -Wno-unknown-warning-option -Wno-type-limits")
|
||||
@ -99,6 +99,14 @@ install(TARGETS immer EXPORT ImmerConfig)
|
||||
install(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer")
|
||||
install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion )
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer" )
|
||||
|
||||
# development target to be used in tests, examples, benchmarks...
|
||||
immer_canonicalize_cmake_booleans(
|
||||
DISABLE_FREE_LIST
|
||||
@ -131,6 +139,8 @@ endif()
|
||||
if (immer_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
find_package(Catch2 REQUIRED)
|
||||
|
||||
add_custom_target(check
|
||||
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
|
29
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
|
||||
:target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster
|
||||
:alt: Github Actions Badge
|
||||
:alt: GitHub Actions Badge
|
||||
|
||||
.. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/arximboldi/immer
|
||||
@ -13,7 +13,10 @@
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<img width="100%" src="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-front.svg" alt="Logotype"/>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-black.svg">
|
||||
<img width="100%" src="https://cdn.rawgit.com/arximboldi/immer/3888170d247359cc0905eed548cd46897caef0f4/doc/_static/logo-front.svg" alt="Logotype">
|
||||
</picture>
|
||||
|
||||
.. include:introduction/start
|
||||
|
||||
@ -74,7 +77,7 @@ Example
|
||||
For a **complete example** check `Ewig, a simple didactic
|
||||
text-editor <https://github.com/arximboldi/ewig>`_ built with this
|
||||
library. You may also wanna check `Lager, a Redux-like library
|
||||
<https://github.com/arximboldi/lager>`_ for writting interactive
|
||||
<https://github.com/arximboldi/lager>`_ for writing interactive
|
||||
software in C++ using a value-oriented design.
|
||||
|
||||
|
||||
|
85
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 <immer/set.hpp>
|
||||
#include <hash_trie.hpp> // Phil Nash
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <hash_trie.hpp> // Phil Nash
|
||||
#include <immer/set.hpp>
|
||||
#include <immer/set_transient.hpp>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
@ -21,8 +22,7 @@ namespace {
|
||||
template <typename Generator, typename Set>
|
||||
auto benchmark_insert_mut_std()
|
||||
{
|
||||
return [] (nonius::chronometer meter)
|
||||
{
|
||||
return [](nonius::chronometer meter) {
|
||||
auto n = meter.param<N>();
|
||||
auto g = Generator{}(n);
|
||||
|
||||
@ -38,8 +38,7 @@ auto benchmark_insert_mut_std()
|
||||
template <typename Generator, typename Set>
|
||||
auto benchmark_insert()
|
||||
{
|
||||
return [] (nonius::chronometer meter)
|
||||
{
|
||||
return [](nonius::chronometer meter) {
|
||||
auto n = meter.param<N>();
|
||||
auto g = Generator{}(n);
|
||||
|
||||
@ -52,4 +51,20 @@ auto benchmark_insert()
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Generator, typename Set>
|
||||
auto benchmark_insert_move()
|
||||
{
|
||||
return [](nonius::chronometer meter) {
|
||||
auto n = meter.param<N>();
|
||||
auto g = Generator{}(n);
|
||||
|
||||
measure(meter, [&] {
|
||||
auto v = Set{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = std::move(v).insert(g[i]);
|
||||
return v;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -15,6 +15,7 @@
|
||||
using generator__ = GENERATOR_T;
|
||||
using t__ = typename decltype(generator__{}(0))::value_type;
|
||||
|
||||
// clang-format off
|
||||
NONIUS_BENCHMARK("std::set", benchmark_insert_mut_std<generator__, std::set<t__>>())
|
||||
NONIUS_BENCHMARK("std::unordered_set", benchmark_insert_mut_std<generator__, std::unordered_set<t__>>())
|
||||
NONIUS_BENCHMARK("boost::flat_set", benchmark_insert_mut_std<generator__, boost::container::flat_set<t__>>())
|
||||
@ -26,3 +27,16 @@ NONIUS_BENCHMARK("immer::set/4B", benchmark_insert<generator__, immer::set<t__,
|
||||
NONIUS_BENCHMARK("immer::set/GC", benchmark_insert<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
|
||||
#endif
|
||||
NONIUS_BENCHMARK("immer::set/UN", benchmark_insert<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
|
||||
|
||||
NONIUS_BENCHMARK("immer::set/move/5B", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
|
||||
NONIUS_BENCHMARK("immer::set/move/4B", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
|
||||
NONIUS_BENCHMARK("immer::set/move/UN", benchmark_insert_move<generator__, immer::set<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
|
||||
|
||||
NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,5>>())
|
||||
NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,def_memory,4>>())
|
||||
#ifndef DISABLE_GC_BENCHMARKS
|
||||
NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,gc_memory,5>>())
|
||||
#endif
|
||||
NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_insert_mut_std<generator__, immer::set_transient<t__, std::hash<t__>,std::equal_to<t__>,unsafe_memory,5>>())
|
||||
|
||||
// clang-format on
|
||||
|
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 {
|
||||
name = "immer-git";
|
||||
version = "git";
|
||||
src = fetchGit ./.;
|
||||
src = srcFilter ./.;
|
||||
nativeBuildInputs = [ cmake ];
|
||||
dontBuild = true;
|
||||
dontUseCmakeBuildDir = true;
|
||||
cmakeFlags = [
|
||||
"-Dimmer_BUILD_TESTS=OFF"
|
||||
"-Dimmer_BUILD_EXAMPLES=OFF"
|
||||
];
|
||||
meta = {
|
||||
homepage = "https://github.com/arximboldi/immer";
|
||||
description = "Postmodern immutable data structures for C++";
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
add_custom_target(doxygen
|
||||
COMMAND doxygen doxygen.config
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
add_custom_target(docs
|
||||
COMMAND make html
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc")
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_dependencies(docs doxygen)
|
||||
|
||||
set(immer_ssh_method
|
||||
@ -18,5 +18,5 @@ set(immer_ssh_method
|
||||
add_custom_target(upload-docs
|
||||
COMMAND
|
||||
rsync -av -e \"${immer_ssh_method}\"
|
||||
${CMAKE_SOURCE_DIR}/doc/_build/html/*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/_build/html/*
|
||||
raskolnikov@sinusoid.es:public/immer/)
|
||||
|
@ -48,3 +48,10 @@ map
|
||||
.. doxygenclass:: immer::map
|
||||
: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
|
||||
transparent to the user.
|
||||
|
||||
Assigment
|
||||
Assignment
|
||||
---------
|
||||
|
||||
We are sorry, we lied. These containers provide *one mutating
|
||||
@ -79,14 +79,14 @@ removing it, as in:
|
||||
:end-before: move-good/end
|
||||
|
||||
So, is it bad style then to use ``const`` as much as possible? I
|
||||
wouldn't say so and it is advisable when ``std::move()`` is not used.
|
||||
An alternative style is to not use ``const`` but adopt an `AAA-style
|
||||
<aaa>`_ (*Almost Always use Auto*). This way, it is easy to look for
|
||||
wouldn't say so, and it is advisable when ``std::move()`` is not used.
|
||||
An alternative style is to not use ``const`` but adopt an `AAA-style`_
|
||||
(*Almost Always use Auto*). This way, it is easy to look for
|
||||
mutations by looking for lines that contain ``=`` but no ``auto``.
|
||||
Remember that when using our immutable containers ``operator=`` is the
|
||||
only way to mutate a variable.
|
||||
|
||||
.. _aaa: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
|
||||
.. _AAA-style: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
|
||||
|
||||
.. admonition:: Why does ``const`` prevent move semantics?
|
||||
|
||||
@ -149,7 +149,7 @@ mutate part of the internal data structure in place when possible.
|
||||
If you don't like this syntax, :doc:`transients<transients>` may be
|
||||
used to obtain similar performance benefits.
|
||||
|
||||
.. admonition:: Assigment guarantees
|
||||
.. admonition:: Assignment guarantees
|
||||
|
||||
From the language point of view, the only requirement on moved from
|
||||
values is that they should still be destructible. We provide the
|
||||
@ -208,7 +208,7 @@ Efficient batch manipulations
|
||||
Sometimes you may write a function that needs to do multiple changes
|
||||
to a container. Like most code you write with this library, this
|
||||
function is *pure*: it takes one container value in, and produces a
|
||||
new container value out, no side-effects.
|
||||
new container value out, no side effects.
|
||||
|
||||
Let's say we want to write a function that inserts all integers in the
|
||||
range :math:`[first, last)` into an immutable vector:
|
||||
|
@ -1,7 +1,7 @@
|
||||
Memory management
|
||||
=================
|
||||
|
||||
Memory management is specially important for immutable data
|
||||
Memory management is especially important for immutable data
|
||||
structures. This is mainly due to:
|
||||
|
||||
#. In order to preserve the old value, new memory has to be allocated
|
||||
@ -79,7 +79,7 @@ allocate objects of those sizes.
|
||||
|
||||
.. _metafunction class: http://www.boost.org/doc/libs/1_62_0/libs/mpl/doc/refmanual/metafunction-class.html
|
||||
|
||||
A **heap** is a type with a methods ``void* allocate(std::size_t)``
|
||||
A **heap** is a type with methods ``void* allocate(std::size_t)``
|
||||
and ``void deallocate(void*)`` that return and release raw memory.
|
||||
For a canonical model of this concept check the
|
||||
:cpp:class:`immer::cpp_heap`.
|
||||
@ -95,8 +95,8 @@ For a canonical model of this concept check the
|
||||
On the other hand, having some **scoped state** does make
|
||||
sense for some use-cases of immutable data structures. For
|
||||
example, we might want to support variations of `region
|
||||
based allocation`_. This interface might evolve to evolve
|
||||
to support some kind of non-global state to accommodate
|
||||
based allocation`_. This interface might evolve to support
|
||||
some kind of non-global state to accommodate
|
||||
these use cases.
|
||||
|
||||
.. _region based allocation: https://en.wikipedia.org/wiki/Region-based_memory_management
|
||||
@ -143,7 +143,7 @@ Heap adaptors
|
||||
Inspired by `Andrei Alexandrescu's talk on allocators <allocation
|
||||
vexation>`_ and `Emery Berger's heap layers <heap layers>`_ we provide
|
||||
allocator adaptors that can be combined using C++ mixins. These
|
||||
enable building more complex allocator out of simpler strategies, or
|
||||
enable building more complex allocators out of simpler strategies, or
|
||||
provide application specific optimizations on top of general
|
||||
allocators.
|
||||
|
||||
|
@ -4,9 +4,9 @@ Transients
|
||||
==========
|
||||
|
||||
*Transients* is a concept borrowed `from Clojure
|
||||
<clojure-transients>`_, with some twists to turn make more idiomatic
|
||||
<clojure-transients>`_, with some twists to make it more idiomatic
|
||||
in C++. Essentially, they are a mutable interface built on top of the
|
||||
same data structures the implements the immutable containers under the
|
||||
same data structures that implement the immutable containers under the
|
||||
hood.
|
||||
|
||||
These can be useful for :ref:`performing efficient batch
|
||||
@ -49,3 +49,10 @@ map_transient
|
||||
.. doxygenclass:: immer::map_transient
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
table_transient
|
||||
---------------
|
||||
|
||||
.. doxygenclass:: immer::table_transient
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
29
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 <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
|
||||
{};
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include <immer/map.hpp>
|
||||
#include <immer/refcount/no_refcount_policy.hpp>
|
||||
|
||||
#include <immer/algorithm.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
using gc_memory = immer::memory_policy<immer::heap_policy<immer::gc_heap>,
|
||||
@ -49,7 +51,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase_move,
|
||||
op_iterate,
|
||||
op_find,
|
||||
op_update
|
||||
op_update,
|
||||
op_update_move,
|
||||
op_update_if_exists,
|
||||
op_update_if_exists_move,
|
||||
op_diff
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -94,6 +100,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_move: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] =
|
||||
std::move(vars[src]).update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_if_exists: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] =
|
||||
vars[src].update_if_exists(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_if_exists_move: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] = std::move(vars[src]).update_if_exists(
|
||||
key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x.first));
|
||||
assert(b.count(x.first));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x.first));
|
||||
assert(!b.count(x.first));
|
||||
},
|
||||
[&](auto&& x, auto&& y) {
|
||||
assert(x.first == y.first);
|
||||
assert(x.second != y.second);
|
||||
});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
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/algorithm.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
|
||||
@ -44,7 +46,9 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase_move,
|
||||
op_iterate,
|
||||
op_find,
|
||||
op_update
|
||||
op_update,
|
||||
op_update_move,
|
||||
op_diff
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -89,6 +93,31 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_move: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] =
|
||||
std::move(vars[src]).update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x.first));
|
||||
assert(b.count(x.first));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x.first));
|
||||
assert(!b.count(x.first));
|
||||
},
|
||||
[&](auto&& x, auto&& y) {
|
||||
assert(x.first == y.first);
|
||||
assert(x.second != y.second);
|
||||
});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include <immer/map.hpp>
|
||||
|
||||
#include <immer/algorithm.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
struct colliding_hash_t
|
||||
@ -37,7 +39,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase_move,
|
||||
op_iterate,
|
||||
op_find,
|
||||
op_update
|
||||
op_update,
|
||||
op_update_move,
|
||||
op_update_if_exists,
|
||||
op_update_if_exists_move,
|
||||
op_diff,
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -82,6 +88,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
vars[dst] = vars[src].update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_move: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] =
|
||||
std::move(vars[src]).update(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_if_exists: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] =
|
||||
vars[src].update_if_exists(key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_update_if_exists_move: {
|
||||
auto key = read<size_t>(in);
|
||||
vars[dst] = std::move(vars[src]).update_if_exists(
|
||||
key, [](int x) { return x + 1; });
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x.first));
|
||||
assert(b.count(x.first));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x.first));
|
||||
assert(!b.count(x.first));
|
||||
},
|
||||
[&](auto&& x, auto&& y) {
|
||||
assert(x.first == y.first);
|
||||
assert(x.second != y.second);
|
||||
});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
@ -49,7 +49,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase,
|
||||
op_insert_move,
|
||||
op_erase_move,
|
||||
op_iterate
|
||||
op_iterate,
|
||||
op_diff
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -81,6 +82,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x));
|
||||
assert(b.count(x));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x));
|
||||
assert(!b.count(x));
|
||||
},
|
||||
[&](auto&& x, auto&& y) { assert(false); });
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
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/set.hpp>
|
||||
|
||||
#include <immer/algorithm.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
using st_memory = immer::memory_policy<immer::heap_policy<immer::cpp_heap>,
|
||||
@ -44,7 +46,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase,
|
||||
op_insert_move,
|
||||
op_erase_move,
|
||||
op_iterate
|
||||
op_iterate,
|
||||
op_diff
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -66,7 +69,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
}
|
||||
case op_erase_move: {
|
||||
auto value = read<size_t>(in);
|
||||
vars[dst] = vars[src].erase(value);
|
||||
vars[dst] = std::move(vars[src]).erase(value);
|
||||
break;
|
||||
}
|
||||
case op_iterate: {
|
||||
@ -76,6 +79,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x));
|
||||
assert(b.count(x));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x));
|
||||
assert(!b.count(x));
|
||||
},
|
||||
[&](auto&& x, auto&& y) { assert(false); });
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "fuzzer_input.hpp"
|
||||
|
||||
#include <immer/algorithm.hpp>
|
||||
#include <immer/set.hpp>
|
||||
|
||||
#include <array>
|
||||
@ -35,7 +36,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
op_erase,
|
||||
op_insert_move,
|
||||
op_erase_move,
|
||||
op_iterate
|
||||
op_iterate,
|
||||
op_diff
|
||||
};
|
||||
auto src = read<char>(in, is_valid_var);
|
||||
auto dst = read<char>(in, is_valid_var);
|
||||
@ -67,6 +69,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case op_diff: {
|
||||
auto&& a = vars[src];
|
||||
auto&& b = vars[dst];
|
||||
diff(
|
||||
a,
|
||||
b,
|
||||
[&](auto&& x) {
|
||||
assert(!a.count(x));
|
||||
assert(b.count(x));
|
||||
},
|
||||
[&](auto&& x) {
|
||||
assert(a.count(x));
|
||||
assert(!b.count(x));
|
||||
},
|
||||
[&](auto&& x, auto&& y) { assert(false); });
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
Guile bindings
|
||||
==============
|
||||
|
||||
This library includes experimental bindings bring efficient immutable
|
||||
This library includes experimental bindings that provide efficient immutable
|
||||
vectors for the `GNU Guile`_ Scheme implementation. The interface is
|
||||
somewhat **incomplete**, but you can already do something interesting
|
||||
things like:
|
||||
@ -15,7 +15,7 @@ things like:
|
||||
|
||||
**Do you want to help** making these bindings complete and production
|
||||
ready? Drop a line at `immer@sinusoid.al
|
||||
<mailto:immer@sinusoid.al>`_ or `open an issue on Github
|
||||
<mailto:immer@sinusoid.al>`_ or `open an issue on GitHub
|
||||
<https://github.com/arximboldi/immer>`_
|
||||
|
||||
.. _GNU Guile: https://www.gnu.org/software/guile/
|
||||
|
@ -2,7 +2,7 @@
|
||||
Python bindings
|
||||
===============
|
||||
|
||||
This library includes experimental bindings bring efficient immutable
|
||||
This library includes experimental bindings that provide efficient immutable
|
||||
vectors for the Python language. They were developed as part of the
|
||||
research for the `ICFP'17 paper`_. The interface is quite
|
||||
**incomplete**, yet you can already do some things like:
|
||||
@ -15,7 +15,7 @@ research for the `ICFP'17 paper`_. The interface is quite
|
||||
|
||||
**Do you want to help** making these bindings complete and production
|
||||
ready? Drop a line at `immer@sinusoid.al
|
||||
<mailto:immer@sinusoid.al>`_ or `open an issue on Github
|
||||
<mailto:immer@sinusoid.al>`_ or `open an issue on GitHub
|
||||
<https://github.com/arximboldi/immer>`_
|
||||
|
||||
Installation
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <type_traits>
|
||||
|
||||
@ -92,6 +93,26 @@ bool for_each_chunk_p(const T* first, const T* last, Fn&& fn)
|
||||
return std::forward<Fn>(fn)(first, last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Iter, class T>
|
||||
T accumulate_move(Iter first, Iter last, T init)
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
init = std::move(init) + *first;
|
||||
return init;
|
||||
}
|
||||
|
||||
template <class Iter, class T, class Fn>
|
||||
T accumulate_move(Iter first, Iter last, T init, Fn op)
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
init = op(std::move(init), *first);
|
||||
return init;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/*!
|
||||
* Equivalent of `std::accumulate` applied to the range `r`.
|
||||
*/
|
||||
@ -99,7 +120,7 @@ template <typename Range, typename T>
|
||||
T accumulate(Range&& r, T init)
|
||||
{
|
||||
for_each_chunk(r, [&](auto first, auto last) {
|
||||
init = std::accumulate(first, last, init);
|
||||
init = detail::accumulate_move(first, last, init);
|
||||
});
|
||||
return init;
|
||||
}
|
||||
@ -108,7 +129,7 @@ template <typename Range, typename T, typename Fn>
|
||||
T accumulate(Range&& r, T init, Fn fn)
|
||||
{
|
||||
for_each_chunk(r, [&](auto first, auto last) {
|
||||
init = std::accumulate(first, last, init, fn);
|
||||
init = detail::accumulate_move(first, last, init, fn);
|
||||
});
|
||||
return init;
|
||||
}
|
||||
@ -121,7 +142,7 @@ template <typename Iterator, typename T>
|
||||
T accumulate(Iterator first, Iterator last, T init)
|
||||
{
|
||||
for_each_chunk(first, last, [&](auto first, auto last) {
|
||||
init = std::accumulate(first, last, init);
|
||||
init = detail::accumulate_move(first, last, init);
|
||||
});
|
||||
return init;
|
||||
}
|
||||
@ -130,7 +151,7 @@ template <typename Iterator, typename T, typename Fn>
|
||||
T accumulate(Iterator first, Iterator last, T init, Fn fn)
|
||||
{
|
||||
for_each_chunk(first, last, [&](auto first, auto last) {
|
||||
init = std::accumulate(first, last, init, fn);
|
||||
init = detail::accumulate_move(first, last, init, fn);
|
||||
});
|
||||
return init;
|
||||
}
|
||||
@ -208,6 +229,97 @@ bool all_of(Iter first, Iter last, Pred p)
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Object that can be used to process changes as computed by the @a diff
|
||||
* algorithm.
|
||||
*
|
||||
* @tparam AddedFn Unary function that is be called whenever an added element is
|
||||
* found. It is called with the added element as argument.
|
||||
*
|
||||
* @tparam RemovedFn Unary function that is called whenever a removed element is
|
||||
* found. It is called with the removed element as argument.
|
||||
*
|
||||
* @tparam ChangedFn Unary function that is called whenever a changed element is
|
||||
* found. It is called with the changed element as argument.
|
||||
*/
|
||||
template <class AddedFn, class RemovedFn, class ChangedFn>
|
||||
struct differ
|
||||
{
|
||||
AddedFn added;
|
||||
RemovedFn removed;
|
||||
ChangedFn changed;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Produces a @a differ object with `added`, `removed` and `changed` functions.
|
||||
*/
|
||||
template <class AddedFn, class RemovedFn, class ChangedFn>
|
||||
auto make_differ(AddedFn&& added, RemovedFn&& removed, ChangedFn&& changed)
|
||||
-> differ<std::decay_t<AddedFn>,
|
||||
std::decay_t<RemovedFn>,
|
||||
std::decay_t<ChangedFn>>
|
||||
{
|
||||
return {std::forward<AddedFn>(added),
|
||||
std::forward<RemovedFn>(removed),
|
||||
std::forward<ChangedFn>(changed)};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Produces a @a differ object with `added` and `removed` functions and no
|
||||
* `changed` function.
|
||||
*/
|
||||
template <class AddedFn, class RemovedFn>
|
||||
auto make_differ(AddedFn&& added, RemovedFn&& removed)
|
||||
{
|
||||
return make_differ(std::forward<AddedFn>(added),
|
||||
std::forward<RemovedFn>(removed),
|
||||
[](auto&&...) {});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the differences between `a` and `b`.
|
||||
*
|
||||
* Changes detected are notified via the differ object, which should support the
|
||||
* following expressions:
|
||||
*
|
||||
* - `differ.added(x)`, invoked when element `x` is found in `b` but not in
|
||||
* `a`.
|
||||
*
|
||||
* - `differ.removed(x)`, invoked when element `x` is found in `a` but not in
|
||||
* `b`.
|
||||
*
|
||||
* - `differ.changed(x, y)`, invoked when element `x` and `y` from `a` and `b`
|
||||
* share the same key but map to a different value.
|
||||
*
|
||||
* This method leverages structural sharing to offer a complexity @f$ O(|diff|)
|
||||
* @f$ when `b` is derived from `a` by performing @f$ |diff| @f$ updates. This
|
||||
* is, this function can detect changes in effectively constant time per update,
|
||||
* as oposed to the @f$ O(|a|+|b|) @f$ complexity of a trivial implementation.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* .. note:: This method is only implemented for ``map`` and ``set``. When sets
|
||||
* are diffed, the ``changed`` function is never called.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
template <typename T, typename Differ>
|
||||
void diff(const T& a, const T& b, Differ&& differ)
|
||||
{
|
||||
a.impl().template diff<std::equal_to<typename T::value_type>>(
|
||||
b.impl(), std::forward<Differ>(differ));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the differences between `a` and `b` using the callbacks in `fns` as
|
||||
* differ. Equivalent to `diff(a, b, make_differ(fns)...)`.
|
||||
*/
|
||||
template <typename T, typename... Fns>
|
||||
void diff(const T& a, const T& b, Fns&&... fns)
|
||||
{
|
||||
diff(a, b, make_differ(std::forward<Fns>(fns)...));
|
||||
}
|
||||
|
||||
/** @} */ // group: algorithm
|
||||
|
||||
} // namespace immer
|
||||
|
@ -33,7 +33,7 @@ class array_transient;
|
||||
* .. tip:: Don't be fooled by the bad complexity of this data
|
||||
* structure. It is a great choice for short sequence or when it
|
||||
* is seldom or never changed. This depends on the ``sizeof(T)``
|
||||
* and the expensiveness of its ``T``'s copy constructor, in case
|
||||
* and the expensiveness of its ``T``'s copy constructor. In case
|
||||
* of doubt, measure. For basic types, using an `array` when
|
||||
* :math:`n < 100` is a good heuristic.
|
||||
*
|
||||
@ -91,7 +91,7 @@ public:
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Constructs a array containing the element `val` repeated `n`
|
||||
* Constructs an array containing the element `val` repeated `n`
|
||||
* times.
|
||||
*/
|
||||
array(size_type n, T v = {})
|
||||
@ -107,7 +107,8 @@ public:
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing just after the last element of the
|
||||
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
|
||||
* collection. It does not allocate memory and its complexity is @f$ O(1)
|
||||
* @f$.
|
||||
*/
|
||||
IMMER_NODISCARD iterator end() const { return impl_.data() + impl_.size; }
|
||||
|
||||
@ -160,7 +161,7 @@ public:
|
||||
|
||||
/*!
|
||||
* Returns a `const` reference to the element at position `index`.
|
||||
* It is undefined when @f$ 0 index \geq size() @f$. It does not
|
||||
* It is undefined when @f$ index \geq size() @f$. It does not
|
||||
* allocate memory and its complexity is *effectively* @f$ O(1)
|
||||
* @f$.
|
||||
*/
|
||||
@ -308,6 +309,14 @@ public:
|
||||
return transient_type{std::move(impl_)};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a value that can be used as identity for the container. If two
|
||||
* values have the same identity, they are guaranteed to be equal and to
|
||||
* contain the same objects. However, two equal containers are not
|
||||
* guaranteed to have the same identity.
|
||||
*/
|
||||
void* identity() const { return impl_.ptr; }
|
||||
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
|
||||
|
@ -129,7 +129,7 @@ private:
|
||||
* @rst
|
||||
*
|
||||
* .. warning:: If memory policy used includes thread unsafe reference counting,
|
||||
* no no thread safety is assumed, and the atom becomes thread unsafe too!
|
||||
* no thread safety is assumed, and the atom becomes thread unsafe too!
|
||||
*
|
||||
* .. note:: ``box<T>`` provides a value based box of type ``T``, this is, we
|
||||
* can think about it as a value-based version of ``std::shared_ptr``. In a
|
||||
@ -138,7 +138,7 @@ private:
|
||||
* ``std::atomic`` interface closely, since it attempts to be a higher level
|
||||
* construction, most similar to Clojure's ``(atom)``. It is remarkable in
|
||||
* particular that, since ``box<T>`` underlying object is immutable, using
|
||||
* ``atom<T>`` is fully thread-safe in ways that ``std::atmic_shared_ptr`` is
|
||||
* ``atom<T>`` is fully thread-safe in ways that ``std::atomic_shared_ptr`` is
|
||||
* not. This is so because dereferencing the underlying pointer in a
|
||||
* ``std::atomic_share_ptr`` may require further synchronization, in
|
||||
* particular when invoking non-const methods.
|
||||
|
@ -219,9 +219,9 @@ IMMER_NODISCARD auto operator!=(T2&& b, const box<T, MP>& a)
|
||||
template <typename T2, typename T, typename MP>
|
||||
IMMER_NODISCARD auto operator<(T2&& b, const box<T, MP>& a)
|
||||
-> std::enable_if_t<!std::is_same<box<T, MP>, std::decay_t<T2>>::value,
|
||||
decltype(a.get() < b)>
|
||||
decltype(b < a.get())>
|
||||
{
|
||||
return a.get() < b;
|
||||
return b < a.get();
|
||||
}
|
||||
|
||||
} // namespace immer
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||
#define IMMER_HAS_CPP17 1
|
||||
#endif
|
||||
|
||||
#if defined(__has_cpp_attribute)
|
||||
#if __has_cpp_attribute(nodiscard)
|
||||
#define IMMER_NODISCARD [[nodiscard]]
|
||||
@ -66,6 +70,10 @@
|
||||
#define IMMER_DEBUG_PRINT 0
|
||||
#endif
|
||||
|
||||
#ifndef IMMER_DEBUG_STATS
|
||||
#define IMMER_DEBUG_STATS 0
|
||||
#endif
|
||||
|
||||
#ifndef IMMER_DEBUG_DEEP_CHECK
|
||||
#define IMMER_DEBUG_DEEP_CHECK 0
|
||||
#endif
|
||||
@ -75,6 +83,10 @@
|
||||
#include <prettyprint.hpp>
|
||||
#endif
|
||||
|
||||
#if IMMER_DEBUG_STATS
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#if IMMER_DEBUG_TRACES
|
||||
#define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl
|
||||
#else
|
||||
|
@ -61,7 +61,7 @@ struct node
|
||||
|
||||
static void delete_n(node_t* p, size_t sz, size_t cap)
|
||||
{
|
||||
destroy_n(p->data(), sz);
|
||||
detail::destroy_n(p->data(), sz);
|
||||
heap::deallocate(sizeof_n(cap), p);
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ struct node
|
||||
{
|
||||
auto p = make_n(n);
|
||||
IMMER_TRY {
|
||||
uninitialized_copy(first, last, p->data());
|
||||
detail::uninitialized_copy(first, last, p->data());
|
||||
return p;
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
|
@ -187,7 +187,8 @@ struct with_capacity
|
||||
static size_t recommend_down(size_t sz, size_t cap)
|
||||
{
|
||||
return sz == 0 ? 1
|
||||
: sz < cap / 2 ? sz * 2 :
|
||||
: sz < cap / 2 ? sz * 2
|
||||
:
|
||||
/* otherwise */ cap;
|
||||
}
|
||||
|
||||
@ -291,6 +292,7 @@ struct with_capacity
|
||||
|
||||
with_capacity take(std::size_t sz) const
|
||||
{
|
||||
assert(sz <= size);
|
||||
auto cap = recommend_down(sz, capacity);
|
||||
auto p = node_t::copy_n(cap, ptr, sz);
|
||||
return {p, sz, cap};
|
||||
@ -298,8 +300,9 @@ struct with_capacity
|
||||
|
||||
void take_mut(edit_t e, std::size_t sz)
|
||||
{
|
||||
assert(sz <= size);
|
||||
if (ptr->can_mutate(e)) {
|
||||
destroy_n(data() + size, size - sz);
|
||||
detail::destroy_n(data() + size, size - sz);
|
||||
size = sz;
|
||||
} else {
|
||||
auto cap = recommend_down(sz, capacity);
|
||||
|
@ -125,6 +125,51 @@ inline count_t popcount(std::uint8_t x)
|
||||
return popcount(static_cast<std::uint32_t>(x));
|
||||
}
|
||||
|
||||
template <typename bitmap_t>
|
||||
class set_bits_range
|
||||
{
|
||||
bitmap_t bitmap;
|
||||
|
||||
class set_bits_iterator
|
||||
{
|
||||
bitmap_t bitmap;
|
||||
|
||||
inline static bitmap_t clearlsbit(bitmap_t bitmap)
|
||||
{
|
||||
return bitmap & (bitmap - 1);
|
||||
}
|
||||
|
||||
inline static bitmap_t lsbit(bitmap_t bitmap)
|
||||
{
|
||||
return bitmap ^ clearlsbit(bitmap);
|
||||
}
|
||||
|
||||
public:
|
||||
set_bits_iterator(bitmap_t bitmap)
|
||||
: bitmap(bitmap){};
|
||||
|
||||
set_bits_iterator operator++()
|
||||
{
|
||||
bitmap = clearlsbit(bitmap);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(set_bits_iterator const& other) const
|
||||
{
|
||||
return bitmap != other.bitmap;
|
||||
}
|
||||
|
||||
bitmap_t operator*() const { return lsbit(bitmap); }
|
||||
};
|
||||
|
||||
public:
|
||||
set_bits_range(bitmap_t bitmap)
|
||||
: bitmap(bitmap)
|
||||
{}
|
||||
set_bits_iterator begin() const { return set_bits_iterator(bitmap); }
|
||||
set_bits_iterator end() const { return set_bits_iterator(0); }
|
||||
};
|
||||
|
||||
} // namespace hamts
|
||||
} // namespace detail
|
||||
} // namespace immer
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,8 @@ struct champ_iterator
|
||||
using tree_t = champ<T, Hash, Eq, MP, B>;
|
||||
using node_t = typename tree_t::node_t;
|
||||
|
||||
champ_iterator() = default;
|
||||
|
||||
struct end_t
|
||||
{};
|
||||
|
||||
|
@ -20,6 +20,11 @@ namespace immer {
|
||||
namespace detail {
|
||||
namespace hamts {
|
||||
|
||||
// For C++14 support.
|
||||
// Calling the destructor inline breaks MSVC in some obscure
|
||||
// corner cases.
|
||||
template <typename T> constexpr void destroy_at(T* p) { p->~T(); }
|
||||
|
||||
template <typename T,
|
||||
typename Hash,
|
||||
typename Equal,
|
||||
@ -56,7 +61,7 @@ struct node
|
||||
aligned_storage_for<T> buffer;
|
||||
};
|
||||
|
||||
using values_t = combine_standard_layout_t<values_data_t, refs_t>;
|
||||
using values_t = combine_standard_layout_t<values_data_t, refs_t, ownee_t>;
|
||||
|
||||
struct inner_t
|
||||
{
|
||||
@ -80,7 +85,7 @@ struct node
|
||||
data_t data;
|
||||
};
|
||||
|
||||
using impl_t = combine_standard_layout_t<impl_data_t, refs_t>;
|
||||
using impl_t = combine_standard_layout_t<impl_data_t, refs_t, ownee_t>;
|
||||
|
||||
impl_t impl;
|
||||
|
||||
@ -193,6 +198,10 @@ struct node
|
||||
}
|
||||
static const ownee_t& ownee(const values_t* x) { return get<ownee_t>(*x); }
|
||||
static ownee_t& ownee(values_t* x) { return get<ownee_t>(*x); }
|
||||
static bool can_mutate(values_t* x, edit_t e)
|
||||
{
|
||||
return refs(x).unique() || ownee(x).can_mutate(e);
|
||||
}
|
||||
|
||||
static refs_t& refs(const node_t* x)
|
||||
{
|
||||
@ -204,6 +213,15 @@ struct node
|
||||
}
|
||||
static ownee_t& ownee(node_t* x) { return get<ownee_t>(x->impl); }
|
||||
|
||||
bool can_mutate(edit_t e) const
|
||||
{
|
||||
return refs(this).unique() || ownee(this).can_mutate(e);
|
||||
}
|
||||
bool can_mutate_values(edit_t e) const
|
||||
{
|
||||
return can_mutate(impl.d.data.inner.values, e);
|
||||
}
|
||||
|
||||
static node_t* make_inner_n(count_t n)
|
||||
{
|
||||
assert(n <= branches<B>);
|
||||
@ -284,7 +302,7 @@ struct node
|
||||
new (vp + 1) T{std::move(x2)};
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
vp->~T();
|
||||
immer::detail::hamts::destroy_at(vp);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -337,6 +355,32 @@ struct node
|
||||
return p;
|
||||
}
|
||||
|
||||
T* ensure_mutable_values(edit_t e)
|
||||
{
|
||||
assert(can_mutate(e));
|
||||
auto old = impl.d.data.inner.values;
|
||||
if (node_t::can_mutate(old, e))
|
||||
return values();
|
||||
else {
|
||||
auto nv = data_count();
|
||||
auto nxt = new (heap::allocate(sizeof_values_n(nv))) values_t{};
|
||||
auto dst = (T*) &nxt->d.buffer;
|
||||
auto src = values();
|
||||
ownee(nxt) = e;
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_copy(src, src + nv, dst);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_values(nxt, nv);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
impl.d.data.inner.values = nxt;
|
||||
if (refs(old).dec())
|
||||
delete_values(old, nv);
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
|
||||
static node_t* copy_collision_insert(node_t* src, T v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||
@ -347,7 +391,7 @@ struct node
|
||||
IMMER_TRY {
|
||||
new (dstp) T{std::move(v)};
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(srcp, srcp + n, dstp + 1);
|
||||
detail::uninitialized_copy(srcp, srcp + n, dstp + 1);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
dstp->~T();
|
||||
@ -361,6 +405,31 @@ struct node
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* move_collision_insert(node_t* src, T v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||
auto n = src->collision_count();
|
||||
auto dst = make_collision_n(n + 1);
|
||||
auto srcp = src->collisions();
|
||||
auto dstp = dst->collisions();
|
||||
IMMER_TRY {
|
||||
new (dstp) T{std::move(v)};
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_move(srcp, srcp + n, dstp + 1);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
dstp->~T();
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_collision(dst, n + 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
delete_collision(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* copy_collision_remove(node_t* src, T* v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||
@ -370,12 +439,12 @@ struct node
|
||||
auto srcp = src->collisions();
|
||||
auto dstp = dst->collisions();
|
||||
IMMER_TRY {
|
||||
dstp = std::uninitialized_copy(srcp, v, dstp);
|
||||
dstp = detail::uninitialized_copy(srcp, v, dstp);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(v + 1, srcp + n, dstp);
|
||||
detail::uninitialized_copy(v + 1, srcp + n, dstp);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy(dst->collisions(), dstp);
|
||||
detail::destroy(dst->collisions(), dstp);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -386,6 +455,32 @@ struct node
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* move_collision_remove(node_t* src, T* v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||
assert(src->collision_count() > 1);
|
||||
auto n = src->collision_count();
|
||||
auto dst = make_collision_n(n - 1);
|
||||
auto srcp = src->collisions();
|
||||
auto dstp = dst->collisions();
|
||||
IMMER_TRY {
|
||||
dstp = detail::uninitialized_move(srcp, v, dstp);
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_move(v + 1, srcp + n, dstp);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy(dst->collisions(), dstp);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_collision(dst, n - 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
delete_collision(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* copy_collision_replace(node_t* src, T* pos, T v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision);
|
||||
@ -397,12 +492,12 @@ struct node
|
||||
IMMER_TRY {
|
||||
new (dstp) T{std::move(v)};
|
||||
IMMER_TRY {
|
||||
dstp = std::uninitialized_copy(srcp, pos, dstp + 1);
|
||||
dstp = detail::uninitialized_copy(srcp, pos, dstp + 1);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(pos + 1, srcp + n, dstp);
|
||||
detail::uninitialized_copy(pos + 1, srcp + n, dstp);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy(dst->collisions(), dstp);
|
||||
detail::destroy(dst->collisions(), dstp);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -428,13 +523,34 @@ struct node
|
||||
auto dstp = dst->children();
|
||||
dst->impl.d.data.inner.datamap = src->datamap();
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
std::uninitialized_copy(srcp, srcp + n, dstp);
|
||||
inc_nodes(srcp, n);
|
||||
srcp[offset]->dec_unsafe();
|
||||
std::copy(srcp, srcp + n, dstp);
|
||||
inc_nodes(srcp, offset);
|
||||
inc_nodes(srcp + offset + 1, n - offset - 1);
|
||||
dstp[offset] = child;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* owned(node_t* n, edit_t e)
|
||||
{
|
||||
ownee(n) = e;
|
||||
return n;
|
||||
}
|
||||
|
||||
static node_t* owned_values(node_t* n, edit_t e)
|
||||
{
|
||||
ownee(n) = e;
|
||||
ownee(n->impl.d.data.inner.values) = e;
|
||||
return n;
|
||||
}
|
||||
|
||||
static node_t* owned_values_safe(node_t* n, edit_t e)
|
||||
{
|
||||
ownee(n) = e;
|
||||
if (n->impl.d.data.inner.values)
|
||||
ownee(n->impl.d.data.inner.values) = e;
|
||||
return n;
|
||||
}
|
||||
|
||||
static node_t* copy_inner_replace_value(node_t* src, count_t offset, T v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
@ -445,13 +561,13 @@ struct node
|
||||
dst->impl.d.data.inner.datamap = src->datamap();
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + nv, dst->values());
|
||||
IMMER_TRY {
|
||||
dst->values()[offset] = std::move(v);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->values(), nv);
|
||||
detail::destroy_n(dst->values(), nv);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -460,8 +576,7 @@ struct node
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
inc_nodes(src->children(), n);
|
||||
std::uninitialized_copy(
|
||||
src->children(), src->children() + n, dst->children());
|
||||
std::copy(src->children(), src->children() + n, dst->children());
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -482,15 +597,15 @@ struct node
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
|
||||
if (nv > 1) {
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(src->values() + voffset + 1,
|
||||
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->values(), voffset);
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -500,15 +615,66 @@ struct node
|
||||
}
|
||||
}
|
||||
inc_nodes(src->children(), n);
|
||||
std::uninitialized_copy(
|
||||
src->children(), src->children() + noffset, dst->children());
|
||||
std::uninitialized_copy(src->children() + noffset,
|
||||
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||
std::copy(src->children() + noffset,
|
||||
src->children() + n,
|
||||
dst->children() + noffset + 1);
|
||||
dst->children()[noffset] = node;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* move_inner_replace_merged(
|
||||
edit_t e, node_t* src, bitmap_t bit, count_t voffset, node_t* node)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
assert(!(src->nodemap() & bit));
|
||||
assert(src->datamap() & bit);
|
||||
assert(voffset == src->data_count(bit));
|
||||
auto n = src->children_count();
|
||||
auto nv = src->data_count();
|
||||
auto dst = make_inner_n(n + 1, nv - 1);
|
||||
auto noffset = src->children_count(bit);
|
||||
dst->impl.d.data.inner.datamap = src->datamap() & ~bit;
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap() | bit;
|
||||
if (nv > 1) {
|
||||
auto mutate_values = can_mutate(src->impl.d.data.inner.values, e);
|
||||
IMMER_TRY {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
else
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
else
|
||||
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_inner(dst, n + 1, nv - 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
// inc_nodes(src->children(), n);
|
||||
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||
std::copy(src->children() + noffset,
|
||||
src->children() + n,
|
||||
dst->children() + noffset + 1);
|
||||
dst->children()[noffset] = node;
|
||||
delete_inner(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* copy_inner_replace_inline(node_t* src,
|
||||
bitmap_t bit,
|
||||
count_t noffset,
|
||||
@ -526,13 +692,13 @@ struct node
|
||||
dst->impl.d.data.inner.datamap = src->datamap() | bit;
|
||||
IMMER_TRY {
|
||||
if (nv)
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
new (dst->values() + voffset) T{std::move(value)};
|
||||
IMMER_TRY {
|
||||
if (nv)
|
||||
std::uninitialized_copy(src->values() + voffset,
|
||||
detail::uninitialized_copy(src->values() + voffset,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset + 1);
|
||||
}
|
||||
@ -542,7 +708,7 @@ struct node
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->values(), voffset);
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -550,16 +716,77 @@ struct node
|
||||
deallocate_inner(dst, n - 1, nv + 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
inc_nodes(src->children(), n);
|
||||
src->children()[noffset]->dec_unsafe();
|
||||
std::uninitialized_copy(
|
||||
src->children(), src->children() + noffset, dst->children());
|
||||
std::uninitialized_copy(src->children() + noffset + 1,
|
||||
inc_nodes(src->children(), noffset);
|
||||
inc_nodes(src->children() + noffset + 1, n - noffset - 1);
|
||||
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||
std::copy(src->children() + noffset + 1,
|
||||
src->children() + n,
|
||||
dst->children() + noffset);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* move_inner_replace_inline(
|
||||
edit_t e, node_t* src, bitmap_t bit, count_t noffset, T value)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
assert(!(src->datamap() & bit));
|
||||
assert(src->nodemap() & bit);
|
||||
assert(noffset == src->children_count(bit));
|
||||
auto n = src->children_count();
|
||||
auto nv = src->data_count();
|
||||
auto dst = make_inner_n(n - 1, nv + 1);
|
||||
auto voffset = src->data_count(bit);
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap() & ~bit;
|
||||
dst->impl.d.data.inner.datamap = src->datamap() | bit;
|
||||
IMMER_TRY {
|
||||
auto mutate_values =
|
||||
nv && can_mutate(src->impl.d.data.inner.values, e);
|
||||
if (nv) {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
else
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
}
|
||||
IMMER_TRY {
|
||||
new (dst->values() + voffset) T{std::move(value)};
|
||||
IMMER_TRY {
|
||||
if (nv) {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(src->values() + voffset,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset +
|
||||
1);
|
||||
else
|
||||
detail::uninitialized_copy(src->values() + voffset,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset +
|
||||
1);
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
dst->values()[voffset].~T();
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_inner(dst, n - 1, nv + 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
std::copy(src->children(), src->children() + noffset, dst->children());
|
||||
std::copy(src->children() + noffset + 1,
|
||||
src->children() + n,
|
||||
dst->children() + noffset);
|
||||
delete_inner(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t*
|
||||
copy_inner_remove_value(node_t* src, bitmap_t bit, count_t voffset)
|
||||
{
|
||||
@ -574,15 +801,15 @@ struct node
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
if (nv > 1) {
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(src->values() + voffset + 1,
|
||||
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->values(), voffset);
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -592,8 +819,66 @@ struct node
|
||||
}
|
||||
}
|
||||
inc_nodes(src->children(), n);
|
||||
std::uninitialized_copy(
|
||||
src->children(), src->children() + n, dst->children());
|
||||
std::copy(src->children(), src->children() + n, dst->children());
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* move_inner_remove_value(edit_t e,
|
||||
node_t* src,
|
||||
bitmap_t bit,
|
||||
count_t voffset)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
assert(!(src->nodemap() & bit));
|
||||
assert(src->datamap() & bit);
|
||||
assert(voffset == src->data_count(bit));
|
||||
auto n = src->children_count();
|
||||
auto nv = src->data_count();
|
||||
auto dst = make_inner_n(n, nv - 1);
|
||||
dst->impl.d.data.inner.datamap = src->datamap() & ~bit;
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
if (nv > 1) {
|
||||
auto mutate_values = can_mutate(src->impl.d.data.inner.values, e);
|
||||
if (mutate_values) {
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_move(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_move(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_inner(dst, n, nv - 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
} else {
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + voffset, dst->values());
|
||||
IMMER_TRY {
|
||||
detail::uninitialized_copy(src->values() + voffset + 1,
|
||||
src->values() + nv,
|
||||
dst->values() + voffset);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy_n(dst->values(), voffset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_inner(dst, n, nv - 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::copy(src->children(), src->children() + n, dst->children());
|
||||
delete_inner(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -608,13 +893,13 @@ struct node
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
IMMER_TRY {
|
||||
if (nv)
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + offset, dst->values());
|
||||
IMMER_TRY {
|
||||
new (dst->values() + offset) T{std::move(v)};
|
||||
IMMER_TRY {
|
||||
if (nv)
|
||||
std::uninitialized_copy(src->values() + offset,
|
||||
detail::uninitialized_copy(src->values() + offset,
|
||||
src->values() + nv,
|
||||
dst->values() + offset + 1);
|
||||
}
|
||||
@ -624,7 +909,7 @@ struct node
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->values(), offset);
|
||||
detail::destroy_n(dst->values(), offset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -633,8 +918,63 @@ struct node
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
inc_nodes(src->children(), n);
|
||||
std::uninitialized_copy(
|
||||
src->children(), src->children() + n, dst->children());
|
||||
std::copy(src->children(), src->children() + n, dst->children());
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t*
|
||||
move_inner_insert_value(edit_t e, node_t* src, bitmap_t bit, T v)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
auto n = src->children_count();
|
||||
auto nv = src->data_count();
|
||||
auto offset = src->data_count(bit);
|
||||
auto dst = make_inner_n(n, nv + 1);
|
||||
dst->impl.d.data.inner.datamap = src->datamap() | bit;
|
||||
dst->impl.d.data.inner.nodemap = src->nodemap();
|
||||
IMMER_TRY {
|
||||
auto mutate_values =
|
||||
nv && can_mutate(src->impl.d.data.inner.values, e);
|
||||
if (nv) {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(
|
||||
src->values(), src->values() + offset, dst->values());
|
||||
else
|
||||
detail::uninitialized_copy(
|
||||
src->values(), src->values() + offset, dst->values());
|
||||
}
|
||||
IMMER_TRY {
|
||||
new (dst->values() + offset) T{std::move(v)};
|
||||
IMMER_TRY {
|
||||
if (nv) {
|
||||
if (mutate_values)
|
||||
detail::uninitialized_move(src->values() + offset,
|
||||
src->values() + nv,
|
||||
dst->values() + offset +
|
||||
1);
|
||||
else
|
||||
detail::uninitialized_copy(src->values() + offset,
|
||||
src->values() + nv,
|
||||
dst->values() + offset +
|
||||
1);
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
dst->values()[offset].~T();
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy_n(dst->values(), offset);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
deallocate_inner(dst, n, nv + 1);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
std::copy(src->children(), src->children() + n, dst->children());
|
||||
delete_inner(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -648,7 +988,8 @@ struct node
|
||||
auto merged = make_merged(
|
||||
shift + B, std::move(v1), hash1, std::move(v2), hash2);
|
||||
IMMER_TRY {
|
||||
return make_inner_n(1, idx1 >> shift, merged);
|
||||
return make_inner_n(
|
||||
1, static_cast<count_t>(idx1 >> shift), merged);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
delete_deep_shift(merged, shift + B);
|
||||
@ -656,9 +997,9 @@ struct node
|
||||
}
|
||||
} else {
|
||||
return make_inner_n(0,
|
||||
idx1 >> shift,
|
||||
static_cast<count_t>(idx1 >> shift),
|
||||
std::move(v1),
|
||||
idx2 >> shift,
|
||||
static_cast<count_t>(idx2 >> shift),
|
||||
std::move(v2));
|
||||
}
|
||||
} else {
|
||||
@ -666,6 +1007,38 @@ struct node
|
||||
}
|
||||
}
|
||||
|
||||
static node_t* make_merged_e(
|
||||
edit_t e, shift_t shift, T v1, hash_t hash1, T v2, hash_t hash2)
|
||||
{
|
||||
if (shift < max_shift<B>) {
|
||||
auto idx1 = hash1 & (mask<B> << shift);
|
||||
auto idx2 = hash2 & (mask<B> << shift);
|
||||
if (idx1 == idx2) {
|
||||
auto merged = make_merged_e(
|
||||
e, shift + B, std::move(v1), hash1, std::move(v2), hash2);
|
||||
IMMER_TRY {
|
||||
return owned(
|
||||
make_inner_n(
|
||||
1, static_cast<count_t>(idx1 >> shift), merged),
|
||||
e);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
delete_deep_shift(merged, shift + B);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
} else {
|
||||
auto r = make_inner_n(0,
|
||||
static_cast<count_t>(idx1 >> shift),
|
||||
std::move(v1),
|
||||
static_cast<count_t>(idx2 >> shift),
|
||||
std::move(v2));
|
||||
return owned_values(r, e);
|
||||
}
|
||||
} else {
|
||||
return owned(make_collision(std::move(v1), std::move(v2)), e);
|
||||
}
|
||||
}
|
||||
|
||||
node_t* inc()
|
||||
{
|
||||
refs(this).inc();
|
||||
@ -679,7 +1052,6 @@ struct node
|
||||
}
|
||||
|
||||
bool dec() const { return refs(this).dec(); }
|
||||
void dec_unsafe() const { refs(this).dec_unsafe(); }
|
||||
|
||||
static void inc_nodes(node_t** p, count_t n)
|
||||
{
|
||||
@ -690,6 +1062,7 @@ struct node
|
||||
static void delete_values(values_t* p, count_t n)
|
||||
{
|
||||
assert(p);
|
||||
detail::destroy_n((T*) &p->d.buffer, n);
|
||||
deallocate_values(p, n);
|
||||
}
|
||||
|
||||
@ -708,6 +1081,7 @@ struct node
|
||||
assert(p);
|
||||
IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision);
|
||||
auto n = p->collision_count();
|
||||
detail::destroy_n(p->collisions(), n);
|
||||
deallocate_collision(p, n);
|
||||
}
|
||||
|
||||
@ -741,13 +1115,11 @@ struct node
|
||||
|
||||
static void deallocate_values(values_t* p, count_t n)
|
||||
{
|
||||
destroy_n((T*) &p->d.buffer, n);
|
||||
heap::deallocate(node_t::sizeof_values_n(n), p);
|
||||
}
|
||||
|
||||
static void deallocate_collision(node_t* p, count_t n)
|
||||
{
|
||||
destroy_n(p->collisions(), n);
|
||||
heap::deallocate(node_t::sizeof_collision_n(n), p);
|
||||
}
|
||||
|
||||
|
@ -82,19 +82,6 @@ protected:
|
||||
std::is_base_of<std::bidirectional_iterator_tag,
|
||||
IteratorCategoryT>::value;
|
||||
|
||||
class reference_proxy
|
||||
{
|
||||
friend iterator_facade;
|
||||
DerivedT iter_;
|
||||
|
||||
reference_proxy(DerivedT iter)
|
||||
: iter_{std::move(iter)}
|
||||
{}
|
||||
|
||||
public:
|
||||
operator ReferenceT() const { return *iter_; }
|
||||
};
|
||||
|
||||
const DerivedT& derived() const
|
||||
{
|
||||
static_assert(std::is_base_of<iterator_facade, DerivedT>::value,
|
||||
@ -111,10 +98,10 @@ protected:
|
||||
public:
|
||||
ReferenceT operator*() const { return access_t::dereference(derived()); }
|
||||
PointerT operator->() const { return &access_t::dereference(derived()); }
|
||||
reference_proxy operator[](DifferenceTypeT n) const
|
||||
ReferenceT operator[](DifferenceTypeT n) const
|
||||
{
|
||||
static_assert(is_random_access, "");
|
||||
return derived() + n;
|
||||
return *(derived() + n);
|
||||
}
|
||||
|
||||
friend bool operator==(const DerivedT& a, const DerivedT& b)
|
||||
|
@ -199,17 +199,23 @@ struct node
|
||||
}
|
||||
static ownee_t& ownee(node_t* x) { return get<ownee_t>(x->impl); }
|
||||
|
||||
static node_t* make_inner_n(count_t n)
|
||||
static node_t* make_inner_n_into(void* buffer, std::size_t size, count_t n)
|
||||
{
|
||||
assert(n <= branches<B>);
|
||||
auto m = heap::allocate(sizeof_inner_n(n));
|
||||
auto p = new (m) node_t;
|
||||
assert(size >= sizeof_inner_n(n));
|
||||
auto p = new (buffer) node_t;
|
||||
p->impl.d.data.inner.relaxed = nullptr;
|
||||
#if IMMER_TAGGED_NODE
|
||||
p->impl.d.kind = node_t::kind_t::inner;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
static node_t* make_inner_n(count_t n)
|
||||
{
|
||||
assert(n <= branches<B>);
|
||||
auto m = heap::allocate(sizeof_inner_n(n));
|
||||
return make_inner_n_into(m, sizeof_inner_n(n), n);
|
||||
}
|
||||
|
||||
static node_t* make_inner_e(edit_t e)
|
||||
{
|
||||
@ -310,16 +316,24 @@ struct node
|
||||
});
|
||||
}
|
||||
|
||||
static node_t* make_leaf_n(count_t n)
|
||||
static node_t* make_leaf_n_into(void* buffer, std::size_t size, count_t n)
|
||||
{
|
||||
assert(n <= branches<BL>);
|
||||
auto p = new (heap::allocate(sizeof_leaf_n(n))) node_t;
|
||||
assert(size >= sizeof_leaf_n(n));
|
||||
auto p = new (buffer) node_t;
|
||||
#if IMMER_TAGGED_NODE
|
||||
p->impl.d.kind = node_t::kind_t::leaf;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
static node_t* make_leaf_n(count_t n)
|
||||
{
|
||||
assert(n <= branches<BL>);
|
||||
auto m = heap::allocate(sizeof_leaf_n(n));
|
||||
return make_leaf_n_into(m, sizeof_leaf_n(n), n);
|
||||
}
|
||||
|
||||
static node_t* make_leaf_e(edit_t e)
|
||||
{
|
||||
auto p = new (heap::allocate(max_sizeof_leaf)) node_t;
|
||||
@ -511,7 +525,7 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
auto dst = make_inner_n(n);
|
||||
inc_nodes(src->inner(), n);
|
||||
std::uninitialized_copy(src->inner(), src->inner() + n, dst->inner());
|
||||
std::copy(src->inner(), src->inner() + n, dst->inner());
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -536,7 +550,20 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
auto p = src->inner();
|
||||
inc_nodes(p, n);
|
||||
std::uninitialized_copy(p, p + n, dst->inner());
|
||||
std::copy(p, p + n, dst->inner());
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* do_copy_inner_replace(
|
||||
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner);
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
auto p = src->inner();
|
||||
inc_nodes(p, offset);
|
||||
inc_nodes(p + offset + 1, n - offset - 1);
|
||||
std::copy(p, p + n, dst->inner());
|
||||
dst->inner()[offset] = child;
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -582,6 +609,23 @@ struct node
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* do_copy_inner_replace_r(
|
||||
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner);
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner);
|
||||
auto src_r = src->relaxed();
|
||||
auto dst_r = dst->relaxed();
|
||||
auto p = src->inner();
|
||||
inc_nodes(p, offset);
|
||||
inc_nodes(p + offset + 1, n - offset - 1);
|
||||
std::copy(src->inner(), src->inner() + n, dst->inner());
|
||||
std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
||||
dst_r->d.count = n;
|
||||
dst->inner()[offset] = child;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n)
|
||||
{
|
||||
if (embed_relaxed)
|
||||
@ -593,12 +637,28 @@ struct node
|
||||
}
|
||||
}
|
||||
|
||||
static node_t* do_copy_inner_replace_sr(
|
||||
node_t* dst, node_t* src, count_t n, count_t offset, node_t* child)
|
||||
{
|
||||
if (embed_relaxed)
|
||||
return do_copy_inner_replace_r(dst, src, n, offset, child);
|
||||
else {
|
||||
auto p = src->inner();
|
||||
inc_nodes(p, offset);
|
||||
inc_nodes(p + offset + 1, n - offset - 1);
|
||||
std::copy(p, p + n, dst->inner());
|
||||
dst->inner()[offset] = child;
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
|
||||
static node_t* copy_leaf(node_t* src, count_t n)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_n(n);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
||||
detail::uninitialized_copy(
|
||||
src->leaf(), src->leaf() + n, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
heap::deallocate(node_t::sizeof_leaf_n(n), dst);
|
||||
@ -612,7 +672,8 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_e(e);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
||||
detail::uninitialized_copy(
|
||||
src->leaf(), src->leaf() + n, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
heap::deallocate(node_t::max_sizeof_leaf, dst);
|
||||
@ -627,7 +688,8 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_n(allocn);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf());
|
||||
detail::uninitialized_copy(
|
||||
src->leaf(), src->leaf() + n, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
heap::deallocate(node_t::sizeof_leaf_n(allocn), dst);
|
||||
@ -642,7 +704,7 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_n(n1 + n2);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -650,11 +712,11 @@ struct node
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->leaf(), n1);
|
||||
detail::destroy_n(dst->leaf(), n1);
|
||||
heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
@ -668,7 +730,7 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_e(e);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src1->leaf(), src1->leaf() + n1, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -676,11 +738,11 @@ struct node
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src2->leaf(), src2->leaf() + n2, dst->leaf() + n1);
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->leaf(), n1);
|
||||
detail::destroy_n(dst->leaf(), n1);
|
||||
heap::deallocate(max_sizeof_leaf, dst);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
@ -692,7 +754,7 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_e(e);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -707,7 +769,7 @@ struct node
|
||||
IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf);
|
||||
auto dst = make_leaf_n(last - idx);
|
||||
IMMER_TRY {
|
||||
std::uninitialized_copy(
|
||||
detail::uninitialized_copy(
|
||||
src->leaf() + idx, src->leaf() + last, dst->leaf());
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -725,7 +787,7 @@ struct node
|
||||
new (dst->leaf() + n) T{std::forward<U>(x)};
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(dst->leaf(), n);
|
||||
detail::destroy_n(dst->leaf(), n);
|
||||
heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
@ -788,7 +850,7 @@ struct node
|
||||
static void delete_leaf(node_t* p, count_t n)
|
||||
{
|
||||
IMMER_ASSERT_TAGGED(p->kind() == kind_t::leaf);
|
||||
destroy_n(p->leaf(), n);
|
||||
detail::destroy_n(p->leaf(), n);
|
||||
heap::deallocate(ownee(p).owned() ? node_t::max_sizeof_leaf
|
||||
: node_t::sizeof_leaf_n(n),
|
||||
p);
|
||||
@ -814,10 +876,12 @@ struct node
|
||||
auto dst_r = impl.d.data.inner.relaxed =
|
||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||
if (src_r) {
|
||||
node_t::refs(src_r).dec_unsafe();
|
||||
auto n = dst_r->d.count = src_r->d.count;
|
||||
std::copy(
|
||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
||||
if (node_t::refs(src_r).dec())
|
||||
heap::deallocate(node_t::sizeof_inner_r_n(n),
|
||||
src_r);
|
||||
}
|
||||
node_t::ownee(dst_r) = e;
|
||||
return dst_r;
|
||||
@ -839,10 +903,12 @@ struct node
|
||||
auto dst_r = impl.d.data.inner.relaxed =
|
||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||
if (src_r) {
|
||||
node_t::refs(src_r).dec_unsafe();
|
||||
auto n = dst_r->d.count = src_r->d.count;
|
||||
std::copy(
|
||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
||||
if (node_t::refs(src_r).dec())
|
||||
heap::deallocate(node_t::sizeof_inner_r_n(n),
|
||||
src_r);
|
||||
}
|
||||
node_t::ownee(dst_r) = ec;
|
||||
return dst_r;
|
||||
@ -863,9 +929,11 @@ struct node
|
||||
auto dst_r =
|
||||
new (heap::allocate(max_sizeof_relaxed)) relaxed_t;
|
||||
if (src_r) {
|
||||
node_t::refs(src_r).dec_unsafe();
|
||||
std::copy(
|
||||
src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes);
|
||||
if (node_t::refs(src_r).dec())
|
||||
heap::deallocate(node_t::sizeof_inner_r_n(n),
|
||||
src_r);
|
||||
}
|
||||
dst_r->d.count = n;
|
||||
node_t::ownee(dst_r) = e;
|
||||
@ -887,7 +955,6 @@ struct node
|
||||
}
|
||||
|
||||
bool dec() const { return refs(this).dec(); }
|
||||
void dec_unsafe() const { refs(this).dec_unsafe(); }
|
||||
|
||||
static void inc_nodes(node_t** p, count_t n)
|
||||
{
|
||||
|
@ -92,7 +92,7 @@ struct for_each_chunk_visitor : visitor_base<for_each_chunk_visitor>
|
||||
static void visit_leaf(Pos&& pos, Fn&& fn)
|
||||
{
|
||||
auto data = pos.node()->leaf();
|
||||
fn(data, data + pos.count());
|
||||
fn(as_const(data), as_const(data) + pos.count());
|
||||
}
|
||||
};
|
||||
|
||||
@ -109,7 +109,7 @@ struct for_each_chunk_p_visitor : visitor_base<for_each_chunk_p_visitor>
|
||||
template <typename Pos, typename Fn>
|
||||
static bool visit_leaf(Pos&& pos, Fn&& fn)
|
||||
{
|
||||
auto data = pos.node()->leaf();
|
||||
auto data = as_const(pos.node()->leaf());
|
||||
return fn(data, data + pos.count());
|
||||
}
|
||||
};
|
||||
@ -468,9 +468,8 @@ struct update_visitor : visitor_base<update_visitor<NodeT>>
|
||||
auto node = node_t::make_inner_sr_n(count, pos.relaxed());
|
||||
IMMER_TRY {
|
||||
auto child = pos.towards_oh(this_t{}, idx, offset, fn);
|
||||
node_t::do_copy_inner_sr(node, pos.node(), count);
|
||||
node->inner()[offset]->dec_unsafe();
|
||||
node->inner()[offset] = child;
|
||||
node_t::do_copy_inner_replace_sr(
|
||||
node, pos.node(), count, offset, child);
|
||||
return node;
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -487,9 +486,8 @@ struct update_visitor : visitor_base<update_visitor<NodeT>>
|
||||
auto node = node_t::make_inner_n(count);
|
||||
IMMER_TRY {
|
||||
auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn);
|
||||
node_t::do_copy_inner(node, pos.node(), count);
|
||||
node->inner()[offset]->dec_unsafe();
|
||||
node->inner()[offset] = child;
|
||||
node_t::do_copy_inner_replace(
|
||||
node, pos.node(), count, offset, child);
|
||||
return node;
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
@ -1067,7 +1065,7 @@ struct slice_right_mut_visitor
|
||||
node->inc();
|
||||
return std::make_tuple(0, nullptr, new_tail_size, node);
|
||||
} else if (mutate) {
|
||||
destroy_n(node->leaf() + new_tail_size,
|
||||
detail::destroy_n(node->leaf() + new_tail_size,
|
||||
old_tail_size - new_tail_size);
|
||||
return std::make_tuple(0, nullptr, new_tail_size, node);
|
||||
} else {
|
||||
@ -1277,7 +1275,7 @@ struct slice_left_mut_visitor
|
||||
pos.each_left(dec_visitor{}, idx);
|
||||
pos.copy_sizes(
|
||||
idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1);
|
||||
std::uninitialized_copy(node->inner() + idx + 1,
|
||||
std::copy(node->inner() + idx + 1,
|
||||
node->inner() + count,
|
||||
newn->inner() + 1);
|
||||
newn->inner()[0] = get<1>(subs);
|
||||
@ -1348,7 +1346,7 @@ struct slice_left_mut_visitor
|
||||
idx + 1, newcount - 1, newr->d.sizes[0], newr->d.sizes + 1);
|
||||
newr->d.count = newcount;
|
||||
newn->inner()[0] = get<1>(subs);
|
||||
std::uninitialized_copy(node->inner() + idx + 1,
|
||||
std::copy(node->inner() + idx + 1,
|
||||
node->inner() + count,
|
||||
newn->inner() + 1);
|
||||
if (!mutate) {
|
||||
@ -1386,7 +1384,7 @@ struct slice_left_mut_visitor
|
||||
auto data = node->leaf();
|
||||
auto newcount = count - idx;
|
||||
std::move(data + idx, data + count, data);
|
||||
destroy_n(data + newcount, idx);
|
||||
detail::destroy_n(data + newcount, idx);
|
||||
return std::make_tuple(0, node);
|
||||
} else {
|
||||
auto newn = node_t::copy_leaf_e(e, node, idx, count);
|
||||
@ -1439,7 +1437,7 @@ struct slice_left_visitor : visitor_base<slice_left_visitor<NodeT, Collapse>>
|
||||
assert(newr->d.sizes[newr->d.count - 1] ==
|
||||
pos.size() - dropped_size);
|
||||
newn->inner()[0] = get<1>(subs);
|
||||
std::uninitialized_copy(n->inner() + idx + 1,
|
||||
std::copy(n->inner() + idx + 1,
|
||||
n->inner() + count,
|
||||
newn->inner() + 1);
|
||||
node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1);
|
||||
@ -1627,7 +1625,7 @@ struct concat_merger
|
||||
auto data = to_->leaf();
|
||||
auto to_copy =
|
||||
std::min(from_count - from_offset, *curr_ - to_offset_);
|
||||
std::uninitialized_copy(from_data + from_offset,
|
||||
detail::uninitialized_copy(from_data + from_offset,
|
||||
from_data + from_offset + to_copy,
|
||||
data + to_offset_);
|
||||
to_offset_ += to_copy;
|
||||
@ -1662,7 +1660,7 @@ struct concat_merger
|
||||
auto data = to_->inner();
|
||||
auto to_copy =
|
||||
std::min(from_count - from_offset, *curr_ - to_offset_);
|
||||
std::uninitialized_copy(from_data + from_offset,
|
||||
std::copy(from_data + from_offset,
|
||||
from_data + from_offset + to_copy,
|
||||
data + to_offset_);
|
||||
node_t::inc_nodes(from_data + from_offset, to_copy);
|
||||
@ -2124,7 +2122,7 @@ struct concat_merger_mut
|
||||
data + to_offset_);
|
||||
} else {
|
||||
if (!from_mutate)
|
||||
std::uninitialized_copy(from_data + from_offset,
|
||||
detail::uninitialized_copy(from_data + from_offset,
|
||||
from_data + from_offset +
|
||||
to_copy,
|
||||
data + to_offset_);
|
||||
|
@ -15,9 +15,9 @@
|
||||
#include <immer/detail/type_traits.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace immer {
|
||||
namespace detail {
|
||||
@ -465,7 +465,7 @@ struct rbtree
|
||||
auto ts = size - tail_off;
|
||||
auto newts = new_size - tail_off;
|
||||
if (tail->can_mutate(e)) {
|
||||
destroy_n(tail->leaf() + newts, ts - newts);
|
||||
detail::destroy_n(tail->leaf() + newts, ts - newts);
|
||||
} else {
|
||||
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
||||
dec_leaf(tail, ts);
|
||||
|
@ -49,13 +49,21 @@ struct rrbtree
|
||||
|
||||
static node_t* empty_root()
|
||||
{
|
||||
static const auto empty_ = node_t::make_inner_n(0u);
|
||||
static const auto empty_ = []{
|
||||
constexpr auto size = node_t::sizeof_inner_n(0);
|
||||
static std::aligned_storage_t<size, alignof(std::max_align_t)> storage;
|
||||
return node_t::make_inner_n_into(&storage, size, 0u);
|
||||
}();
|
||||
return empty_->inc();
|
||||
}
|
||||
|
||||
static node_t* empty_tail()
|
||||
{
|
||||
static const auto empty_ = node_t::make_leaf_n(0u);
|
||||
static const auto empty_ = []{
|
||||
constexpr auto size = node_t::sizeof_leaf_n(0);
|
||||
static std::aligned_storage_t<size, alignof(std::max_align_t)> storage;
|
||||
return node_t::make_leaf_n_into(&storage, size, 0u);
|
||||
}();
|
||||
return empty_->inc();
|
||||
}
|
||||
|
||||
@ -90,7 +98,7 @@ struct rrbtree
|
||||
return result;
|
||||
}
|
||||
|
||||
rrbtree()
|
||||
rrbtree() noexcept
|
||||
: size{0}
|
||||
, shift{BL}
|
||||
, root{empty_root()}
|
||||
@ -99,7 +107,7 @@ struct rrbtree
|
||||
assert(check_tree());
|
||||
}
|
||||
|
||||
rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t)
|
||||
rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) noexcept
|
||||
: size{sz}
|
||||
, shift{sh}
|
||||
, root{r}
|
||||
@ -108,13 +116,13 @@ struct rrbtree
|
||||
assert(check_tree());
|
||||
}
|
||||
|
||||
rrbtree(const rrbtree& other)
|
||||
rrbtree(const rrbtree& other) noexcept
|
||||
: rrbtree{other.size, other.shift, other.root, other.tail}
|
||||
{
|
||||
inc();
|
||||
}
|
||||
|
||||
rrbtree(rrbtree&& other)
|
||||
rrbtree(rrbtree&& other) noexcept
|
||||
: rrbtree{}
|
||||
{
|
||||
swap(*this, other);
|
||||
@ -127,13 +135,13 @@ struct rrbtree
|
||||
return *this;
|
||||
}
|
||||
|
||||
rrbtree& operator=(rrbtree&& other)
|
||||
rrbtree& operator=(rrbtree&& other) noexcept
|
||||
{
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend void swap(rrbtree& x, rrbtree& y)
|
||||
friend void swap(rrbtree& x, rrbtree& y) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
swap(x.size, y.size);
|
||||
@ -574,7 +582,7 @@ struct rrbtree
|
||||
auto ts = size - tail_off;
|
||||
auto newts = new_size - tail_off;
|
||||
if (tail->can_mutate(e)) {
|
||||
destroy_n(tail->leaf() + newts, ts - newts);
|
||||
detail::destroy_n(tail->leaf() + newts, ts - newts);
|
||||
} else {
|
||||
auto new_tail = node_t::copy_leaf_e(e, tail, newts);
|
||||
dec_leaf(tail, ts);
|
||||
@ -793,7 +801,7 @@ struct rrbtree
|
||||
return;
|
||||
} else if (tail_size + r.size <= branches<BL>) {
|
||||
l.ensure_mutable_tail(el, tail_size);
|
||||
std::uninitialized_copy(r.tail->leaf(),
|
||||
detail::uninitialized_copy(r.tail->leaf(),
|
||||
r.tail->leaf() + r.size,
|
||||
l.tail->leaf() + tail_size);
|
||||
l.size += r.size;
|
||||
@ -801,7 +809,7 @@ struct rrbtree
|
||||
} else {
|
||||
auto remaining = branches<BL> - 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,
|
||||
l.tail->leaf() + tail_size);
|
||||
IMMER_TRY {
|
||||
@ -819,7 +827,7 @@ struct rrbtree
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
@ -1066,7 +1074,7 @@ struct rrbtree
|
||||
r.tail->leaf() + r.size,
|
||||
l.tail->leaf() + tail_size);
|
||||
else
|
||||
std::uninitialized_copy(r.tail->leaf(),
|
||||
detail::uninitialized_copy(r.tail->leaf(),
|
||||
r.tail->leaf() + r.size,
|
||||
l.tail->leaf() + tail_size);
|
||||
l.size += r.size;
|
||||
@ -1079,7 +1087,7 @@ struct rrbtree
|
||||
r.tail->leaf() + remaining,
|
||||
l.tail->leaf() + tail_size);
|
||||
else
|
||||
std::uninitialized_copy(r.tail->leaf(),
|
||||
detail::uninitialized_copy(r.tail->leaf(),
|
||||
r.tail->leaf() + remaining,
|
||||
l.tail->leaf() + tail_size);
|
||||
IMMER_TRY {
|
||||
@ -1097,7 +1105,7 @@ struct rrbtree
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||
detail::destroy_n(r.tail->leaf() + tail_size, remaining);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
|
@ -201,23 +201,5 @@ struct std_distance_supports<
|
||||
template <typename T, typename U>
|
||||
constexpr bool std_distance_supports_v = std_distance_supports<T, U>::value;
|
||||
|
||||
template <typename T, typename U, typename V, typename = void>
|
||||
struct std_uninitialized_copy_supports : std::false_type
|
||||
{};
|
||||
|
||||
template <typename T, typename U, typename V>
|
||||
struct std_uninitialized_copy_supports<
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
void_t<decltype(std::uninitialized_copy(
|
||||
std::declval<T>(), std::declval<U>(), std::declval<V>()))>>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
template <typename T, typename U, typename V>
|
||||
constexpr bool std_uninitialized_copy_supports_v =
|
||||
std_uninitialized_copy_supports<T, U, V>::value;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace immer
|
||||
|
@ -24,6 +24,18 @@
|
||||
namespace immer {
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
const T* as_const(T* x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& as_const(T& x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using aligned_storage_for =
|
||||
typename std::aligned_storage<sizeof(T), alignof(T)>::type;
|
||||
@ -39,26 +51,110 @@ T&& auto_const_cast(const T&& x)
|
||||
return const_cast<T&&>(std::move(x));
|
||||
}
|
||||
|
||||
template <typename Iter1, typename Iter2>
|
||||
auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out)
|
||||
template <class T>
|
||||
inline auto destroy_at(T* p) noexcept
|
||||
-> std::enable_if_t<std::is_trivially_destructible<T>::value>
|
||||
{
|
||||
return std::uninitialized_copy(
|
||||
std::make_move_iterator(in1), std::make_move_iterator(in2), out);
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void destroy(T* first, T* last)
|
||||
inline auto destroy_at(T* p) noexcept
|
||||
-> std::enable_if_t<!std::is_trivially_destructible<T>::value>
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
first->~T();
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class T, class Size>
|
||||
void destroy_n(T* p, Size n)
|
||||
template <typename Iter1>
|
||||
constexpr bool can_trivially_detroy = std::is_trivially_destructible<
|
||||
typename std::iterator_traits<Iter1>::value_type>::value;
|
||||
|
||||
template <class Iter>
|
||||
auto destroy(Iter, Iter last) noexcept
|
||||
-> std::enable_if_t<can_trivially_detroy<Iter>, Iter>
|
||||
{
|
||||
auto e = p + n;
|
||||
for (; p != e; ++p)
|
||||
p->~T();
|
||||
return last;
|
||||
}
|
||||
template <class Iter>
|
||||
auto destroy(Iter first, Iter last) noexcept
|
||||
-> std::enable_if_t<!can_trivially_detroy<Iter>, Iter>
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
detail::destroy_at(std::addressof(*first));
|
||||
return first;
|
||||
}
|
||||
|
||||
template <class Iter, class Size>
|
||||
auto destroy_n(Iter first, Size n) noexcept
|
||||
-> std::enable_if_t<can_trivially_detroy<Iter>, Iter>
|
||||
{
|
||||
return first + n;
|
||||
}
|
||||
template <class Iter, class Size>
|
||||
auto destroy_n(Iter first, Size n) noexcept
|
||||
-> std::enable_if_t<!can_trivially_detroy<Iter>, Iter>
|
||||
{
|
||||
for (; n > 0; (void) ++first, --n)
|
||||
detail::destroy_at(std::addressof(*first));
|
||||
return first;
|
||||
}
|
||||
|
||||
template <typename Iter1, typename Iter2>
|
||||
constexpr bool can_trivially_copy =
|
||||
std::is_same<typename std::iterator_traits<Iter1>::value_type,
|
||||
typename std::iterator_traits<Iter2>::value_type>::value&&
|
||||
std::is_trivially_copyable<
|
||||
typename std::iterator_traits<Iter1>::value_type>::value;
|
||||
|
||||
template <typename Iter1, typename Iter2>
|
||||
auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) noexcept
|
||||
-> std::enable_if_t<can_trivially_copy<Iter1, Iter2>, Iter2>
|
||||
{
|
||||
return std::copy(first, last, out);
|
||||
}
|
||||
template <typename Iter1, typename Iter2>
|
||||
auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out)
|
||||
-> std::enable_if_t<!can_trivially_copy<Iter1, Iter2>, Iter2>
|
||||
|
||||
{
|
||||
using value_t = typename std::iterator_traits<Iter2>::value_type;
|
||||
auto current = out;
|
||||
IMMER_TRY {
|
||||
for (; first != last; ++first, (void) ++current) {
|
||||
::new (const_cast<void*>(static_cast<const volatile void*>(
|
||||
std::addressof(*current)))) value_t(std::move(*first));
|
||||
}
|
||||
return current;
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy(out, current);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SourceIter, typename Sent, typename SinkIter>
|
||||
auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) noexcept
|
||||
-> std::enable_if_t<can_trivially_copy<SourceIter, SinkIter>, SinkIter>
|
||||
{
|
||||
return std::copy(first, last, out);
|
||||
}
|
||||
template <typename SourceIter, typename Sent, typename SinkIter>
|
||||
auto uninitialized_copy(SourceIter first, Sent last, SinkIter out)
|
||||
-> std::enable_if_t<!can_trivially_copy<SourceIter, SinkIter>, SinkIter>
|
||||
{
|
||||
using value_t = typename std::iterator_traits<SinkIter>::value_type;
|
||||
auto current = out;
|
||||
IMMER_TRY {
|
||||
for (; first != last; ++first, (void) ++current) {
|
||||
::new (const_cast<void*>(static_cast<const volatile void*>(
|
||||
std::addressof(*current)))) value_t(*first);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
detail::destroy(out, current);
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Heap, typename T, typename... Args>
|
||||
@ -214,53 +310,5 @@ distance(Iterator first, Sentinel last)
|
||||
return last - first;
|
||||
}
|
||||
|
||||
/*!
|
||||
* An alias to `std::uninitialized_copy`
|
||||
*/
|
||||
template <
|
||||
typename Iterator,
|
||||
typename Sentinel,
|
||||
typename SinkIter,
|
||||
std::enable_if_t<
|
||||
detail::std_uninitialized_copy_supports_v<Iterator, Sentinel, SinkIter>,
|
||||
bool> = true>
|
||||
SinkIter uninitialized_copy(Iterator first, Sentinel last, SinkIter d_first)
|
||||
{
|
||||
return std::uninitialized_copy(first, last, d_first);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Equivalent of the `std::uninitialized_copy` applied to the
|
||||
* sentinel-delimited forward range @f$ [first, last) @f$
|
||||
*/
|
||||
template <typename SourceIter,
|
||||
typename Sent,
|
||||
typename SinkIter,
|
||||
std::enable_if_t<
|
||||
(!detail::std_uninitialized_copy_supports_v<SourceIter,
|
||||
Sent,
|
||||
SinkIter>) &&detail::
|
||||
compatible_sentinel_v<SourceIter, Sent> &&
|
||||
detail::is_forward_iterator_v<SinkIter>,
|
||||
bool> = true>
|
||||
SinkIter uninitialized_copy(SourceIter first, Sent last, SinkIter d_first)
|
||||
{
|
||||
auto current = d_first;
|
||||
IMMER_TRY {
|
||||
while (first != last) {
|
||||
*current++ = *first;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
IMMER_CATCH (...) {
|
||||
using Value = typename std::iterator_traits<SinkIter>::value_type;
|
||||
for (; d_first != current; ++d_first) {
|
||||
d_first->~Value();
|
||||
}
|
||||
IMMER_RETHROW;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace immer
|
||||
|
@ -249,7 +249,7 @@ public:
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a flex_vector with `value` inserted at the frony. It may
|
||||
* Returns a flex_vector with `value` inserted at the front. It may
|
||||
* allocate memory and its complexity is @f$ O(log(size)) @f$.
|
||||
*
|
||||
* @rst
|
||||
@ -503,6 +503,17 @@ public:
|
||||
IMMER_NODISCARD transient_type transient() const& { return impl_; }
|
||||
IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
|
||||
|
||||
/*!
|
||||
* Returns a value that can be used as identity for the container. If two
|
||||
* values have the same identity, they are guaranteed to be equal and to
|
||||
* contain the same objects. However, two equal containers are not
|
||||
* guaranteed to have the same identity.
|
||||
*/
|
||||
std::pair<void*, void*> identity() const
|
||||
{
|
||||
return {impl_.root, impl_.tail};
|
||||
}
|
||||
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
|
||||
@ -605,4 +616,9 @@ private:
|
||||
impl_t impl_ = {};
|
||||
};
|
||||
|
||||
static_assert(std::is_nothrow_move_constructible<flex_vector<int>>::value,
|
||||
"flex_vector is not nothrow move constructible");
|
||||
static_assert(std::is_nothrow_move_assignable<flex_vector<int>>::value,
|
||||
"flex_vector is not nothrow move assignable");
|
||||
|
||||
} // namespace immer
|
||||
|
@ -33,7 +33,7 @@ struct cpp_heap
|
||||
* `allocate`. One must not use nor deallocate again a memory
|
||||
* region that once it has been deallocated.
|
||||
*/
|
||||
static void deallocate(std::size_t size, void* data)
|
||||
static void deallocate(std::size_t, void* data)
|
||||
{
|
||||
::operator delete(data);
|
||||
}
|
||||
|
@ -85,8 +85,7 @@ struct enable_optimized_heap_policy
|
||||
* @rst
|
||||
*
|
||||
* .. tip:: For many applications that use immutable data structures
|
||||
* significantly, this is actually the best heap policy, and it
|
||||
* might become the default in the future.
|
||||
* significantly, this is actually the best heap policy.
|
||||
*
|
||||
* Note that most our data structures internally use trees with the
|
||||
* same big branching factors. This means that all *vectors*,
|
||||
|
@ -14,8 +14,8 @@
|
||||
#include <immer/memory_policy.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace immer {
|
||||
|
||||
@ -41,7 +41,7 @@ class map_transient;
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* This cotainer provides a good trade-off between cache locality,
|
||||
* This container provides a good trade-off between cache locality,
|
||||
* search, update performance and structural sharing. It does so by
|
||||
* storing the data in contiguous chunks of :math:`2^{B}` elements.
|
||||
* When storing big objects, the size of these contiguous chunks can
|
||||
@ -68,12 +68,19 @@ class map
|
||||
{
|
||||
using value_t = std::pair<K, T>;
|
||||
|
||||
using move_t =
|
||||
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
|
||||
|
||||
struct project_value
|
||||
{
|
||||
const T& operator()(const value_t& v) const noexcept
|
||||
{
|
||||
return v.second;
|
||||
}
|
||||
T&& operator()(value_t&& v) const noexcept
|
||||
{
|
||||
return std::move(v.second);
|
||||
}
|
||||
};
|
||||
|
||||
struct project_value_ptr
|
||||
@ -115,7 +122,10 @@ class map
|
||||
auto operator()(const value_t& v) { return Hash{}(v.first); }
|
||||
|
||||
template <typename Key>
|
||||
auto operator()(const Key& v) { return Hash{}(v); }
|
||||
auto operator()(const Key& v)
|
||||
{
|
||||
return Hash{}(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct equal_key
|
||||
@ -160,8 +170,29 @@ public:
|
||||
|
||||
using transient_type = map_transient<K, T, Hash, Equal, MemoryPolicy, B>;
|
||||
|
||||
using memory_policy_type = MemoryPolicy;
|
||||
|
||||
/*!
|
||||
* Default constructor. It creates a set of `size() == 0`. It
|
||||
* Constructs a map containing the elements in `values`.
|
||||
*/
|
||||
map(std::initializer_list<value_type> values)
|
||||
: impl_{impl_t::from_initializer_list(values)}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Constructs a map containing the elements in the range
|
||||
* defined by the input iterator `first` and range sentinel `last`.
|
||||
*/
|
||||
template <typename Iter,
|
||||
typename Sent,
|
||||
std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
|
||||
bool> = true>
|
||||
map(Iter first, Sent last)
|
||||
: impl_{impl_t::from_range(first, last)}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Default constructor. It creates a map of `size() == 0`. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
map() = default;
|
||||
@ -202,7 +233,9 @@ public:
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD size_type count(const Key& k) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
@ -229,7 +262,9 @@ public:
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T& operator[](const Key& k) const
|
||||
{
|
||||
return impl_.template get<project_value, default_value>(k);
|
||||
@ -252,7 +287,9 @@ public:
|
||||
* `std::out_of_range` error. It does not allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
const T& at(const Key& k) const
|
||||
{
|
||||
return impl_.template get<project_value, error_value>(k);
|
||||
@ -297,7 +334,7 @@ public:
|
||||
* ``std::optional<const T&>`` but this construction is not valid
|
||||
* in any current standard. As a compromise we return a
|
||||
* pointer, which has similar syntactic properties yet it is
|
||||
* unfortunatelly unnecessarily unrestricted.
|
||||
* unfortunately unnecessarily unrestricted.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
@ -307,7 +344,6 @@ public:
|
||||
detail::constantly<const T*, nullptr>>(k);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the value associated with the key `k`. If
|
||||
* the key is not contained in the map, a `nullptr` is returned.
|
||||
@ -317,7 +353,9 @@ public:
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template<typename Key, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T* find(const Key& k) const
|
||||
{
|
||||
return impl_.template get<project_value_ptr,
|
||||
@ -325,7 +363,7 @@ public:
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns whether the sets are equal.
|
||||
* Returns whether the maps are equal.
|
||||
*/
|
||||
IMMER_NODISCARD bool operator==(const map& other) const
|
||||
{
|
||||
@ -342,10 +380,14 @@ public:
|
||||
* It may allocate memory and its complexity is *effectively* @f$
|
||||
* O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD map insert(value_type value) const
|
||||
IMMER_NODISCARD map insert(value_type value) const&
|
||||
{
|
||||
return impl_.add(std::move(value));
|
||||
}
|
||||
IMMER_NODISCARD decltype(auto) insert(value_type value) &&
|
||||
{
|
||||
return insert_move(move_t{}, std::move(value));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a map containing the association `(k, v)`. If the key
|
||||
@ -353,10 +395,14 @@ public:
|
||||
* It may allocate memory and its complexity is *effectively* @f$
|
||||
* O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD map set(key_type k, mapped_type v) const
|
||||
IMMER_NODISCARD map set(key_type k, mapped_type v) const&
|
||||
{
|
||||
return impl_.add({std::move(k), std::move(v)});
|
||||
}
|
||||
IMMER_NODISCARD decltype(auto) set(key_type k, mapped_type v) &&
|
||||
{
|
||||
return set_move(move_t{}, std::move(k), std::move(v));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a map replacing the association `(k, v)` by the
|
||||
@ -366,22 +412,50 @@ public:
|
||||
* and its complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template <typename Fn>
|
||||
IMMER_NODISCARD map update(key_type k, Fn&& fn) const
|
||||
IMMER_NODISCARD map update(key_type k, Fn&& fn) const&
|
||||
{
|
||||
return impl_
|
||||
.template update<project_value, default_value, combine_value>(
|
||||
std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
template <typename Fn>
|
||||
IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) &&
|
||||
{
|
||||
return update_move(move_t{}, std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a map replacing the association `(k, v)` by the association new
|
||||
* association `(k, fn(v))`, where `v` is the currently associated value for
|
||||
* `k` in the map. It does nothing if `k` is not present in the map. It
|
||||
* may allocate memory and its complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template <typename Fn>
|
||||
IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const&
|
||||
{
|
||||
return impl_.template update_if_exists<project_value, combine_value>(
|
||||
std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
template <typename Fn>
|
||||
IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) &&
|
||||
{
|
||||
return update_if_exists_move(
|
||||
move_t{}, std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a map without the key `k`. If the key is not
|
||||
* associated in the map it returns the same map. It may allocate
|
||||
* memory and its complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD map erase(const K& k) const { return impl_.sub(k); }
|
||||
IMMER_NODISCARD map erase(const K& k) const& { return impl_.sub(k); }
|
||||
IMMER_NODISCARD decltype(auto) erase(const K& k) &&
|
||||
{
|
||||
return erase_move(move_t{}, k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns an @a transient form of this container, a
|
||||
* Returns a @a transient form of this container, an
|
||||
* `immer::map_transient`.
|
||||
*/
|
||||
IMMER_NODISCARD transient_type transient() const&
|
||||
@ -393,12 +467,79 @@ public:
|
||||
return transient_type{std::move(impl_)};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a value that can be used as identity for the container. If two
|
||||
* values have the same identity, they are guaranteed to be equal and to
|
||||
* contain the same objects. However, two equal containers are not
|
||||
* guaranteed to have the same identity.
|
||||
*/
|
||||
void* identity() const { return impl_.root; }
|
||||
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
|
||||
private:
|
||||
friend transient_type;
|
||||
|
||||
map&& insert_move(std::true_type, value_type value)
|
||||
{
|
||||
impl_.add_mut({}, std::move(value));
|
||||
return std::move(*this);
|
||||
}
|
||||
map insert_move(std::false_type, value_type value)
|
||||
{
|
||||
return impl_.add(std::move(value));
|
||||
}
|
||||
|
||||
map&& set_move(std::true_type, key_type k, mapped_type m)
|
||||
{
|
||||
impl_.add_mut({}, {std::move(k), std::move(m)});
|
||||
return std::move(*this);
|
||||
}
|
||||
map set_move(std::false_type, key_type k, mapped_type m)
|
||||
{
|
||||
return impl_.add({std::move(k), std::move(m)});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
map&& update_move(std::true_type, key_type k, Fn&& fn)
|
||||
{
|
||||
impl_.template update_mut<project_value, default_value, combine_value>(
|
||||
{}, std::move(k), std::forward<Fn>(fn));
|
||||
return std::move(*this);
|
||||
}
|
||||
template <typename Fn>
|
||||
map update_move(std::false_type, key_type k, Fn&& fn)
|
||||
{
|
||||
return impl_
|
||||
.template update<project_value, default_value, combine_value>(
|
||||
std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn)
|
||||
{
|
||||
impl_.template update_if_exists_mut<project_value, combine_value>(
|
||||
{}, std::move(k), std::forward<Fn>(fn));
|
||||
return std::move(*this);
|
||||
}
|
||||
template <typename Fn>
|
||||
map update_if_exists_move(std::false_type, key_type k, Fn&& fn)
|
||||
{
|
||||
return impl_.template update_if_exists<project_value, combine_value>(
|
||||
std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
map&& erase_move(std::true_type, const key_type& value)
|
||||
{
|
||||
impl_.sub_mut({}, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
map erase_move(std::false_type, const key_type& value)
|
||||
{
|
||||
return impl_.sub(value);
|
||||
}
|
||||
|
||||
map(impl_t impl)
|
||||
: impl_(std::move(impl))
|
||||
{}
|
||||
|
@ -15,18 +15,21 @@
|
||||
|
||||
namespace immer {
|
||||
|
||||
template <typename K,
|
||||
typename T,
|
||||
typename Hash,
|
||||
typename Equal,
|
||||
typename MemoryPolicy,
|
||||
detail::hamts::bits_t B>
|
||||
class map;
|
||||
|
||||
/*!
|
||||
* Mutable version of `immer::map`.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* .. admonition:: Become a sponsor!
|
||||
* :class: danger
|
||||
*
|
||||
* This component is planned but it has **not been implemented yet**.
|
||||
*
|
||||
* Transiens can critically improve the performance of applications
|
||||
* intensively using ``set`` and ``map``. If you are working for an
|
||||
* organization using the library in a commercial project, please consider
|
||||
* **sponsoring this work**: juanpe@sinusoid.al
|
||||
* Refer to :doc:`transients` to learn more about when and how to use
|
||||
* the mutable versions of immutable containers.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
@ -36,6 +39,280 @@ template <typename K,
|
||||
typename Equal = std::equal_to<K>,
|
||||
typename MemoryPolicy = default_memory_policy,
|
||||
detail::hamts::bits_t B = default_bits>
|
||||
class map_transient;
|
||||
class map_transient : MemoryPolicy::transience_t::owner
|
||||
{
|
||||
using base_t = typename MemoryPolicy::transience_t::owner;
|
||||
using owner_t = base_t;
|
||||
|
||||
public:
|
||||
using persistent_type = map<K, T, Hash, Equal, MemoryPolicy, B>;
|
||||
|
||||
using key_type = K;
|
||||
using mapped_type = T;
|
||||
using value_type = std::pair<K, T>;
|
||||
using size_type = detail::hamts::size_t;
|
||||
using diference_type = std::ptrdiff_t;
|
||||
using hasher = Hash;
|
||||
using key_equal = Equal;
|
||||
using reference = const value_type&;
|
||||
using const_reference = const value_type&;
|
||||
|
||||
using iterator = typename persistent_type::iterator;
|
||||
using const_iterator = iterator;
|
||||
|
||||
/*!
|
||||
* Default constructor. It creates a map of `size() == 0`. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
map_transient() = default;
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing at the first element of the
|
||||
* collection. It does not allocate memory and its complexity is
|
||||
* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD iterator begin() const { return {impl_}; }
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing just after the last element of the
|
||||
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD iterator end() const
|
||||
{
|
||||
return {impl_, typename iterator::end_t{}};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns the number of elements in the container. It does
|
||||
* not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD size_type size() const { return impl_.size; }
|
||||
|
||||
/*!
|
||||
* Returns `true` if there are no elements in the container. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
|
||||
|
||||
/*!
|
||||
* Returns `1` when the key `k` is contained in the map or `0`
|
||||
* otherwise. It won't allocate memory and its complexity is
|
||||
* *effectively* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD size_type count(const Key& k) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
detail::constantly<size_type, 0>>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns `1` when the key `k` is contained in the map or `0`
|
||||
* otherwise. It won't allocate memory and its complexity is
|
||||
* *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD size_type count(const K& k) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
detail::constantly<size_type, 0>>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a `const` reference to the values associated to the key
|
||||
* `k`. If the key is not contained in the map, it returns a
|
||||
* default constructed value. It does not allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T& operator[](const Key& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value,
|
||||
typename persistent_type::default_value>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a `const` reference to the values associated to the key
|
||||
* `k`. If the key is not contained in the map, it returns a
|
||||
* default constructed value. It does not allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD const T& operator[](const K& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value,
|
||||
typename persistent_type::default_value>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a `const` reference to the values associated to the key
|
||||
* `k`. If the key is not contained in the map, throws an
|
||||
* `std::out_of_range` error. It does not allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
const T& at(const Key& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value,
|
||||
typename persistent_type::error_value>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a `const` reference to the values associated to the key
|
||||
* `k`. If the key is not contained in the map, throws an
|
||||
* `std::out_of_range` error. It does not allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
const T& at(const K& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value,
|
||||
typename persistent_type::error_value>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the value associated with the key `k`. If
|
||||
* the key is not contained in the map, a `nullptr` is returned.
|
||||
* It does not allocate memory and its complexity is *effectively*
|
||||
* @f$ O(1) @f$.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* .. admonition:: Why doesn't this function return an iterator?
|
||||
*
|
||||
* Associative containers from the C++ standard library provide a
|
||||
* ``find`` method that returns an iterator pointing to the
|
||||
* element in the container or ``end()`` when the key is missing.
|
||||
* In the case of an unordered container, the only meaningful
|
||||
* thing one may do with it is to compare it with the end, to
|
||||
* test if the find was succesfull, and dereference it. This
|
||||
* comparison is cumbersome compared to testing for a non-empty
|
||||
* optional value. Furthermore, for an immutable container,
|
||||
* returning an iterator would have some additional performance
|
||||
* cost, with no benefits otherwise.
|
||||
*
|
||||
* In our opinion, this function should return a
|
||||
* ``std::optional<const T&>`` but this construction is not valid
|
||||
* in any current standard. As a compromise we return a
|
||||
* pointer, which has similar syntactic properties yet it is
|
||||
* unfortunately unnecessarily unrestricted.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
IMMER_NODISCARD const T* find(const K& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value_ptr,
|
||||
detail::constantly<const T*, nullptr>>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the value associated with the key `k`. If
|
||||
* the key is not contained in the map, a `nullptr` is returned.
|
||||
* It does not allocate memory and its complexity is *effectively*
|
||||
* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template <typename Key,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T* find(const Key& k) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value_ptr,
|
||||
detail::constantly<const T*, nullptr>>(k);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inserts the association `value`. If the key is already in the map, it
|
||||
* replaces its association in the map. It may allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
void insert(value_type value) { impl_.add_mut(*this, std::move(value)); }
|
||||
|
||||
/*!
|
||||
* Inserts the association `(k, v)`. If the key is already in the map, it
|
||||
* replaces its association in the map. It may allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
void set(key_type k, mapped_type v)
|
||||
{
|
||||
impl_.add_mut(*this, {std::move(k), std::move(v)});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Replaces the association `(k, v)` by the association new association `(k,
|
||||
* fn(v))`, where `v` is the currently associated value for `k` in the map
|
||||
* or a default constructed value otherwise. It may allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template <typename Fn>
|
||||
void update(key_type k, Fn&& fn)
|
||||
{
|
||||
impl_.template update_mut<typename persistent_type::project_value,
|
||||
typename persistent_type::default_value,
|
||||
typename persistent_type::combine_value>(
|
||||
*this, std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Replaces the association `(k, v)` by the association new association `(k,
|
||||
* fn(v))`, where `v` is the currently associated value for `k` in the map
|
||||
* or does nothing if `k` is not present in the map. It may allocate memory
|
||||
* and its complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
template <typename Fn>
|
||||
void update_if_exists(key_type k, Fn&& fn)
|
||||
{
|
||||
impl_.template update_if_exists_mut<
|
||||
typename persistent_type::project_value,
|
||||
typename persistent_type::combine_value>(
|
||||
*this, std::move(k), std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes the key `k` from the k. Does nothing if the key is not
|
||||
* associated in the map. It may allocate memory and its complexity is
|
||||
* *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
void erase(const K& k) { impl_.sub_mut(*this, k); }
|
||||
|
||||
/*!
|
||||
* Returns an @a immutable form of this container, an
|
||||
* `immer::map`.
|
||||
*/
|
||||
IMMER_NODISCARD persistent_type persistent() &
|
||||
{
|
||||
this->owner_t::operator=(owner_t{});
|
||||
return impl_;
|
||||
}
|
||||
IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); }
|
||||
|
||||
private:
|
||||
friend persistent_type;
|
||||
using impl_t = typename persistent_type::impl_t;
|
||||
|
||||
map_transient(impl_t impl)
|
||||
: impl_(std::move(impl))
|
||||
{}
|
||||
|
||||
impl_t impl_ = impl_t::empty();
|
||||
|
||||
public:
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
|
@ -24,7 +24,6 @@ struct no_refcount_policy
|
||||
|
||||
void inc() {}
|
||||
bool dec() { return false; }
|
||||
void dec_unsafe() {}
|
||||
bool unique() { return false; }
|
||||
};
|
||||
|
||||
|
@ -34,12 +34,6 @@ struct refcount_policy
|
||||
|
||||
bool dec() { return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); }
|
||||
|
||||
void dec_unsafe()
|
||||
{
|
||||
assert(refcount.load() > 1);
|
||||
refcount.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool unique() { return refcount == 1; }
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,6 @@ struct unsafe_refcount_policy
|
||||
|
||||
void inc() { ++refcount; }
|
||||
bool dec() { return --refcount == 0; }
|
||||
void dec_unsafe() { --refcount; }
|
||||
bool unique() { return refcount == 1; }
|
||||
};
|
||||
|
||||
|
@ -62,13 +62,15 @@ class set
|
||||
{
|
||||
using impl_t = detail::hamts::champ<T, Hash, Equal, MemoryPolicy, B>;
|
||||
|
||||
using move_t =
|
||||
std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
|
||||
|
||||
struct project_value_ptr
|
||||
{
|
||||
const T* operator()(const T& v) const noexcept { return &v; }
|
||||
};
|
||||
|
||||
public:
|
||||
using key_type = T;
|
||||
using value_type = T;
|
||||
using size_type = detail::hamts::size_t;
|
||||
using diference_type = std::ptrdiff_t;
|
||||
@ -83,12 +85,33 @@ public:
|
||||
|
||||
using transient_type = set_transient<T, Hash, Equal, MemoryPolicy, B>;
|
||||
|
||||
using memory_policy_type = MemoryPolicy;
|
||||
|
||||
/*!
|
||||
* Default constructor. It creates a set of `size() == 0`. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
set() = default;
|
||||
|
||||
/*!
|
||||
* Constructs a set containing the elements in `values`.
|
||||
*/
|
||||
set(std::initializer_list<value_type> values)
|
||||
: impl_{impl_t::from_initializer_list(values)}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Constructs a set containing the elements in the range
|
||||
* defined by the input iterator `first` and range sentinel `last`.
|
||||
*/
|
||||
template <typename Iter,
|
||||
typename Sent,
|
||||
std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
|
||||
bool> = true>
|
||||
set(Iter first, Sent last)
|
||||
: impl_{impl_t::from_range(first, last)}
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing at the first element of the
|
||||
* collection. It does not allocate memory and its complexity is
|
||||
@ -125,7 +148,9 @@ public:
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template<typename K, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename K,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD size_type count(const K& value) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
@ -164,7 +189,9 @@ public:
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template<typename K, typename U = Hash, typename = typename U::is_transparent>
|
||||
template <typename K,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T* find(const K& value) const
|
||||
{
|
||||
return impl_.template get<project_value_ptr,
|
||||
@ -188,17 +215,28 @@ public:
|
||||
* the set, it returns the same set. It may allocate memory and
|
||||
* its complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD set insert(T value) const
|
||||
IMMER_NODISCARD set insert(T value) const&
|
||||
{
|
||||
return impl_.add(std::move(value));
|
||||
}
|
||||
IMMER_NODISCARD decltype(auto) insert(T value) &&
|
||||
{
|
||||
return insert_move(move_t{}, std::move(value));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a set without `value`. If the `value` is not in the
|
||||
* set it returns the same set. It may allocate memory and its
|
||||
* complexity is *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD set erase(const T& value) const { return impl_.sub(value); }
|
||||
IMMER_NODISCARD set erase(const T& value) const&
|
||||
{
|
||||
return impl_.sub(value);
|
||||
}
|
||||
IMMER_NODISCARD decltype(auto) erase(const T& value) &&
|
||||
{
|
||||
return erase_move(move_t{}, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns an @a transient form of this container, a
|
||||
@ -213,12 +251,40 @@ public:
|
||||
return transient_type{std::move(impl_)};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a value that can be used as identity for the container. If two
|
||||
* values have the same identity, they are guaranteed to be equal and to
|
||||
* contain the same objects. However, two equal containers are not
|
||||
* guaranteed to have the same identity.
|
||||
*/
|
||||
void* identity() const { return impl_.root; }
|
||||
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
|
||||
private:
|
||||
friend transient_type;
|
||||
|
||||
set&& insert_move(std::true_type, value_type value)
|
||||
{
|
||||
impl_.add_mut({}, std::move(value));
|
||||
return std::move(*this);
|
||||
}
|
||||
set insert_move(std::false_type, value_type value)
|
||||
{
|
||||
return impl_.add(std::move(value));
|
||||
}
|
||||
|
||||
set&& erase_move(std::true_type, const value_type& value)
|
||||
{
|
||||
impl_.sub_mut({}, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
set erase_move(std::false_type, const value_type& value)
|
||||
{
|
||||
return impl_.sub(value);
|
||||
}
|
||||
|
||||
set(impl_t impl)
|
||||
: impl_(std::move(impl))
|
||||
{}
|
||||
|
@ -16,17 +16,12 @@
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* Mutable version of `immer::set`.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* .. admonition:: Become a sponsor!
|
||||
* :class: danger
|
||||
*
|
||||
* This component is planned but it has **not been implemented yet**.
|
||||
*
|
||||
* Transiens can critically improve the performance of applications
|
||||
* intensively using ``set`` and ``map``. If you are working for an
|
||||
* organization using the library in a commercial project, please consider
|
||||
* **sponsoring this work**: juanpe@sinusoid.al
|
||||
* Refer to :doc:`transients` to learn more about when and how to use
|
||||
* the mutable versions of immutable containers.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
@ -35,6 +30,155 @@ template <typename T,
|
||||
typename Equal = std::equal_to<T>,
|
||||
typename MemoryPolicy = default_memory_policy,
|
||||
detail::hamts::bits_t B = default_bits>
|
||||
class set_transient;
|
||||
class set_transient : MemoryPolicy::transience_t::owner
|
||||
{
|
||||
using base_t = typename MemoryPolicy::transience_t::owner;
|
||||
using owner_t = base_t;
|
||||
|
||||
public:
|
||||
using persistent_type = set<T, Hash, Equal, MemoryPolicy, B>;
|
||||
|
||||
using value_type = T;
|
||||
using size_type = detail::hamts::size_t;
|
||||
using diference_type = std::ptrdiff_t;
|
||||
using hasher = Hash;
|
||||
using key_equal = Equal;
|
||||
using reference = const T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = typename persistent_type::iterator;
|
||||
using const_iterator = iterator;
|
||||
|
||||
/*!
|
||||
* Default constructor. It creates a set of `size() == 0`. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
set_transient() = default;
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing at the first element of the
|
||||
* collection. It does not allocate memory and its complexity is
|
||||
* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD iterator begin() const { return {impl_}; }
|
||||
|
||||
/*!
|
||||
* Returns an iterator pointing just after the last element of the
|
||||
* collection. It does not allocate and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD iterator end() const
|
||||
{
|
||||
return {impl_, typename iterator::end_t{}};
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns the number of elements in the container. It does
|
||||
* not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD size_type size() const { return impl_.size; }
|
||||
|
||||
/*!
|
||||
* Returns `true` if there are no elements in the container. It
|
||||
* does not allocate memory and its complexity is @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
|
||||
|
||||
/*!
|
||||
* Returns `1` when `value` is contained in the set or `0`
|
||||
* otherwise. It won't allocate memory and its complexity is
|
||||
* *effectively* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template <typename K,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD size_type count(const K& value) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
detail::constantly<size_type, 0>>(value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns `1` when `value` is contained in the set or `0`
|
||||
* otherwise. It won't allocate memory and its complexity is
|
||||
* *effectively* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD size_type count(const T& value) const
|
||||
{
|
||||
return impl_.template get<detail::constantly<size_type, 1>,
|
||||
detail::constantly<size_type, 0>>(value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the value if `value` is contained in the
|
||||
* set, or nullptr otherwise.
|
||||
* It does not allocate memory and its complexity is *effectively*
|
||||
* @f$ O(1) @f$.
|
||||
*/
|
||||
IMMER_NODISCARD const T* find(const T& value) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value_ptr,
|
||||
detail::constantly<const T*, nullptr>>(value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the value if `value` is contained in the
|
||||
* set, or nullptr otherwise.
|
||||
* It does not allocate memory and its complexity is *effectively*
|
||||
* @f$ O(1) @f$.
|
||||
*
|
||||
* This overload participates in overload resolution only if
|
||||
* `Hash::is_transparent` is valid and denotes a type.
|
||||
*/
|
||||
template <typename K,
|
||||
typename U = Hash,
|
||||
typename = typename U::is_transparent>
|
||||
IMMER_NODISCARD const T* find(const K& value) const
|
||||
{
|
||||
return impl_.template get<typename persistent_type::project_value_ptr,
|
||||
detail::constantly<const T*, nullptr>>(value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inserts `value` into the set, and does nothing if the value is already
|
||||
* there It may allocate memory and its complexity is *effectively* @f$
|
||||
* O(1) @f$.
|
||||
*/
|
||||
void insert(T value) { impl_.add_mut(*this, std::move(value)); }
|
||||
|
||||
/*!
|
||||
* Removes the `value` from the set, doing nothing if the value is not in
|
||||
* the set. It may allocate memory and its complexity is *effectively* @f$
|
||||
* O(1) @f$.
|
||||
*/
|
||||
void erase(const T& value) { impl_.sub_mut(*this, value); }
|
||||
|
||||
/*!
|
||||
* Returns an @a immutable form of this container, an
|
||||
* `immer::set`.
|
||||
*/
|
||||
IMMER_NODISCARD persistent_type persistent() &
|
||||
{
|
||||
this->owner_t::operator=(owner_t{});
|
||||
return impl_;
|
||||
}
|
||||
IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); }
|
||||
|
||||
private:
|
||||
friend persistent_type;
|
||||
using impl_t = typename persistent_type::impl_t;
|
||||
|
||||
set_transient(impl_t impl)
|
||||
: impl_(std::move(impl))
|
||||
{}
|
||||
|
||||
impl_t impl_ = impl_t::empty();
|
||||
|
||||
public:
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
|
547
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
|
||||
*
|
||||
* This cotainer provides a good trade-off between cache locality,
|
||||
* This container provides a good trade-off between cache locality,
|
||||
* random access, update performance and structural sharing. It does
|
||||
* so by storing the data in contiguous chunks of :math:`2^{BL}`
|
||||
* elements. By default, when ``sizeof(T) == sizeof(void*)`` then
|
||||
@ -340,6 +340,17 @@ public:
|
||||
IMMER_NODISCARD transient_type transient() const& { return impl_; }
|
||||
IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
|
||||
|
||||
/*!
|
||||
* Returns a value that can be used as identity for the container. If two
|
||||
* values have the same identity, they are guaranteed to be equal and to
|
||||
* contain the same objects. However, two equal containers are not
|
||||
* guaranteed to have the same identity.
|
||||
*/
|
||||
std::pair<void*, void*> identity() const
|
||||
{
|
||||
return {impl_.root, impl_.tail};
|
||||
}
|
||||
|
||||
// Semi-private
|
||||
const impl_t& impl() const { return impl_; }
|
||||
|
||||
|
@ -49,6 +49,7 @@ tc.stdenv.mkDerivation rec {
|
||||
buildInputs = [
|
||||
tc.cc
|
||||
git
|
||||
catch2
|
||||
cmake
|
||||
pkgconfig
|
||||
ninja
|
||||
@ -58,6 +59,7 @@ tc.stdenv.mkDerivation rec {
|
||||
boost
|
||||
boehmgc
|
||||
fmt
|
||||
valgrind
|
||||
benchmarks.c_rrb
|
||||
benchmarks.steady
|
||||
benchmarks.chunkedseq
|
||||
|
4
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})
|
||||
target_compile_definitions(${_target} PUBLIC
|
||||
-DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data"
|
||||
DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
CATCH_CONFIG_MAIN)
|
||||
target_link_libraries(${_target} PUBLIC immer-dev)
|
||||
target_link_libraries(${_target} PUBLIC immer-dev Catch2::Catch2)
|
||||
add_test("test/${_output}" ${_output})
|
||||
endforeach()
|
||||
|
221
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"
|
||||
|
||||
IMMER_RANGES_CHECK(std::ranges::contiguous_range<immer::array<std::string>>);
|
||||
IMMER_RANGES_CHECK(
|
||||
std::ranges::contiguous_range<immer::array_transient<std::string>>);
|
||||
|
||||
TEST_CASE("array_transient default constructor compiles")
|
||||
{
|
||||
immer::array_transient<int> transient;
|
||||
|
@ -10,7 +10,7 @@
|
||||
#error "define the box template to use in ATOM_T"
|
||||
#endif
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
template <typename T>
|
||||
using BOX_T = typename ATOM_T<T>::box_type;
|
||||
|
@ -10,7 +10,7 @@
|
||||
#error "define the box template to use in BOX_T"
|
||||
#endif
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
TEST_CASE("construction and copy")
|
||||
{
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <immer/set.hpp>
|
||||
#include <immer/vector.hpp>
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
struct rec_vec
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <immer/vector.hpp>
|
||||
#include <immer/vector_transient.hpp>
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
TEST_CASE("issue-33")
|
||||
{
|
||||
|
@ -6,7 +6,7 @@
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <forward_list>
|
||||
#include <immer/detail/type_traits.hpp>
|
||||
#include <list>
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <doctest.h>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
using namespace immer;
|
||||
|
||||
@ -27,7 +27,7 @@ TEST_CASE("instantiation")
|
||||
|
||||
TEST_CASE("push back one element")
|
||||
{
|
||||
SUBCASE("one element")
|
||||
SECTION("one element")
|
||||
{
|
||||
const auto v1 = dvektor<int>{};
|
||||
auto v2 = v1.push_back(42);
|
||||
@ -36,7 +36,7 @@ TEST_CASE("push back one element")
|
||||
CHECK(v2[0] == 42);
|
||||
}
|
||||
|
||||
SUBCASE("many elements")
|
||||
SECTION("many elements")
|
||||
{
|
||||
const auto n = 666u;
|
||||
auto v = dvektor<unsigned>{};
|
||||
@ -56,7 +56,7 @@ TEST_CASE("update")
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.push_back(i);
|
||||
|
||||
SUBCASE("assoc")
|
||||
SECTION("assoc")
|
||||
{
|
||||
const auto u = v.assoc(3u, 13u);
|
||||
CHECK(u.size() == v.size());
|
||||
@ -67,7 +67,7 @@ TEST_CASE("update")
|
||||
CHECK(v[3u] == 3u);
|
||||
}
|
||||
|
||||
SUBCASE("assoc further")
|
||||
SECTION("assoc further")
|
||||
{
|
||||
for (auto i = n; i < 666; ++i)
|
||||
v = v.push_back(i);
|
||||
@ -88,7 +88,7 @@ TEST_CASE("update")
|
||||
CHECK(v[200u] == 200u);
|
||||
}
|
||||
|
||||
SUBCASE("assoc further more")
|
||||
SECTION("assoc further more")
|
||||
{
|
||||
auto v = immer::dvektor<unsigned, 4>{};
|
||||
|
||||
@ -101,7 +101,7 @@ TEST_CASE("update")
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("update")
|
||||
SECTION("update")
|
||||
{
|
||||
const auto u = v.update(10u, [](auto x) { return x + 10; });
|
||||
CHECK(u.size() == v.size());
|
||||
@ -123,13 +123,13 @@ TEST_CASE("big")
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.push_back(i);
|
||||
|
||||
SUBCASE("read")
|
||||
SECTION("read")
|
||||
{
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
CHECK(v[i] == i);
|
||||
}
|
||||
|
||||
SUBCASE("assoc")
|
||||
SECTION("assoc")
|
||||
{
|
||||
for (auto i = 0u; i < n; ++i) {
|
||||
v = v.assoc(i, i + 1);
|
||||
@ -146,7 +146,7 @@ TEST_CASE("iterator")
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.push_back(i);
|
||||
|
||||
SUBCASE("works with range loop")
|
||||
SECTION("works with range loop")
|
||||
{
|
||||
auto i = 0u;
|
||||
for (const auto& x : v)
|
||||
@ -154,16 +154,16 @@ TEST_CASE("iterator")
|
||||
CHECK(i == v.size());
|
||||
}
|
||||
|
||||
SUBCASE("works with standard algorithms")
|
||||
SECTION("works with standard algorithms")
|
||||
{
|
||||
auto s = std::vector<unsigned>(n);
|
||||
std::iota(s.begin(), s.end(), 0u);
|
||||
std::equal(v.begin(), v.end(), s.begin(), s.end());
|
||||
}
|
||||
|
||||
SUBCASE("can go back from end") { CHECK(n - 1 == *--v.end()); }
|
||||
SECTION("can go back from end") { CHECK(n - 1 == *--v.end()); }
|
||||
|
||||
SUBCASE("works with reversed range adaptor")
|
||||
SECTION("works with reversed range adaptor")
|
||||
{
|
||||
auto r = v | boost::adaptors::reversed;
|
||||
auto i = n;
|
||||
@ -171,7 +171,7 @@ TEST_CASE("iterator")
|
||||
CHECK(x == --i);
|
||||
}
|
||||
|
||||
SUBCASE("works with strided range adaptor")
|
||||
SECTION("works with strided range adaptor")
|
||||
{
|
||||
auto r = v | boost::adaptors::strided(5);
|
||||
auto i = 0u;
|
||||
@ -179,14 +179,14 @@ TEST_CASE("iterator")
|
||||
CHECK(x == 5 * i++);
|
||||
}
|
||||
|
||||
SUBCASE("works reversed")
|
||||
SECTION("works reversed")
|
||||
{
|
||||
auto i = n;
|
||||
for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter)
|
||||
CHECK(*iter == --i);
|
||||
}
|
||||
|
||||
SUBCASE("advance and distance")
|
||||
SECTION("advance and distance")
|
||||
{
|
||||
auto i1 = v.begin();
|
||||
auto i2 = i1 + 100;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||
#include <array>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <immer/flex_vector.hpp>
|
||||
#include <iostream>
|
||||
|
||||
@ -174,7 +174,7 @@ TEST_CASE("bug: concatenate too big vectors")
|
||||
var4 = var4.push_back(42);
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
// Assertion `!p->relaxed()' failed
|
||||
SECTION("")
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||
#include <array>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <immer/flex_vector.hpp>
|
||||
#include <iostream>
|
||||
|
||||
@ -159,7 +159,7 @@ TEST_CASE("bug: memory leak because of move update")
|
||||
var0 = std::move(var0).push_back(21);
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
@ -263,7 +263,8 @@ TEST_CASE("non-bug: crash")
|
||||
var4 = var4 + var4;
|
||||
var4 = var4.update(4, [](auto x) { return x + 1; });
|
||||
}
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||
#include <array>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <immer/flex_vector.hpp>
|
||||
#include <iostream>
|
||||
|
||||
@ -172,7 +172,7 @@ TEST_CASE("bug: use after free on move-take")
|
||||
var0 = std::move(var0).take(68);
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||
#include <array>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <immer/flex_vector.hpp>
|
||||
#include <iostream>
|
||||
|
||||
@ -205,7 +205,7 @@ TEST_CASE("bug: concat with moving the right side")
|
||||
var0 = var0 + std::move(var1);
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("vm")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "extra/fuzzer/fuzzer_input.hpp"
|
||||
#include <array>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <immer/flex_vector.hpp>
|
||||
#include <immer/flex_vector_transient.hpp>
|
||||
#include <immer/heap/gc_heap.hpp>
|
||||
@ -231,7 +231,7 @@ TEST_CASE("bug: concatenating transients")
|
||||
t1.append(t0);
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
@ -271,7 +271,7 @@ TEST_CASE("bug: concatenating moved transients")
|
||||
t2.append(std::move(t0));
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
@ -309,7 +309,7 @@ TEST_CASE("bug: concatenating moved transients")
|
||||
t0 = {};
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
return;
|
||||
@ -348,7 +348,7 @@ TEST_CASE("bug: aegsdas")
|
||||
t1 = {};
|
||||
}
|
||||
|
||||
#if __GNUC__ != 9 && __GNUC__ != 8
|
||||
#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG
|
||||
SECTION("")
|
||||
{
|
||||
constexpr std::uint8_t input[] = {
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/range/irange.hpp>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@ -29,6 +29,9 @@
|
||||
#error "define the vector template to use in VECTOR_T"
|
||||
#endif
|
||||
|
||||
IMMER_RANGES_CHECK(
|
||||
std::ranges::random_access_range<FLEX_VECTOR_T<std::string>>);
|
||||
|
||||
template <typename V = FLEX_VECTOR_T<unsigned>>
|
||||
auto make_test_flex_vector(unsigned min, unsigned max)
|
||||
{
|
||||
@ -106,6 +109,18 @@ TEST_CASE("push_front")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random_access iteration")
|
||||
{
|
||||
auto v = make_test_flex_vector(0, 10);
|
||||
auto iter = v.begin();
|
||||
CHECK(*iter == 0);
|
||||
CHECK(iter[0] == 0);
|
||||
CHECK(iter[3] == 3);
|
||||
CHECK(iter[9] == 9);
|
||||
iter += 4;
|
||||
CHECK(iter[-4] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("concat")
|
||||
{
|
||||
#if IMMER_SLOW_TESTS
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <immer/vector.hpp>
|
||||
#include <immer/vector_transient.hpp>
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#if IMMER_CXX_STANDARD >= 17
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/range/irange.hpp>
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@ -33,6 +33,11 @@
|
||||
#error "define the vector template to use in VECTOR_T"
|
||||
#endif
|
||||
|
||||
IMMER_RANGES_CHECK(
|
||||
std::ranges::random_access_range<FLEX_VECTOR_T<std::string>>);
|
||||
IMMER_RANGES_CHECK(
|
||||
std::ranges::random_access_range<FLEX_VECTOR_TRANSIENT_T<std::string>>);
|
||||
|
||||
template <typename V = VECTOR_T<unsigned>>
|
||||
auto make_test_flex_vector(unsigned min, unsigned max)
|
||||
{
|
||||
|
@ -6,6 +6,8 @@
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#define IMMER_DEBUG_STATS 1
|
||||
|
||||
#include <immer/map.hpp>
|
||||
|
||||
#define MAP_T ::immer::map
|
||||
|
@ -23,4 +23,5 @@ template <typename K,
|
||||
using test_map_t = immer::map<K, T, Hash, Eq, gc_memory, 3u>;
|
||||
|
||||
#define MAP_T test_map_t
|
||||
#define IMMER_IS_LIBGC_TEST 1
|
||||
#include "generic.ipp"
|
||||
|
@ -13,15 +13,21 @@
|
||||
#endif
|
||||
|
||||
#include <immer/algorithm.hpp>
|
||||
#include <immer/box.hpp>
|
||||
|
||||
#include "test/dada.hpp"
|
||||
#include "test/util.hpp"
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
IMMER_RANGES_CHECK(std::ranges::forward_range<MAP_T<std::string, std::string>>);
|
||||
|
||||
using memory_policy_t = MAP_T<unsigned, unsigned>::memory_policy_type;
|
||||
|
||||
template <typename T = unsigned>
|
||||
auto make_generator()
|
||||
{
|
||||
@ -66,7 +72,7 @@ auto make_test_map(unsigned n)
|
||||
{
|
||||
auto s = MAP_T<unsigned, unsigned>{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
s = s.insert({i, i});
|
||||
s = std::move(s).insert({i, i});
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -74,7 +80,7 @@ auto make_test_map(const std::vector<std::pair<conflictor, unsigned>>& vals)
|
||||
{
|
||||
auto s = MAP_T<conflictor, unsigned, hash_conflictor>{};
|
||||
for (auto&& v : vals)
|
||||
s = s.insert(v);
|
||||
s = std::move(s).insert(v);
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -84,6 +90,7 @@ TEST_CASE("instantiation")
|
||||
{
|
||||
auto v = MAP_T<int, int>{};
|
||||
CHECK(v.size() == 0u);
|
||||
CHECK(v.identity() == MAP_T<int, int>{}.identity());
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,6 +109,19 @@ TEST_CASE("basic insertion")
|
||||
CHECK(v3.count(42) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("initializer list and range constructors")
|
||||
{
|
||||
auto v0 = std::unordered_map<std::string, int>{
|
||||
{{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}};
|
||||
auto v1 = MAP_T<std::string, int>{
|
||||
{{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}};
|
||||
auto v2 = MAP_T<std::string, int>{v0.begin(), v0.end()};
|
||||
CHECK(v1.size() == 4);
|
||||
CHECK(v1.count(std::string{"foo"}) == 1);
|
||||
CHECK(v1.at(std::string{"bar"}) == 13);
|
||||
CHECK(v1 == v2);
|
||||
}
|
||||
|
||||
TEST_CASE("accessor")
|
||||
{
|
||||
const auto n = 666u;
|
||||
@ -151,8 +171,39 @@ TEST_CASE("equals and setting")
|
||||
CHECK(v.set(1234, 42) == v.insert({1234, 42}));
|
||||
CHECK(v.update(1234, [](auto&& x) { return x + 1; }) == v.set(1234, 1));
|
||||
CHECK(v.update(42, [](auto&& x) { return x + 1; }) == v.set(42, 43));
|
||||
|
||||
CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }) == v);
|
||||
CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }) ==
|
||||
v.set(42, 43));
|
||||
|
||||
CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }).identity() ==
|
||||
v.identity());
|
||||
CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }).identity() !=
|
||||
v.set(42, 43).identity());
|
||||
|
||||
#if IMMER_DEBUG_STATS
|
||||
std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats())
|
||||
.get_summary();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if IMMER_DEBUG_STATS
|
||||
TEST_CASE("debug stats")
|
||||
{
|
||||
{
|
||||
std::cout
|
||||
<< immer::map<int, int>{}.impl().get_debug_stats().get_summary();
|
||||
}
|
||||
{
|
||||
immer::map<int, int> map;
|
||||
for (int i = 0; i <= 10; i++) {
|
||||
map = std::move(map).set(i, i);
|
||||
}
|
||||
std::cout << map.impl().get_debug_stats().get_summary();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterator")
|
||||
{
|
||||
const auto N = 666u;
|
||||
@ -216,11 +267,157 @@ TEST_CASE("update a lot")
|
||||
{
|
||||
auto v = make_test_map(666u);
|
||||
|
||||
SECTION("immutable")
|
||||
{
|
||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
||||
v = v.update(i, [](auto&& x) { return x + 1; });
|
||||
CHECK(v[i] == i + 1);
|
||||
}
|
||||
}
|
||||
SECTION("move")
|
||||
{
|
||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
||||
v = std::move(v).update(i, [](auto&& x) { return x + 1; });
|
||||
CHECK(v[i] == i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("erase")
|
||||
{
|
||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
||||
v = std::move(v).erase(i);
|
||||
CHECK(v.count(i) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_if_exists a lot")
|
||||
{
|
||||
auto v = make_test_map(666u);
|
||||
|
||||
SECTION("immutable")
|
||||
{
|
||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
||||
v = v.update_if_exists(i, [](auto&& x) { return x + 1; });
|
||||
CHECK(v[i] == i + 1);
|
||||
}
|
||||
}
|
||||
SECTION("move")
|
||||
{
|
||||
for (decltype(v.size()) i = 0; i < v.size(); ++i) {
|
||||
v = std::move(v).update_if_exists(i,
|
||||
[](auto&& x) { return x + 1; });
|
||||
CHECK(v[i] == i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !IMMER_IS_LIBGC_TEST
|
||||
TEST_CASE("update boxed move string")
|
||||
{
|
||||
constexpr auto N = 666u;
|
||||
constexpr auto S = 7;
|
||||
auto s = MAP_T<std::string, immer::box<std::string, memory_policy_t>>{};
|
||||
SECTION("preserve immutability")
|
||||
{
|
||||
auto s0 = s;
|
||||
auto i0 = 0u;
|
||||
// insert
|
||||
for (auto i = 0u; i < N; ++i) {
|
||||
if (i % S == 0) {
|
||||
s0 = s;
|
||||
i0 = i;
|
||||
}
|
||||
s = std::move(s).update(std::to_string(i),
|
||||
[&](auto&&) { return std::to_string(i); });
|
||||
{
|
||||
CHECK(s.size() == i + 1);
|
||||
for (auto j : test_irange(0u, i + 1)) {
|
||||
CHECK(s.count(std::to_string(j)) == 1);
|
||||
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
for (auto j : test_irange(i + 1u, N))
|
||||
CHECK(s.count(std::to_string(j)) == 0);
|
||||
}
|
||||
{
|
||||
CHECK(s0.size() == i0);
|
||||
for (auto j : test_irange(0u, i0)) {
|
||||
CHECK(s0.count(std::to_string(j)) == 1);
|
||||
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
for (auto j : test_irange(i0, N))
|
||||
CHECK(s0.count(std::to_string(j)) == 0);
|
||||
}
|
||||
}
|
||||
// update
|
||||
for (auto i = 0u; i < N; ++i) {
|
||||
if (i % S == 0) {
|
||||
s0 = s;
|
||||
i0 = i;
|
||||
}
|
||||
s = std::move(s).update(std::to_string(i), [&](auto&&) {
|
||||
return std::to_string(i + 1);
|
||||
});
|
||||
{
|
||||
CHECK(s.size() == N);
|
||||
for (auto j : test_irange(0u, i + 1))
|
||||
CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1));
|
||||
for (auto j : test_irange(i + 1u, N))
|
||||
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
{
|
||||
CHECK(s0.size() == N);
|
||||
for (auto j : test_irange(0u, i0))
|
||||
CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1));
|
||||
for (auto j : test_irange(i0, N))
|
||||
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !IMMER_IS_LIBGC_TEST
|
||||
TEST_CASE("update_if_exists boxed move string")
|
||||
{
|
||||
constexpr auto N = 666u;
|
||||
constexpr auto S = 7;
|
||||
auto s = MAP_T<std::string, immer::box<std::string, memory_policy_t>>{};
|
||||
SECTION("preserve immutability")
|
||||
{
|
||||
auto s0 = s;
|
||||
auto i0 = 0u;
|
||||
// insert
|
||||
for (auto i = 0u; i < N; ++i) {
|
||||
s = std::move(s).set(std::to_string(i), std::to_string(i));
|
||||
}
|
||||
// update
|
||||
for (auto i = 0u; i < N; ++i) {
|
||||
if (i % S == 0) {
|
||||
s0 = s;
|
||||
i0 = i;
|
||||
}
|
||||
s = std::move(s).update_if_exists(std::to_string(i), [&](auto&&) {
|
||||
return std::to_string(i + 1);
|
||||
});
|
||||
{
|
||||
CHECK(s.size() == N);
|
||||
for (auto j : test_irange(0u, i + 1))
|
||||
CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1));
|
||||
for (auto j : test_irange(i + 1u, N))
|
||||
CHECK(*s.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
{
|
||||
CHECK(s0.size() == N);
|
||||
for (auto j : test_irange(0u, i0))
|
||||
CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1));
|
||||
for (auto j : test_irange(i0, N))
|
||||
CHECK(*s0.find(std::to_string(j)) == std::to_string(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("exception safety")
|
||||
{
|
||||
@ -231,12 +428,12 @@ TEST_CASE("exception safety")
|
||||
using dadaist_conflictor_map_t = typename dadaist_wrapper<
|
||||
MAP_T<conflictor, unsigned, hash_conflictor>>::type;
|
||||
|
||||
SECTION("update collisions")
|
||||
SECTION("update")
|
||||
{
|
||||
auto v = dadaist_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.set(i, i);
|
||||
v = std::move(v).set(i, i);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
@ -252,6 +449,27 @@ TEST_CASE("exception safety")
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("update_if_exists")
|
||||
{
|
||||
auto v = dadaist_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = std::move(v).set(i, i);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
v = v.update_if_exists(i, [](auto x) { return x + 1; });
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(i) == i + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(i) == i);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("update collisisions")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
@ -273,6 +491,143 @@ TEST_CASE("exception safety")
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("update_if_exists collisisions")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
v = v.update_if_exists(vals[i].first,
|
||||
[](auto x) { return x + 1; });
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("set collisisions")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
auto x = vals[i].second;
|
||||
v = v.set(vals[i].first, x + 1);
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("set collisisions move")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = v.insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
auto x = vals[i].second;
|
||||
v = std::move(v).set(vals[i].first, x + 1);
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("update collisisions move")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = std::move(v).insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
v = std::move(v).update(vals[i].first,
|
||||
[](auto x) { return x + 1; });
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("update_if_exists collisisions move")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = std::move(v).insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
auto s = d.next();
|
||||
v = std::move(v).update_if_exists(vals[i].first,
|
||||
[](auto x) { return x + 1; });
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second + 1);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings > 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
|
||||
SECTION("erase collisisions move")
|
||||
{
|
||||
auto vals = make_values_with_collisions(n);
|
||||
auto v = dadaist_conflictor_map_t{};
|
||||
auto d = dadaism{};
|
||||
for (auto i = 0u; i < n; ++i)
|
||||
v = std::move(v).insert(vals[i]);
|
||||
for (auto i = 0u; i < v.size();) {
|
||||
try {
|
||||
// auto s = d.next();
|
||||
v = std::move(v).erase(vals[i].first);
|
||||
++i;
|
||||
} catch (dada_error) {}
|
||||
for (auto i : test_irange(0u, i))
|
||||
CHECK(v.count(vals[i].first) == 0);
|
||||
for (auto i : test_irange(i, n))
|
||||
CHECK(v.at(vals[i].first) == vals[i].second);
|
||||
}
|
||||
CHECK(d.happenings == 0);
|
||||
IMMER_TRACE_E(d.happenings);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -363,3 +718,75 @@ TEST_CASE("issue 134")
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void test_diff(unsigned old_num,
|
||||
unsigned add_num,
|
||||
unsigned remove_num,
|
||||
unsigned change_num)
|
||||
{
|
||||
auto values = make_values_with_collisions(old_num + add_num);
|
||||
std::vector<std::pair<conflictor, unsigned>> initial_values(
|
||||
values.begin(), values.begin() + old_num);
|
||||
std::vector<std::pair<conflictor, unsigned>> new_values(
|
||||
values.begin() + old_num, values.end());
|
||||
auto map = make_test_map(initial_values);
|
||||
|
||||
std::vector<conflictor> old_keys;
|
||||
for (auto const& val : map)
|
||||
old_keys.push_back(val.first);
|
||||
|
||||
auto first_snapshot = map;
|
||||
CHECK(old_num == first_snapshot.size());
|
||||
|
||||
// remove
|
||||
auto shuffle = old_keys;
|
||||
std::random_shuffle(shuffle.begin(), shuffle.end());
|
||||
std::vector<conflictor> remove_keys(shuffle.begin(),
|
||||
shuffle.begin() + remove_num);
|
||||
std::vector<conflictor> rest_keys(shuffle.begin() + remove_num,
|
||||
shuffle.end());
|
||||
|
||||
using key_set = std::unordered_set<conflictor, hash_conflictor>;
|
||||
key_set removed_keys(remove_keys.begin(), remove_keys.end());
|
||||
for (auto const& key : remove_keys)
|
||||
map = map.erase(key);
|
||||
CHECK(old_num - remove_num == map.size());
|
||||
|
||||
// add
|
||||
key_set added_keys;
|
||||
for (auto const& data : new_values) {
|
||||
map = map.set(data.first, data.second);
|
||||
added_keys.insert(data.first);
|
||||
}
|
||||
|
||||
// change
|
||||
key_set changed_keys;
|
||||
for (auto i = 0u; i < change_num; i++) {
|
||||
auto key = rest_keys[i];
|
||||
map = map.update(key, [](auto val) { return ++val; });
|
||||
changed_keys.insert(key);
|
||||
}
|
||||
|
||||
diff(
|
||||
first_snapshot,
|
||||
map,
|
||||
[&](auto const& data) { REQUIRE(added_keys.erase(data.first) > 0); },
|
||||
[&](auto const& data) { REQUIRE(removed_keys.erase(data.first) > 0); },
|
||||
[&](auto const& old_data, auto const& new_data) {
|
||||
(void) old_data;
|
||||
REQUIRE(changed_keys.erase(new_data.first) > 0);
|
||||
});
|
||||
|
||||
CHECK(added_keys.empty());
|
||||
CHECK(changed_keys.empty());
|
||||
CHECK(removed_keys.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("diff")
|
||||
{
|
||||
test_diff(16, 10, 10, 3);
|
||||
test_diff(100, 10, 10, 10);
|
||||
test_diff(1500, 10, 1000, 100);
|
||||
test_diff(16, 1500, 10, 3);
|
||||
test_diff(100, 0, 0, 50);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <immer/map.hpp>
|
||||
#include <immer/vector.hpp>
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
TEST_CASE("const map")
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user