Showing preview only (819K chars total). Download the full file or copy to clipboard to get everything.
Repository: WG21-SG14/SG14
Branch: master
Commit: c92614381100
Files: 30
Total size: 794.4 KB
Directory structure:
gitextract_rzo9ruc3/
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── Docs/
│ ├── Proposals/
│ │ ├── D0447R16 - Introduction of hive to the Standard Library.html
│ │ ├── Fixed_Point_Library_Proposal.md
│ │ ├── p0037.html
│ │ ├── rawstorage.html
│ │ ├── ring_proposal_r5.tex
│ │ ├── uninitialized.html
│ │ └── unstable_remove.html
│ ├── fixed_point.md
│ └── plf_licensing.txt
├── README.md
├── SG14/
│ ├── algorithm_ext.h
│ ├── flat_map.h
│ ├── flat_set.h
│ ├── inplace_function.h
│ ├── plf_colony.h
│ ├── ring.h
│ └── slot_map.h
└── SG14_test/
├── SG14_test.h
├── flat_map_test.cpp
├── flat_set_test.cpp
├── inplace_function_test.cpp
├── main.cpp
├── plf_colony_test.cpp
├── ring_test.cpp
├── slot_map_test.cpp
├── uninitialized_test.cpp
└── unstable_remove_test.cpp
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*.tlog
VS2015/SG14/SG14_test/Release/vc140.pdb
*.obj
VS2015/SG14/SG14_test/Release/SG14_test.log
*.pdb
VS2015/SG14/SG14_test/Debug/vc120.idb
*.idb
*.ipdb
VS2015/SG14/Release/SG14_test.iobj
*.ilk
VS2015/SG14/Release/SG14_test.exe
*.exe
VS2015/SG14/.vs/SG14/v14/.suo
*.opensdf
VS2015/SG14/SG14.sdf
*.suo
VS2015/SG14/SG14.sdf
VS2015/SG14/SG14_test/Debug/SG14_test.log
# Xcode
xcuserdata
# CLion
cmake/.idea
/.vs
/.vs/SG14/v15
/.vs/slnx.sqlite
/.vs/SG14/v15/Browse.VC.opendb
/.vs/SG14/v15/Browse.VC.db
/.vs/ProjectSettings.json
/build/
================================================
FILE: .travis.yml
================================================
language: cpp
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update;
brew upgrade cmake;
brew install cmake;
fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
eval "${MATRIX_EVAL}";
DEPS_DIR="${TRAVIS_BUILD_DIR}/deps";
mkdir ${DEPS_DIR} && cd ${DEPS_DIR};
CMAKE_URL="https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.tar.gz";
mkdir cmake && travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake;
export PATH=${DEPS_DIR}/cmake/bin:${PATH};
cd ..;
fi
matrix:
include:
- os: windows
env:
- CONFIG=Release
- os: windows
env:
- CONFIG=Debug
- os: osx
osx_image: xcode9.2
env:
- CONFIG=Release
- os: osx
osx_image: xcode9.2
env:
- CONFIG=Debug
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-5
- g++-5
env:
- MATRIX_EVAL="CC=gcc-5 && CXX=g++-5 && CONFIG=Debug"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-5
- g++-5
env:
- MATRIX_EVAL="CC=gcc-5 && CXX=g++-5 && CONFIG=Release"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-6
- g++-6
env:
- MATRIX_EVAL="CC=gcc-6 && CXX=g++-6 && CONFIG=Debug"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-6
- g++-6
env:
- MATRIX_EVAL="CC=gcc-6 && CXX=g++-6 && CONFIG=Release"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-7
- g++-7
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7 && CONFIG=Debug"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-7
- g++-7
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7 && CONFIG=Release"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-8
- g++-8
env:
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8 && CONFIG=Debug"
- os: linux
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-8
- g++-8
env:
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8 && CONFIG=Release"
script:
- mkdir build && cd build && cmake .. && cmake --build . && ./bin/sg14_tests
notifications:
email:
on_success: never
on_failure: always
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.10)
project(sg14 CXX)
find_package(Threads REQUIRED)
# Prefer C++17, downgrade if it isn't available.
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_STANDARD 17)
set(SG14_INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/SG14")
set(SG14_TEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/SG14_test")
# Output binary to predictable location.
set(BINARY_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_OUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${BINARY_OUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${BINARY_OUT_DIR})
foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${BINARY_OUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${BINARY_OUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${BINARY_OUT_DIR})
endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES)
link_directories(${CMAKE_CURRENT_BINARY_DIR}/lib) # For future use with testing library.
##
# Project
##
add_library(${PROJECT_NAME} INTERFACE)
include(GNUInstallDirs)
target_include_directories(${PROJECT_NAME} INTERFACE
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${SG14_INCLUDE_DIRECTORY}>
)
##
# Unit Tests
##
set(TEST_SOURCE_FILES
${SG14_TEST_SOURCE_DIRECTORY}/main.cpp
${SG14_TEST_SOURCE_DIRECTORY}/flat_map_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/flat_set_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/inplace_function_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/plf_colony_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/ring_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/slot_map_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/uninitialized_test.cpp
${SG14_TEST_SOURCE_DIRECTORY}/unstable_remove_test.cpp
)
set(TEST_NAME ${PROJECT_NAME}_tests)
add_executable(${TEST_NAME} ${TEST_SOURCE_FILES})
target_link_libraries(${TEST_NAME} ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(${TEST_NAME} PRIVATE "${SG14_TEST_SOURCE_DIRECTORY}")
# Compile options
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
target_compile_options(${TEST_NAME} PRIVATE -Wall -Wextra -Werror)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
target_compile_options(${TEST_NAME} PRIVATE -Wall -Wextra -Werror)
set_source_files_properties(${SG14_TEST_SOURCE_DIRECTORY}/plf_colony_test.cpp PROPERTIES
COMPILE_FLAGS "-Wno-unused-parameter"
)
if (CMAKE_CXX_COMPILER_VERSION MATCHES "^7.*")
set_source_files_properties(${SG14_TEST_SOURCE_DIRECTORY}/slot_map_test.cpp PROPERTIES
COMPILE_FLAGS "-Wno-error=unused-variable -Wno-error=unused-but-set-variable") # Fix gcc7 issues with structured bindings
message("Disabled -Wunused-variable and -Wunused-but-set-variable for gcc ${CMAKE_CXX_COMPILER_VERSION}.")
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${TEST_NAME})
target_compile_options(${TEST_NAME} PRIVATE /Zc:__cplusplus /permissive- /W4 /WX)
add_definitions(-DNOMINMAX -D_SCL_SECURE_NO_WARNINGS)
set_source_files_properties(${SG14_TEST_SOURCE_DIRECTORY}/plf_colony_test.cpp PROPERTIES
COMPILE_FLAGS "/wd4127") # Disable conditional expression is constant, use if constexpr
endif()
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}_targets)
install(EXPORT ${PROJECT_NAME}_targets
NAMESPACE ${PROJECT_NAME}::
FILE ${PROJECT_NAME}-config.cmake
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}"
)
install(DIRECTORY "${SG14_INCLUDE_DIRECTORY}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
================================================
FILE: Docs/Proposals/D0447R16 - Introduction of hive to the Standard Library.html
================================================
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="viewport" content="width=device-width">
<meta content="True" name="HandheldFriendly">
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
<title>Introduction of std::hive to the standard library</title>
<style type="text/css">
pre {
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
body {
font-size: 12pt;
font-weight: normal;
font-style: normal;
font-family: serif;
color: black;
background-color: white;
line-height: 1.2em;
margin-left: 4em;
margin-right: 2em;
}
/* paragraphs */
p {
padding: 0;
line-height: 1.3em;
margin-top: 1.2em;
margin-bottom: 1em;
text-align: left;
}
table {
margin-top: 3.8em;
margin-bottom: 2em;
text-align: left;
table-layout:fixed;
width:100%;
}
td {
overflow:auto;
word-wrap:break-word;
}
/* headings */
h1 {
font-size: 195%;
font-weight: bold;
font-style: normal;
font-variant: small-caps;
line-height: 1.6em;
text-align: left;
padding: 0;
margin-top: 3.5em;
margin-bottom: 1.7em;
}
h2 {
font-size: 122%;
font-weight: bold;
font-style: normal;
text-decoration: underline;
padding: 0;
margin-top: 4.5em;
margin-bottom: 1.1em;
}
h3 {
font-size: 110%;
font-weight: bold;
font-style: normal;
text-decoration: underline;
padding: 0;
margin-top: 4em;
margin-bottom: 1.1em;
}
h4 {
font-size: 100%;
font-weight: bold;
font-style: normal;
padding: 0;
margin-top: 4em;
margin-bottom: 1.1em;
}
h5 {
font-size: 90%;
font-weight: bold;
font-style: italic;
padding: 0;
margin-top: 3em;
margin-bottom: 1em;
}
h6 {
font-size: 80%;
font-weight: bold;
font-style: normal;
padding: 0;
margin-top: 1em;
margin-bottom: 1em;
}
/* divisions */
div {
padding: 0;
margin-top: 0em;
margin-bottom: 0em;
}
ul {
margin: 12pt 0pt 22pt 18pt;
padding: 0pt 0pt 0pt 0pt;
list-style-type: square;
font-size: 98%;
}
ol {
margin: 12pt 0pt 22pt 17pt;
padding: 0pt 0pt 0pt 0pt;
}
li {
margin: 0pt 0pt 10.5pt 0pt;
padding: 0pt 0pt 0pt 0pt;
text-indent: 0pt;
display: list-item;
}
/* inline */
strong {
font-weight: bold;
}
sup,
sub {
vertical-align: baseline;
position: relative;
top: -0.4em;
font-size: 70%;
}
sub {
top: 0.4em;
}
em {
font-style: italic;
}
code {
font-family: Courier New, Courier, monospace;
font-size: 90%;
padding: 0;
word-wrap:break-word;
}
ins {
background-color: yellow;
text-decoration: underline;
}
del {
text-decoration: line-through;
}
a:hover {
color: #4398E1;
}
a:active {
color: #4598E1;
text-decoration: none;
}
a:link.review {
color: #AAAAAF;
}
a:hover.review {
color: #4398E1;
}
a:visited.review {
color: #444444;
}
a:active.review {
color: #AAAAAF;
text-decoration: none;
}
</style>
</head>
<body>
Audience: LEWG, SG14, WG21<br>
Document number: D0447R16<br>
Date: 2021-06-21<br>
Project: Introduction of std::hive to the standard library<br>
Reply-to: Matthew Bentley <mattreecebentley@gmail.com><br>
<h1>Introduction of std::hive to the standard library</h1>
<h2>Table of Contents</h2>
<ol type="I">
<li><a href="#introduction">Introduction</a></li>
<li><a href="#questions">Questions for the committee</a></li>
<li><a href="#motivation">Motivation and Scope</a></li>
<li><a href="#impact">Impact On the Standard</a></li>
<li><a href="#design">Design Decisions</a></li>
<li><a href="#technical">Technical Specification</a></li>
<li><a href="#acknowledgements">Acknowledgements</a></li>
<li>Appendixes:
<ol type="A">
<li><a href="#basicusage">Basic usage examples</a></li>
<li><a href="#benchmarks">Reference implementation benchmarks</a></li>
<li><a href="#faq">Frequently Asked Questions</a></li>
<li><a href="#responses">Specific responses to previous committee feedback</a></li>
<li><a href="#sg14gameengine">Typical game engine requirements</a></li>
<li><a href="#timecomplexityexplanations">Time complexity requirement explanations</a></li>
<li><a href="#yunoconstexpr">Why not constexpr?</a></li>
<li><a href="#referencediff">Reference implementation differences and link</a></li>
</ol>
</li>
</ol>
<h2><a id="revisions"></a>Revision history</h2>
<ul>
<li>R16: Range-constructor corrected to allow sentinels.</li>
<li>R15: Added throw details to splice. Further design decisions information on reshape and splice. Assign() overload for sentinels (differing iterator types) added. Minor text snaffu corrections. Colony changed to hive based on D2332R0.</li>
<li>R14: get_iterator_from_pointer changed to get_iterator - the pointer part is implied by the fact that it's the only argument. Added const_iterator overload for get_iterator - which takes a const_pointer and is a const function. Some wording corrections, additional design decisions information. HTML corrections.</li>
<li>R13: Revisions based on committee feedback. Skipfield template parameter changed to priority enum in order to not over-specify container implementation. Other wording changes to reduce over-specifying implementation. Some non-member template functions moved to be friend functions. std::limits changed to std::hive_limits. block_limits() changed to block_capacity_limits().</li>
<li>R12: Fill, range and initializer_list inserts changed to void return, since the insertions are not guaranteed to be sequential in terms of hive order and therefore returning an iterator to the first insertion is not useful. Non-default-value fill constructor changed to non-explicit to match other std:: containers. Correction to reserve() wording. Other minor corrections and clarity improvements.</li>
<li>R11: Overhaul of technical specification to be more 'wording-like'. Minor
alterations & clarifications. Additional alternative approach added to
Design Decisions under skipfield information. Overall rewording. Reordering
based on feedback. Removal of some easily-replicated 'helper' functions.
Change to noexcept guarantees. Assign added. get_block_capacity_limits and
set_block_capacity_limits functions renamed to block_limits and reshape.
Addition of block-limits default constructors. Reserve() and
shrink_to_fit() reintroduced. Trim(), erase and erase_if overloads added.</li>
<li>R10: Additional information about time complexity requirements added to
appendix, some minor corrections to time complexity info. The 'bentley
pattern' (this was always a temporary name) is renamed to the more astute
'low-complexity jump-counting pattern'. Likewise the 'advanced
jump-counting skipfield' is renamed to the 'high-complexity jump-counting
pattern' - for reasoning behind this go <a href="https://plflib.org/blog.htm#whatsinaname">here</a>. Both refer to
time complexity of operations, as opposed to algorithmic complexity. Some
other corrections.</li>
<li>R9: Link to Bentley pattern paper added, and is spellchecked now.</li>
<li>R8: Correction to SIMD info. Correction to structure (missing appendices
title, member functions and technical specification were conjoined,
acknowledgments section had mysteriously gone missing since an earlier
version, now restored and updated). Update intro. HTML corrections.</li>
<li>R7: Minor changes to member functions.</li>
<li>R6: Re-write. Reserve() and shrink_to_fit() removed from
specification.</li>
<li>R5: Additional note for reserve, re-write of introduction.</li>
<li>R4: Addition of revision history and review feedback appendices. General
rewording. Cutting of some dead wood. Addition of some more dead wood.
Reversion to HTML, benchmarks moved to external URL, based on feedback.
Change of font to Times New Roman based on looking at what other papers
were using, though I did briefly consider Comic Sans. Change to insert
specifications.</li>
<li>R3: Jonathan Wakely's extensive technical critique has been actioned on,
in both documentation and the reference implementation. "Be clearer about
what operations this supports, early in the paper." - done (V. Technical
Specifications). "Be clear about the O() time of each operation, early in
the paper." - done for main operations, see V. Technical Specifications.
Responses to some other feedbacks included in the foreword.</li>
<li>R2: Rewording.</li>
</ul>
<h2><a id="introduction"></a>I. Introduction</h2>
<p>The purpose of a container in the standard library cannot be to provide the
optimal solution for all scenarios. Inevitably in fields such as
high-performance trading or gaming, the optimal solution within critical loops
will be a custom-made one that fits that scenario perfectly. However, outside
of the most critical of hot paths, there is a wide range of application for
more generalized solutions.</p>
<p>hive is a formalisation, extension and optimization of what is typically
known as a 'bucket array' container in game programming circles; similar
structures exist in various incarnations across the high-performance computing,
high performance trading, 3D simulation, physics simulation, robotics, server/client
application and particle simulation fields (see: <a href="https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1iWHyVnsLBQ">https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1iWHyVnsLBQ</a>).</p>
<p>The concept of a bucket array is: you have multiple memory blocks of
elements, and a boolean token for each element which denotes whether or not
that element is 'active' or 'erased', commonly known as a skipfield. If it is
'erased', it is skipped over during iteration. When all elements in a block are
erased, the block is removed, so that iteration does not lose performance by
having to skip empty blocks. If an insertion occurs when all the blocks are
full, a new memory block is allocated.</p>
<p>The advantages of this structure are as follows: because a skipfield is
used, no reallocation of elements is necessary upon erasure. Because the
structure uses multiple memory blocks, insertions to a full container also do
not trigger reallocations. This means that element memory locations stay stable
and iterators stay valid regardless of erasure/insertion. This is highly
desirable, for example, <a href="#sg14gameengine">in game programming</a>
because there are usually multiple elements in different containers which need
to reference each other during gameplay and elements are being inserted or
erased in real time.</p>
<p>Problematic aspects of a typical bucket array are that they tend to have a
fixed memory block size, do not re-use memory locations from erased elements,
and utilize a boolean skipfield. The fixed block size (as opposed to block
sizes with a growth factor) and lack of erased-element re-use leads to far more
allocations/deallocations than is necessary. Given that allocation is a costly
operation in most operating systems, this becomes important in
performance-critical environments. The boolean skipfield makes iteration time
complexity undefined, as there is no way of knowing ahead of time how many
erased elements occur between any two non-erased elements. This can create
variable latency during iteration. It also requires branching code, which may
cause issues on processors with deep pipelines and poor branch-prediction
failure performance.</p>
<p>A hive uses a non-boolean method for skipping erased elements, which allows for O(1) amortized iteration time complexity
and more-predictable iteration performance than a bucket array. It also
utilizes a growth factor for memory blocks and reuses erased element locations
upon insertion, which leads to fewer allocations/reallocations. Because it
reuses erased element memory space, the exact location of insertion is
undefined, unless no erasures have occurred or an equal number of erasures and
insertions have occurred (in which case the insertion location is the back of
the container). The container is therefore considered unordered but sortable.
Lastly, because there is no way of predicting in advance where erasures
('skips') may occur during iteration, an O(1) time complexity [ ] operator is
not necessarily possible (depending on implementation) and therefore, the container is bidirectional but not random-access.</p>
<p>There are two patterns for accessing stored elements in a hive: the first
is to iterate over the container and process each element (or skip some
elements using the advance/prev/next/iterator ++/-- functions). The second is
to store the iterator returned by the insert() function (or a pointer derived
from the iterator) in some other structure and access the inserted element in
that way. To better understand how insertion and erasure work in a hive, see
the following images.</p>
<h3>Insertion to back</h3>
<p>The following images demonstrate how insertion works in a hive compared to
a vector when size == capacity.</p>
<img src="https://plflib.org/vector_addition.gif" alt="Visual demonstration of inserting to a full vector"
style="max-width: 100%; height: auto;">
<img src="https://plflib.org/hive_addition.gif"
alt="Visual demonstration of inserting to a full hive"
style="max-width: 100%; height: auto;">
<h3>Non-back erasure</h3>
<p>The following images demonstrate how non-back erasure works in a hive
compared to a vector.</p>
<img src="https://plflib.org/vector_erasure.gif"
alt="Visual demonstration of randomly erasing from a vector"
style="max-width: 100%; height: auto;">
<img src="https://plflib.org/hive_erasure.gif"
alt="Visual demonstration of randomly erasing from a hive"
style="max-width: 100%; height: auto;">
<h2><a id="questions"></a>II. Questions for the Committee</h2>
<ol>
<li>It is possible to make the memory() function constant time at a cost (see details in it's entry in the design decisions section of the paper) but since this is expected to be a seldom-used function I've decided not to do so and leave the time complexity as implementation-defined. If there are any objections, please state them. Also, memory_usage() has been suggested as a better name?</li>
<li>The conditions under which memory blocks are retained by the erase() functions and added to the "reserved" pile instead of deallocated, is presently implementation-defined. Are there any objections to this? Should we define this? See the notes on erase() in Design Decisions, and the item in the FAQ. One option is to specify that only the current back block may be retained, however I feel like this should be implementation-defined.</li>
<li>Given that this is a largely unordered container, should resize() be included? Currently it is not and I see no particular reason to do so, but if there are valid reasons let me know.</li>
<li>Bikeshedding is welcome for reshape(). Was previously set_block_capacity_limits(), which is more clear, but does not describe the action when container already contains blocks which are outside of the newly-specified capacity limits. change_block_capacity_limits()?</li>
</ol>
<h2><a id="motivation"></a>III. Motivation and Scope</h2>
<p><i>Note: Throughout this document I will use the term 'link' to denote any
form of referencing between elements whether it be via
ids/iterators/pointers/indexes/references/etc.</i></p>
<p>There are situations where data is heavily interlinked, iterated over
frequently, and changing often. An example is the typical video game engine.
Most games will have a central generic 'entity' or 'actor' class, regardless of
their overall schema (an entity class does not imply an <a
href="https://en.wikipedia.org/wiki/Entity-component-system">ECS</a>).
Entity/actor objects tend to be 'has a'-style objects rather than 'is a'-style
objects, which link to, rather than contain, shared resources like sprites,
sounds and so on. Those shared resources are usually located in separate
containers/arrays so that they can re-used by multiple entities. Entities are
in turn referenced by other structures within a game engine, such as
quadtrees/octrees, level structures, and so on.</p>
<p>Entities may be erased at any time (for example, a wall gets destroyed and
no longer is required to be processed by the game's engine, so is erased) and
new entities inserted (for example, a new enemy is spawned). While this is all
happening the links between entities, resources and superstructures such as
levels and quadtrees, must stay valid in order for the game to run. The order
of the entities and resources themselves within the containers is, in the
context of a game, typically unimportant, so an unordered container is okay.</p>
<p>Unfortunately the container with the best iteration performance in the
standard library, vector<sup><a href="#benchmarks">[1]</a></sup>, loses pointer
validity to elements within it upon insertion, and pointer/index validity upon
erasure. This tends to lead to sophisticated and often restrictive workarounds
when developers attempt to utilize vector or similar containers under the above
circumstances.</p>
<p>std::list and the like are not suitable due to their poor locality, which
leads to poor cache performance during iteration. This is however an ideal
situation for a container such as hive, which has a high degree of locality.
Even though that locality can be punctuated by gaps from erased elements, it
still works out better in terms of iteration performance<sup><a
href="#benchmarks">[1]</a></sup> than every existing standard library container
other than deque/vector, regardless of the ratio of erased to non-erased
elements.</p>
<p>Some more specific requirements for containers in the context of game
development are listed in the <a href="#sg14gameengine">appendix</a>.</p>
<p>As another example, particle simulation (weather, physics etcetera) often
involves large clusters of particles which interact with external objects and
each other. The particles each have individual properties (spin, momentum,
direction etc) and are being created and destroyed continuously. Therefore the
order of the particles is unimportant, what is important is the speed of
erasure and insertion. No current standard library container has both strong
insertion and non-back erasure speed, so again this is a good match for
hive.</p>
<p><a href="https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1iWHyVnsLBQ">Reports
from other fields</a> suggest that, because most developers aren't aware of
containers such as this, they often end up using solutions which are sub-par
for iterative performance such as std::map and std::list in order to preserve pointer
validity, when most of their processing work is actually iteration-based. So,
introducing this container would both create a convenient solution to these
situations, as well as increasing awareness of better-performing approaches in
general. It will also ease communication across fields, as opposed to the
current scenario where each field uses a similar container but each has a
different name for it.</p>
<h2><a id="impact"></a>IV. Impact On the Standard</h2>
<p>This is purely a library addition, requiring no changes to the language.</p>
<h2><a id="design"></a>V. Design Decisions</h2>
<p>The three core aspects of a hive from an abstract perspective are: </p>
<ol>
<li>A collection of element memory blocks + metadata, to prevent reallocation
during insertion (as opposed to a single memory block)</li>
<li>A method of skipping erased elements in O(1) time during iteration (as opposed to reallocating subsequent elements during erasure)</li>
<li>An erased-element location recording mechanism, to enable the re-use of
memory from erased elements in subsequent insertions, which in turn
increases cache locality and reduces the number of block
allocations/deallocations</li>
</ol>
<p>Each memory block houses multiple elements. The metadata about each block
may or may not be allocated with the blocks themselves (could be contained in a
separate structure). This metadata should include at a minimum, the number of
non-erased elements within each block and the block's capacity - which allows the
container to know when the block is empty and needs to be removed from the
iterative chain, and also allows iterators to judge when the end of one block
has been reached. A non-boolean method of skipping over
erased elements during iteration while maintaining O(1) amortized iteration
time complexity is required (amortized due to block traversal, which would typically require a few more
operations). Finally, a mechanism for keeping track of elements which have been
erased must be present, so that those memory locations can be reused upon
subsequent element insertions.</p>
<p>The following aspects of a hive must be implementation-defined in order to
allow for variance and possible performance improvement, and to conform with
possible changes to C++ in the future:</p>
<ul>
<li>the method used to skip erased elements</li>
<li>time complexity of operations to update whatever metadata is associated with the skip method</li>
<li>erasure-recording mechanism</li>
<li>element memory block metadata</li>
<li>iterator structure</li>
<li>memory block growth factor</li>
<li>time complexity of advance()/next()/prev()</li>
</ul>
<p>However the implementation of these <em>is</em> significantly constrained by
the requirements of the container (lack of reallocation, stable pointers to
non-erased elements regardless of erasures/insertions).</p>
<p>In terms of the <a href="https://plflib.org/colony.htm">reference
implementation</a> the specific structure and mechanisms have changed many
times over the course of development, however the interface to the container
and its time complexity guarantees have remained largely unchanged (with the
exception of the time complexity for updating skipfield nodes - which has not
impacted significantly on performance). So it is reasonably likely that
regardless of specific implementation, it will be possible to maintain this
general specification without obviating future improvements in implementation,
so long as time complexity guarantees for the above list are
implementation-defined.</p>
<p>Below I explain the reference implementation's approach in terms of the
three core aspects described above, along with descriptions of some
alternatives implementation approaches.</p>
<h4>1. Collection of element memory blocks + metadata</h4>
<p>In the reference implementation this is essentially a doubly-linked list of
'group' structs containing (a) a dynamically-allocated element memory block, (b) memory block metadata and (c)
a dynamically-allocated skipfield. The memory blocks and skipfields have a growth factor of 2 from one
group to the next. The metadata includes information necessary for an iterator
to iterate over hive elements, such as the last insertion point within the
memory block, and other information useful to specific functions, such as the
total number of non-erased elements in the node. This approach keeps the
operation of freeing empty memory blocks from the hive container at O(1) time
complexity. Further information is available <a
href="https://plflib.org/chained_group_allocation_pattern.htm">here</a>.</p>
<p>Using a vector of group structs with dynamically-allocated element memory blocks, using the swap-and-pop idiom where groups need to be erased from the iterative sequence, would not work. To explain, when a group becomes empty of elements, it must be removed from the sequence of groups, because otherwise you end up with highly-variable latency during iteration due to the need to skip over an unknown number of empty groups when traversing from one non-empty group to the next. Simply erasing the group will not suffice, as this would create a variable amount of latency during erasure when the group becomes empty, based on the number of groups after that group which would need to be reallocated backward in the vector. But even if one swapped the to-be-erased group with the back group, and then pop'd the to-be-erased group off the back, this would not solve the problem, as iterators require a stable pointer to the group they are traversing in order to traverse to the next group in the sequence. If an iterator pointed to an element in the back group, and the back group was swapped with the to-be-erased group, this would invalidate the iterator.</p>
<p>A vector of pointers to group structs is more-possible. Erasing groups would still have highly-variable latency due to reallocation, however the cost of reallocating pointers may be negligible depending on architecture. While the number of pointers can be expected to be low in most cases due to the growth factor in memory blocks, if the user has defined their own memory block capacity limits the number of pointers could be large, and this has to be taken into consideration. In this case using a pop-and-swap idiom is still not possible, because while it would not necessarily invalidate the internal references of an iterator pointing to an element within the back group, the sequence of blocks would be changed and therefore the iterator would be moved backwards in the iterative sequence.</p>
<p>A vector of memory blocks, as opposed to a vector of pointers to memory
blocks or a vector of group structs with dynamically-allocated memory blocks, would also not work, both due to the above points and because as it would (a) disallow a growth factor in the memory
blocks and (b) invalidate pointers to elements in subsequent blocks when a
memory block became empty of elements and was therefore removed from the
vector. In short, negating hive's beneficial aspects.</p>
<h4>2. A non-boolean method of skipping erased elements in O(1) time during iteration</h4>
<p>The reference implementation currently uses a skipfield pattern called the
<a href="https://plflib.org/matt_bentley_-_the_low_complexity_jump-counting_pattern.pdf">Low complexity jump-counting pattern</a>. This effectively encodes the length of runs of consecutive erased elements, into a skipfield, which allows for O(1) time
complexity during iteration. Since there is no branching involved in iterating
over the skipfield aside from end-of-block checks, it can be less problematic
computationally than a boolean skipfield (which has to branch for every
skipfield read) in terms of CPUs which don't handle branching or
branch-prediction failure efficiently (eg. Core2). It also does not have the variable latency associated with a boolean skipfield.</p>
<p>The pattern stores and modifies the run-lengths during insertion and erasure
with O(1) time complexity. It has a lot of similarities to the <a
href="https://plflib.org/matt_bentley_-_the_high_complexity_jump-counting_pattern.pdf">High
complexity jump-counting pattern</a>, which was a pattern previously used by
the reference implementation. Using the High complexity jump-counting pattern
is an alternative, though the skipfield update time complexity guarantees for
that pattern are effectively undefined, or between O(1) and O(skipfield length)
for each insertion/erasure. In practice those updates result in one
memcpy operation which resolves to a single block-copy operation, but it is
still a little slower than the Low complexity jump-counting pattern. The
method you use to skip erased elements will typically also have an effect on the type of
memory-reuse mechanism you can utilize.</p>
<p>A pure boolean skipfield is not usable because it makes iteration time
complexity undefined - it could for example result in thousands of branching
statements + skipfield reads for a single ++ operation in the case of many
consecutive erased elements. In the high-performance fields for which this
container was initially designed, this brings with it unacceptable latency.
However another strategy using a combination of a jump-counting <i>and</i>
boolean skipfield, which saves memory at the expense of computational
efficiency, is possible as follows:</p>
<ol>
<li>Instead of storing the data for the low complexity jump-counting pattern
in it's own skipfield, have a boolean bitfield indicating which elements
are erased. Store the jump-counting data in the erased element's memory
space instead (possibly alongside free list data).</li>
<li>When iterating, check whether the element is erased or not using the
boolean bitfield; if it is not erased, do nothing. If it is erased, read
the jump value from the erased element's memory space and skip forward the
appropriate number of nodes both in the element memory block and the
boolean bitfield.</li>
</ol>
<p>This approach has the advantage of still performing O(1) iterations from one
non-erased element to the next, unlike a pure boolean skipfield approach, but
compared to a pure jump-counting approach introduces 3 additional costs per
iteration via (1) a branch operation when checking the bitfield, (2) an
additional read (of the erased element's memory space) and (3) a bitmasking
operation + bitshift to read the bit. But it does reduce the memory overhead of
the skipfield to 1 bit per-element, which reduces the cache load. An implementation and benchmarking would be required in order to establish how this approach compares to the current implementation's performance.</p>
<p>Another method worth mentioning is the use of a referencing array - for example, having a vector of elements, together with a vector of either indexes or pointers to those elements. When an element is erased, the vector of elements itself is not updated - no elements are reallocated. Meanwhile the referencing vector is updated and the index or pointer to the erased element is erased. When iteration occurs it iterates over the referencing vector, accessing each element in the element vector via the indexes/pointers. The disadvantages of this technique are (a) much higher memory usage, particularly for small elements and (b) highly-variable latency during erasure due to reallocation in the referencing array. Since once of the goals of hive is predictable latency, this is likely not suitable.</p>
<p><a href="http://bitsquid.blogspot.ca/2011/09/managing-decoupling-part-4-id-lookup.html">Packed arrays</a> are not worth mentioning as the iteration method is considered separate from the referencing mechanism, making them unsuitable for a std:: container.</p>
<h4>3. Erased-element location recording mechanism</h4>
<p>There are two valid approaches here; both involve per-memory-block <a
href="https://en.wikipedia.org/wiki/Free_list">free lists</a>, utilizing the
memory space of erased elements. The first approach forms a free list of all
erased elements. The second forms a free list of the first element in each
<i>run</i> of consecutive erased elements ("skipblocks", in terms of the
terminology used in the jump-counting pattern papers). The second can be more
efficient, but requires a doubly-linked free list rather than a singly-linked
free list, at least with a jump-counting skipfield - otherwise it would become an O(N) operation to update links in the
skipfield, when a skipblock expands or contracts during erasure or
insertion.</p>
<p>The reference implementation currently uses the second approach, using three
things to keep track of erased element locations:</p>
<ol type="a">
<li>Metadata for each memory block includes a 'next block with erasures'
pointer. The container itself contains a 'blocks with erasures' list-head
pointer. These are used by the container to create an intrusive
singly-linked list of memory blocks with erased elements which can be
re-used for future insertions.</li>
<li>Metadata for each memory block also includes a 'free list head' index
number, which records the index (within the memory block) of the first
element of the last-created skipblock - the 'head' skipblock.</li>
<li>The memory space of the first erased element in each skipblock is
reinterpret_cast'd via pointers as two index numbers, the first giving the
index of the previous skipblock in that memory block, the second giving the
index of the next skipblock in the sequence. In the case of the 'head'
skipblock in the sequence, a unique number is used for the 'next' index.
This forms a free list of runs of erased element memory locations which may
be re-used.</li>
</ol>
<p>Using indexes for next and previous links, instead of pointers, reduces the necessary bit-depth of the next and previous links, thereby reducing the necessary over-alignment of the container's element type. If a global (ie. all memory blocks) free list were used, pointers would be necessary, as hive is bidirectional and does not support the [ ] operator. This would potentially increase the necessary over-alignment of the element type to 128 bits for a doubly-linked free list. A global free list would also decrease cache locality when traversing the free list by jumping between memory blocks.</p>
<p>Previous versions of the reference implementation used a singly-linked free
list of erased elements instead of a doubly-linked free list of skipblocks.
This was possible with the High complexity jump-counting pattern, but not
possible using the Low complexity jump-counting pattern as it cannot calculate
a skipblock's start node location from a middle node's value like the High
complexity pattern can. But using free-lists of skipblocks is a more efficient
approach as it requires fewer free list nodes. In addition, re-using only the start or end nodes of a skipblock is faster because it never splits a single skipblock in two (which would require adding a new skipblock to the free list).</p>
<p>One cannot use a stack of pointers (or similar) to erased elements for this
mechanism, as early versions of the reference implementation did, because this
can create allocations during erasure, which changes the exception guarantees
of erase(). One could instead scan all skipfields until an erased location was
found, or simply have the first item in the list above and then scan the first
available block, though both of these approaches would be slow.</p>
<p>In terms of the alternative <i>boolean + jump-counting skipfield</i>
approach described in the erased-element-skip-method section above, one could store both the
jump-counting data and free list data in any given erased element's memory
space, provided of course that elements are aligned to be wide enough to fit
both.</p>
<h3>Implementation of iterator class</h3>
<p>Any iterator implementation is going to be dependent on the erased-element-skipping mechanism used. The reference implementation's iterator stores a pointer to the current 'group' struct mentioned above, plus a pointer to the current element and a
pointer to its corresponding skipfield node. An alternative approach is to
store the group pointer + an index, since the index can indicate both the
offset from the memory block for the element, as well as the offset from the
start of the skipfield for the skipfield node. However multiple implementations
and benchmarks across many processors have shown this to be worse-performing
than the separate pointer-based approach, despite the increased memory cost for
the iterator class itself.</p>
<p>++ operation is as follows, utilising the reference implementation's
Low-complexity jump-counting pattern:</p>
<ol>
<li>Add 1 to the existing element and skipfield pointers.</li>
<li>Dereference skipfield pointer to get value of skipfield node, add value
of skipfield node to both the skipfield pointer and the element pointer. If
the node is erased, its value will be a positive integer indicating the
number of nodes until the next non-erased node, if not erased it will be
zero.</li>
<li>If element pointer is now beyond end of element memory block, change
group pointer to next group, element pointer to the start of the next
group's element memory block, skipfield pointer to the start of the next
group's skipfield. In case there is a skipblock at the beginning of this
memory block, dereference skipfield pointer to get value of skipfield node
and add value of skipfield node to both the skipfield pointer and the
element pointer. There is no need to repeat the check for end of block, as
the block would have been removed from the iteration sequence if it were
empty of elements.</li>
</ol>
<p>-- operation is the same except both step 1 and 2 involve subtraction rather
than adding, and step 3 checks to see if the element pointer is now before the
beginning of the memory block. If so it traverses to the back of the previous
group, and subtracts the value of the back skipfield node from the element
pointer and skipfield pointer.</p>
<p>Iterators are bidirectional but also provide constant time
complexity >, <, >=, <= and <=> operators for convenience
(eg. in <code>for</code> loops when skipping over multiple elements per loop
and there is a possibility of going past a pre-determined end element). This is
achieved by keeping a record of the order of memory blocks. In the reference
implementation this is done by assigning a number to each memory block in its
metadata. In an implementation using a vector of pointers to memory blocks
instead of a linked list, one could use the position of the pointers within the
vector to determine this. Comparing relative order of the two iterators' memory
blocks via this number, then comparing the memory locations of the elements
themselves, if they happen to be in the same memory block, is enough to
implement all greater/lesser comparisons.</p>
<h3>Additional notes on specific functions</h3>
<ul>
<li><code style="font-weight:bold">iterator insert</code> (all variants)<br>
<p>Insertion re-uses previously-erased element memory locations when
available, so position of insertion is effectively random unless no
previous erasures have occurred, in which case all elements will be
inserted linearly to the back of the container, at least in the current
implementation. These details have been removed from the standard in order
to allow leeway for potentially-better implementations in future - though
it is expected that a hive will always reuse erased memory locations, it
is impossible to predict optimal strategies for unknown future hardware.</p>
</li>
<li><code style="font-weight:bold">void insert</code> (all variants)<br>
<p>For range, fill and initializer_list insertion, it is not possible to guarantee that all the elements inserted will be sequential in the hive's iterative sequence, and therefore it is not considered useful to return an iterator to the first inserted element. There is a precedent for this in the various std:: map containers. Therefore these functions return void presently.</p>
<p>For range insert and range constructors, thhe syntax has been modified compared to other containers in order to take two potentially-different iterator types in order to support sentinels and the like.</p>
</li>
<li><code style="font-weight:bold">iterator erase</code> (all variants)<br>
<p>Firstly it should be noted that erase may retain memory blocks which become completely empty of elements due to erasures, adding them to the set of unused memory blocks which are normally created by reserve(). Under what circumstances these memory blocks are retained rather than deallocated is implementation-defined - however given that small memory blocks have low cache locality compared to larger ones, from a performance perspective it is best to only retain the larger of the blocks currently allocated in the hive. In most cases this would mean the back block would almost always be retained. There is a lot of nuance to this, and it's also a matter of trading off complexity of implementation vs actual benchmarked speed vs latency. In my tests retaining both back blocks and 2nd-to-back blocks while ignoring actual capacity of blocks seems to have the best overall performance characteristics.</p>
<p>There are three major performance advantages to retaining back blocks as opposed to any block - the first is that these will be, under most circumstances, the largest blocks in the hive (given the built-in growth factor) - the only exception to this is when splice is used, which may result in a smaller block following a larger block (implementation-dependent). Larger blocks == more cache locality during iteration, large numbers of erased elements notwithstanding. The second advantage is that in situations where elements are being inserted to and erased from the back of the hive (this assumes no erased element locations in other memory blocks, which would otherwise be used for insertions) continuously and in quick succession, retaining the back block avoids large numbers of deallocations/reallocations. The third advantage is that deallocations of larger blocks can, in part, be moved to non-critical code regions via trim(). Though ultimately if the user wants total control of when allocations and deallocations occur they would want to use a custom allocator.</p>
<p>Lastly, specifying a return iterator for range-erase may seem pointless, as no reallocation of elements occurs in erase so the return iterator will almost always be the <code>last</code> iterator of the <code>const_iterator first, const_iterator last</code> pair. However if <code>last</code> was <code>end()</code>, the new value of <code>end()</code> (if it has changed due to empty block removal) will be returned. In this case either the user submitted <code>end()</code> as <code>last</code>, or they incremented an iterator pointing to the final element in the hive and submitted that as <code>last</code>. The latter is the only valid reason to return an iterator from the function, as it may occur as part of a loop which is erasing elements and ends when <code>end()</code> is reached. If <code>end()</code> is changed by the erasure of an entire memory block, but the iterator being used in the loop does not accurately reflect <code>end()</code>'s new value, that iterator could iterate past <code>end()</code> and the loop would never finish.</li>
<li><code style="font-weight:bold">void reshape(std::hive_limits block_capacity_limits);</code><br>
<p>This function updates the block capacity limits in the hive and, if necessary, changes any blocks which fall outside of those limits to be within the limits. For this reason it may trigger an exception with non-copyable/movable types, and also invalidate pointers/iterators/etc to elements.</p>
<p>The order of elements post-reshape is not guaranteed to be stable in
order to allow for optimizations. Specifically in the instance where a
given element memory block no longer fits within the limits supplied by
the user, depending on the state of the hive as a whole, the elements
within that memory block could be reallocated to previously-erased
element locations in other memory blocks which do fit within the
supplied limits. Or they could be reallocated to the back of the final memory block.</p>
<p>Additionally if there is empty capacity at the back of the last
block in the container, at least some of the elements could be moved to
that position rather than being reallocated to a new memory block. Both
of these techniques increase cache locality by removing skipped memory
spaces within existing memory blocks. However whether they are used is
implementation-dependent.</p>
</li>
<li><code style="font-weight:bold">iterator get_iterator(pointer p) noexcept;<br>
const_iterator get_iterator(const_pointer p) const noexcept;</code><br>
<p>Because hive iterators are likely to be large, storing three
pieces of data - current memory block, current element within memory
block and potentially, current skipfield node - a program storing many
links to elements within a hive may opt to dereference iterators to
get pointers and store those instead of iterators, to save memory. This
function reverses the process, giving an iterator which can then be
used for operations such as erase. get_const_iterator was fielded as a workaround for the possibility of someone wanting to supply a non-const pointer
and get a const_iterator back, however <code>as_const</code> fulfills this same role when supplied to get_iterator and doesn't require expanding the interface of hive.</p>
</li>
<li><code style="font-weight:bold">void shrink_to_fit();</code>
<p>A decision had to be made as to whether this function should, in the
context of hive, be allowed to reallocate elements (as std::vector
does) or simply trim off unused memory blocks (as std::deque does). Due
to the fact that a large hive memory block could have as few as one
remaining element after a series of erasures, it makes little sense to
only trim unused blocks, and instead a shrink_to_fit is expected to
reallocate all non-erased elements to as few memory blocks as possible
in order to increase cache locality during iteration and reduce memory
use. As with reshape(), the order of elements post-reshape is not
guaranteed to be stable, to allow for potential optimizations. The
trim() command is also introduced as a way to free unused memory blocks
which have been previously reserved, without reallocating elements and
invalidating iterators.</p>
</li>
<li><code style="font-weight:bold">void sort();</code>
<p>It is forseen that although the container has unordered insertion, there may be circumstances where sorting is desired. Because hive uses bidirectional iterators, using std::sort or similar is not possible. Therefore an internal sort routine is warranted, as it is with std::list. An implementation of the sort routine used in the reference implementation of hive can be found in a non-container-specific form at <a href="https://plflib.org/indiesort.htm">plflib.org/indiesort.htm</a> - see that page for the technique's advantages over the usual sort algorithms for non-random-access containers. Unfortunately to date there has been no interest in including this algorithm in the standard library. An allowance is made for sort to allocate memory if necessary, so that algorithms such as indiesort can be used internally.</p>
</li>
<li><code style="font-weight:bold">void splice(hive &x);</code>
<p>Whether <code>x</code>'s blocks are transferred to the beginning or
end of <code>*this</code>'s iterative sequence, or interlaced in some way (for example, to preserve relative capacity growth-factor ordering of subsequent blocks) is implementation-defined. Better
performance may be gained in some cases by allowing the source blocks
to go to the front rather than the back, depending on how full the
final block in <code>x</code>'s iterative sequence is. This is because
unused elements that are not at the back of hive's iterative sequence
will need to be marked as skipped, and skipping over large numbers of
elements will incur a small performance disadvantage during iteration
compared to skipping over a small number of elements, due to memory
locality.</p>
<p>This function is not noexcept for three reasons - the first is that a length_error exception may be thrown if any of the capacities of the source <code>x</code>'s blocks are outside of the range defined by the destination's (<code>*this</code>) minimum and maximum block capacity limits. Second is that an exception may be thrown if the allocators of the two hives are different. Third is that in the case of an implementation using a linked list of group structs (ala the reference implementation) transferring blocks involves no allocation, however in the case of an implementation using a vector of pointers to blocks, an additional allocation may have to be made if the group pointer vector isn't of sufficient capacity to accomodate pointers to the spliced blocks from the source.</p>
</li>
<li><code style="font-weight:bold">size_type memory() const noexcept;</code>
<p>A hive uses memory block metadata and may use a skipfield, both which are
implementation-defined, so it is not possible for a user to estimate
internal memory usage from size(), sizeof() or capacity(). This function fulfills
that role. Because some types of elements may allocate their own memory
dynamically (eg. std::hive<std::vector>) only the static
allocation of each element is included in this functions byte count.</p>
<p>This function can be made constant time by adding a counter to the hive that keeps track of the number of reserved memory blocks available, or by having a vector of pointers to memory blocks instead an intrusive linked list of memory blocks. However in the case of the reference implementation which uses linked lists, the counter metadata would only be used by this function and since this function is not expected to be in heavy use, the time complexity of this function is left as implementation-defined to allow flexibility.</p>
</li>
<li>Non-member function overloads for <code style="font-weight:bold">advance, prev and next</code> (all variants)<br>
<p>For these functions, complexity is dependent on state of hive, position of iterator and
amount of distance, but in many cases will be less than linear, and may
be constant. To explain: it is necessary in a hive to store metadata
both about the capacity of each block (for the purpose of iteration)
and how many non-erased elements are present within the block (for the
purpose of removing blocks from the iterative chain once they become
empty). For this reason, intermediary blocks between the iterator's
initial block and its final destination block (if these are not the
same block, and if the initial block and final block are not
immediately adjacent) can be skipped rather than iterated linearly
across, by using the "number of non-erased elements" metadata.</p>
<p>This means that the only linear time operations are any iterations
within the initial block and the final block. However if either the
initial or final block have no erased elements (as determined by
comparing whether the block's capacity metadata and the block's "number
of non-erased elements" metadata are equal), linear iteration can be
skipped for that block and pointer/index math used instead to determine
distances, reducing complexity to constant time. Hence the best case
for this operation is constant time, the worst is linear to the
distance.</p>
</li>
<li>Non-member function overloads for <code style="font-weight:bold">distance</code> (all variants)<br>
<p>The same considerations which apply to advance, prev and next also
apply to distance - intermediary blocks between first and last's blocks
can be skipped in constant time and their "number of non-erased
elements" metadata added to the cumulative distance count, while
first's block and last's block (if they are not the same block) must be
linearly iterated across unless either block has no erased elements, in
which case the operation becomes pointer/index math and is reduced to
constant time for that block. In addition, if first's block is not the
same as last's block, and last is equal to end() or --end(), or is the
last element in that block, last's block's elements can also counted
from the "number of non-erased elements" metadata rather than via
iteration.</p>
</li>
<li>Priority template parameter for the container<br>
<p>This forms a non-binding request for the container to prioritize either performance or memory use, supplied in the form of a scoped enum. The reference implementation uses a regular un-scoped enum, as it must also work under C++03. In terms of the reference implementation the priority parameter changes the skipfield type from unsigned short (performance) to unsigned char (memory use) - which in turn changes the maximum block limits, because in the reference implementation the block capacities are limited to numeric_limits<skipfield_type>::max. The maximum block capacity limit affects iteration performance, due to a greater or lesser number of elements being able to be sequential in memory, and the subsequent effects on cache. For small numbers of elements ie. under 1000, unsigned char also will be faster in addition to needing less memory, due to the lowered cache usage and the fact that the maximum block capacity limit is not significantly limiting cache locality at this point. Hence, prioritizing for performance may not necessarily be faster in all circumstances, but should be faster in most - if in fact the request is actioned upon, and it is not guaranteed to be actioned upon in all implementations.</p>
<p>There is a point of diminishing returns in terms of how many elements can be stored sequentially in memory and how that impacts performance, due to the limits of cache size - hence it was found that increasing the skipfield type to unsigned int and thence increasing the block capacity limit, did not have a performance advantage on any number of elements.</p>
</li>
</ul>
<h3>Results of implementation</h3>
<p>In practical application the reference implementation is generally faster
for insertion and (non-back) erasure than current standard library
containers, and generally faster for iteration than any container except
vector and deque. For full details, see <a href="#benchmarks">benchmarks</a>.</p>
<h2><a id="technical"></a>VI. Technical Specification</h2>
<p>Suggested location of hive in the standard is 22.3, Sequence
Containers.</p>
<h3>22.3.7 Header <code><hive></code> synopsis [hive.syn]</h3>
<div style="background: #ffffff; overflow:auto; width:auto; border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;">
<pre style="margin: 0; line-height: 125%">
#include <initializer_list> // see 17.10.2
#include <compare> // see 17.11.1
#include <concepts> // see 18.3
#include <stdexcept> // see 19.2
#include <utility> // see 20.2.1
#include <memory> // see 20.10
namespace std {
// 22.3.14, class template hive
struct hive_limits;
enum class hive_priority;
template <class T, class Allocator = allocator<T>, hive_priority priority = hive_priority::performance> class hive;
namespace pmr {
template <class T>
using hive = std::hive<T, polymorphic_allocator<T>>;
}
}</pre>
</div>
<h4><a id="iteratorinvalidation"></a>Iterator Invalidation</h4>
<table border="1">
<tbody>
<tr>
<td>All read-only operations, swap, std::swap, splice, operator=
&& (source), reserve, trim</td>
<td>Never.</td>
</tr>
<tr>
<td>clear, operator= & (destination), operator= &&
(destination)</td>
<td>Always.</td>
</tr>
<tr>
<td>reshape</td>
<td>Only if memory blocks exist whose capacities do not fit within the
supplied limits.</td>
</tr>
<tr>
<td>shrink_to_fit</td>
<td>Only if capacity() != size().</td>
</tr>
<tr>
<td>erase</td>
<td>Only for the erased element. If an iterator is == end() it may be
invalidated if the back element of the hive is erased (similar to
deque (22.3.9)).
Likewise if a reverse_iterator is == rend() it may be invalidated if the front element of the hive is erased.
The same applies with cend() and crend() for const_iterator and const_reverse_iterator respectively.
</td>
</tr>
<tr>
<td>insert, emplace</td>
<td>If an iterator is == end() or == begin() it may be invalidated by a subsequent insert/emplace.
Likewise if a reverse_iterator is == rend() or == rbegin() it may be
invalidated by a subsequent insert/emplace.
The same rules apply with cend(), cbegin() and crend(), crbegin() for const_iterator and const_reverse_iterator respectively.
</td>
</tr>
</tbody>
</table>
<h3>22.3.14 Class template <code>hive</code> [hive]</h3>
<h4>22.3.14.1 Class template <code>hive</code> overview [hive.overview]</h4>
<ol>
<li>A hive is a sequence container that allows constant-time insert and
erase operations. Insertion location is the back of the container when no
erasures have occured. When erasures have occured it will re-use existing
erased element memory spaces where possible and insert to those locations.
Storage management is handled automatically and is specifically organized
in multiple blocks of sequential elements. Unlike vectors (22.3.12) and
deques (22.3.9), fast random access to hive elements is not supported,
but specializations of advance/next/prev give access which can be
better than linear time in the number of elements traversed.</li>
<li>Erasures are processed using implementation-defined strategies for skipping erased elements during iteration, rather than reallocating subsequent elements as is expected in a vector or deque.</li>
<li>Memory block element capacities have an implementation-defined growth
factor, for example a new block's capacity could be equal to the summed capacities of the existing
blocks.</li>
<li>Limits can be placed on the minimum and maximum element capacities of
memory blocks, both by a user and by an implementation. Minimum capacity
shall be no more than maximum capacity. When limits are not specified by a
user, the implementation's default limits are used. Where user-specified limits do
not fit within the implementation's limits (ie. user minimum is less than
implementation minimum or user maximum is more than implementation maximum)
an exception is thrown. User-specified limits can be supplied to a
constructor or to the reshape() function, using the
<code>std::hive_limits</code> struct with its <code>min</code> and
<code>max</code> members set to the minimum and maximum element capacity
limits respectively. The current limits in a hive instance can be
obtained from block_capacity_limits().</li>
<li>A hive satisfies all of the requirements of a container, of a
reversible container (given in two tables in 22.2), of a sequence
container, including most of the optional sequence container requirements
(22.2.3), and of an allocator-aware container (Table 78). The exceptions
are the <code>operator[]</code> and <code>at</code> member functions, which
are not provided.</li>
<li>hive iterators satisfy bidirectional requirements but also provide
relational operators <, <=, >, >= and <=> which compare
the relative ordering of two iterators in the sequence of a hive
instance.</li>
<li>Iterator operations ++ and -- take constant amortized time, other iterator operations take constant time.</li>
</ol>
<code>template <class T, class Allocator = std::allocator<T>, priority Priority = priority::performance> class hive</code>
<p><code><b>T</b></code> - the element type. In general T shall meet the
requirements of <a
href="https://en.cppreference.com/w/cpp/named_req/Erasable">Erasable</a>, <a
href="https://en.cppreference.com/w/cpp/named_req/CopyAssignable">CopyAssignable</a>
and <a
href="https://en.cppreference.com/w/cpp/named_req/CopyConstructible">CopyConstructible</a>.<br>
However, if emplace is utilized to insert elements into the hive, and no
functions which involve copying or moving are utilized, T is only required to
meet the requirements of <a href="https://en.cppreference.com/w/cpp/named_req/Erasable">Erasable</a>.<br>
If move-insert is utilized instead of emplace, T shall also meet the
requirements of <a
href="https://en.cppreference.com/w/cpp/named_req/MoveConstructible">MoveConstructible</a>.<br>
<br>
<code><b>Allocator</b></code> - an allocator that is used to acquire memory to
store the elements. The type shall meet the requirements of <a
href="https://en.cppreference.com/w/cpp/named_req/Allocator">Allocator</a>. The
behavior is undefined if <code>Allocator::value_type</code> is not the same as
T.<br>
<br>
<code><b>Priority</b></code> - if set to <code>priority::memory_use</code> this is a non-binding request to prioritize lowered memory usage over container performance. [ Note: The request is non-binding to allow latitude for implementation-specific optimizations. If this feature is implemented, it is <i>not</i> specified that the container shall have better performance when using priority::performance instead of priority::memory_usage in <i>all</i> scenarios, but that it shall have better performance in <i>most</i> scenarios. - end note ]</p>
<div style="background: #ffffff; overflow:auto; width:auto; border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;">
<pre style="margin: 0; line-height: 125%">namespace std {
struct hive_limits
{
size_t min, max;
hive_limits(size_t minimum, size_t maximum) noexcept : min(minimum), max(maximum) {}
};
enum struct hive_priority { performance, memory_use };
template <class T, class Allocator = allocator<T>, hive_priority Priority = hive_priority::performance>
class hive {
public:
// types
using value_type = T;
using allocator_type = Allocator;
using pointer = typename allocator_traits<Allocator>::pointer;
using const_pointer = typename allocator_traits<Allocator>::const_pointer;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = implementation-defined; // see 22.2
using difference_type = implementation-defined; // see 22.2
using iterator = implementation-defined; // see 22.2
using const_iterator = implementation-defined; // see 22.2
using reverse_iterator = implementation-defined; // see 22.2
using const_reverse_iterator = implementation-defined; // see 22.2
hive() noexcept(noexcept(Allocator())) : hive(Allocator()) { }
explicit hive(std::hive_limits block_capacity_limits) noexcept(noexcept(Allocator())) : hive(Allocator()) { }
explicit hive(const Allocator&) noexcept;
explicit hive(std::hive_limits block_capacity_limits, const Allocator&) noexcept;
explicit hive(size_type n, std::hive_limits block_capacity_limits = implementation-defined, const Allocator& = Allocator());
hive(size_type n, const T& value, std::hive_limits block_capacity_limits = implementation-defined, const Allocator& = Allocator());
template<class InputIterator1, class InputIterator2>
hive(InputIterator1 first, InputIterator2 last, std::hive_limits block_capacity_limits = implementation-defined, const Allocator& = Allocator());
hive(const hive& x);
hive(hive&&) noexcept;
hive(const hive&, const Allocator&);
hive(hive&&, const Allocator&);
hive(initializer_list<T>, std::hive_limits block_capacity_limits = implementation-defined, const Allocator& = Allocator());
~hive() noexcept;
hive& operator= (const hive& x);
hive& operator= (hive&& x) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value);
hive& operator= (initializer_list<T>);
template<class InputIterator1, class InputIterator2> void assign(InputIterator1 first, InputIterator2 last);
void assign(size_type n, const T& t);
void assign(initializer_list<T>);
allocator_type get_allocator() const noexcept;
// iterators
iterator begin() noexcept;
const_iterator begin() const noexcept;
iterator end() noexcept;
const_iterator end() const noexcept;
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
const_reverse_iterator crbegin() const noexcept;
const_reverse_iterator crend() const noexcept;
// capacity
[[nodiscard]] bool empty() const noexcept;
size_type size() const noexcept;
size_type max_size() const noexcept;
size_type capacity() const noexcept;
size_type memory() const noexcept;
void reserve(size_type n);
void shrink_to_fit();
void trim() noexcept;
// modifiers
template <class... Args> iterator emplace(Args&&... args);
iterator insert(const T& x);
iterator insert(T&& x);
void insert(size_type n, const T& x);
template <class InputIterator1, class InputIterator2> void insert(InputIterator1 first, InputIterator2 last);
void insert(initializer_list<T> il);
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
void swap(hive&) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value);
void clear() noexcept;
// hive operations
void splice(hive &x);
std::hive_limits block_capacity_limits() const noexcept;
void reshape(std::hive_limits block_capacity_limits);
iterator get_iterator(pointer p) noexcept;
const_iterator get_iterator(const_pointer p) const noexcept;
void sort();
template <class Compare> void sort(Compare comp);
friend bool operator== (const hive &x, const hive &y);
friend bool operator!= (const hive &x, const hive &y);
class iterator
{
friend void advance(iterator &it, Distance n);
friend iterator next(iterator it, difference_type distance = 1);
friend iterator prev(iterator it, difference_type distance = 1);
friend difference_type distance(iterator first, iterator last);
}
class const_iterator
{
friend void advance(const_iterator &it, Distance n);
friend const_iterator next(const_iterator it, difference_type distance = 1);
friend const_iterator prev(const_iterator it, difference_type distance = 1);
friend difference_type distance(const_iterator first, const_iterator last);
}
class reverse_iterator
{
friend void advance(reverse_iterator &it, Distance n);
friend reverse_iterator next(reverse_iterator it, difference_type distance = 1);
friend reverse_iterator prev(reverse_iterator it, difference_type distance = 1);
friend difference_type distance(reverse_iterator first, reverse_iterator last);
}
class const_reverse_iterator
{
friend void advance(const_reverse_iterator &it, Distance n);
friend const_reverse_iterator next(const_reverse_iterator it, difference_type distance = 1);
friend const_reverse_iterator prev(const_reverse_iterator it, difference_type distance = 1);
friend difference_type distance(const_reverse_iterator first, const_reverse_iterator last);
}
// swap
friend void swap(hive& x, hive& y)
noexcept(noexcept(x.swap(y)));
// erase
template <class Predicate>
friend size_type erase_if(hive& c, Predicate pred);
template <class U>
friend size_type erase(hive& c, const U& value);
}
template<class InputIterator, class Allocator = allocator<iter-value-type <InputIterator>>>
hive(InputIterator, InputIterator, Allocator = Allocator())
-> hive<iter-value-type <InputIterator>, Allocator>;
</pre>
</div>
<h4>22.3.14.2 hive constructors, copy, and assignment [hive.cons]</h4>
<code style="font-weight:bold">explicit hive(const Allocator&);</code>
<ol>
<li>Effects: Constructs an empty hive, using the specified allocator.</li>
<li>Complexity: Constant.</li>
</ol>
<br>
<code style="font-weight:bold">explicit hive(size_type n, const T& value, std::hive_limits block_capacities = implementation-defined, const Allocator& =Allocator());</code>
<ol start="3">
<li>Preconditions: <code>T</code> shall be <i>Cpp17MoveInsertable</i> into
<code>*this</code>.</li>
<li>Effects: Constructs a hive with n copies of <code>value</code>, using
the specified allocator.</li>
<li>Complexity: Linear in n.</li>
<li>Throws: <code>length_error</code> if <code>block_capacities.min</code> or
<code>block_capacities.max</code> are outside the implementation's minimum
and maximum element memory block capacity limits, or if
<code>block_capacities.min > block_capacities.max</code>.
<li>Remarks: If <code>n</code> is larger than
<code>block_capacities.min</code>, the capacity of the first block created
will be the smaller of <code>n</code> or <code>block_capacities.max</code>.</li>
</ol>
<br>
<pre><code style="font-weight:bold">template <class InputIterator1, class InputIterator2>
hive(InputIterator1 first, InputIterator2 last, std::hive_limits block_capacities = implementation-defined, const Allocator& = Allocator());</code></pre>
<ol start="8">
<li>Preconditions: <code>InputIterator1</code> shall be <code>std::equality_comparable_with InputIterator2</code>.</li>
<li>Effects: Constructs a hive equal to the range [first, last), using the
specified allocator.</li>
<li>Complexity: Linear in distance(first, last).</li>
<li>Throws: <code>length_error</code> if <code>block_capacities.min</code> or
<code>block_capacities.max</code> are outside the implementation's minimum
and maximum element memory block capacity limits, or if
<code>block_capacities.min > block_capacities.max</code>. Or
<li>Remarks: If iterators are random-access, let <code>n</code> be last -
first; if <code>n</code> is larger than <code>block_capacities.min</code>,
the capacity of the first block created will be the smaller of
<code>n</code> or <code>block_capacities.max</code>.</li>
</ol>
<h4>22.3.14.3 hive capacity [hive.capacity]</h4>
<code style="font-weight:bold">size_type capacity() const noexcept;</code>
<ol>
<li>Returns: The total number of elements that the hive can currently
contain without needing to allocate more memory blocks.</li>
</ol>
<br>
<code style="font-weight:bold">size_type memory() const noexcept;</code>
<ol start="2">
<li>Returns: The memory use, in bytes, of the container as a whole,
including elements but not including any dynamic allocation incurred by
those elements.</li>
</ol>
<br>
<code style="font-weight:bold">void reserve(size_type n);</code>
<ol start="3">
<li>Effects: A directive that informs a hive of a planned change in size,
so that it can manage the storage allocation accordingly. Since minimum and
maximum memory block sizes can be specified by users, after
<code>reserve()</code>, <code>capacity()</code> is not guaranteed to be
equal to the argument of <code>reserve()</code>, may be greater. Does not
cause reallocation of elements.</li>
<li>Complexity: It does not change the size of the sequence and creates at
most <code>(n / block_capacity_limits().max) + 1</code> allocations.</li>
<li>Throws: <code>length_error</code> if <code>n > max_size()</code><sup><a href="#r223">223</a></sup>.</li>
</ol>
<p style="font-size: 90%"><a id="r223"></a>223) reserve() uses Allocator::allocate() which may throw an appropriate exception.</p>
<br>
<code style="font-weight:bold">void shrink_to_fit();</code>
<ol start="6">
<li>Preconditions: <code>T</code> is <i>Cpp17MoveInsertable</i> into
<code>*this</code>.</li>
<li>Effects: shrink_to_fit is a non-binding request to reduce
<code>capacity()</code> to be closer to <code>size()</code>. [ Note: The
request is non-binding to allow latitude for implementation-specific
optimizations. - end note ] It does not increase <code>capacity()</code>,
but may reduce <code>capacity()</code> by causing reallocation. It may move
elements from multiple memory blocks and consolidate them into a smaller
number of memory blocks.<br>
If an exception is thrown other than by the move constructor of a
non-<em>Cpp17CopyInsertable</em> T, there are no effects.</li>
<li>Complexity: If reallocation happens, linear to the number of elements
reallocated.</li>
<li>Remarks: Reallocation invalidates all the references, pointers, and
iterators referring to the elements reallocated as well as the past-the-end
iterator. [Note: If no reallocation happens, they remain valid. —end
note] The order of elements post-operation is not guaranteed to be stable.
</li>
</ol>
<br>
<code style="font-weight:bold">void trim();</code>
<ol start="10">
<li>Effects: Removes and deallocates empty memory blocks created by prior
calls to <code>reserve()</code> or <code>erase()</code>. If such memory
blocks are present, <code>capacity()</code> will be reduced.</li>
<li>Complexity: Linear in the number of reserved blocks to deallocate.</li>
<li>Remarks: Does not reallocate elements and no references, pointers or
iterators referring to elements in the sequence will be invalidated.</li>
</ol>
<br>
<h4>22.3.14.4 hive modifiers [hive.modifiers]</h4>
<pre><code style="font-weight:bold">iterator insert(const T& x);
iterator insert(T&& x);
void insert(size_type n, const T& x);
template <class InputIterator1, class InputIterator2>
void insert(InputIterator1 first, InputIterator2 last);
void insert(initializer_list<T>);
template <class... Args>
iterator emplace(Args&&... args);</code></pre>
<ol>
<li>Preconditions: For <code>template <class InputIterator1, class InputIterator2> void insert(InputIterator1 first, InputIterator2 last)</code>, <code>InputIterator1</code> shall be <code>std::equality_comparable_with InputIterator2</code>.</li>
<li>Complexity: Insertion of a single element into a hive takes constant
time and exactly one call to a constructor of <code>T</code>. Insertion of
multiple elements into a hive is linear in the number of elements
inserted, and the number of calls to the copy constructor or move
constructor of <code>T</code> is exactly equal to the number of elements
inserted.</li>
<li>Remarks: Does not affect the validity of iterators and references, unless
an iterator points to <code>end()</code>, in which case it may be
invalidated. Likewise if a reverse_iterator points to <code>rend()</code>
it may be invalidated. If an exception is thrown there are no effects.</li>
</ol>
<br>
<code style="font-weight:bold">iterator erase(const_iterator position);</code>
<ol start="3">
<li>Effects: Invalidates only the iterators and references to the erased
element.</li>
<li>Complexity: Constant. [Note: operations pertaining to the updating of any data associated with the erased-elemment skipping mechanism is not factored into this; it is implementation-defined and may be constant, linear or otherwise defined. —end note]</li>
</ol>
<br>
<code style="font-weight:bold">iterator erase(const_iterator first, const_iterator last);</code>
<ol start="5">
<li>Effects: Invalidates only the iterators and references to the erased
elements. In some cases if an iterator is equal to <code>end()</code> and
the back element of the hive is erased, that iterator may be invalidated.
Likewise if a reverse_iterator is equal to <code>rend()</code> and the
front element of the hive is erased, that reverse_iterator may be
invalidated.</li>
<li>Complexity: Linear in the number of elements erased for
non-trivially-destructible types, for trivially-destructible types constant
in best case and linear in worst case, approximating logarithmic in the
number of elements erased on average.</li>
</ol>
<br>
<code style="font-weight:bold">void swap(hive& x) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value);<br>
</code>
<ol start="7">
<li>Effects: Exchanges the contents and <code>capacity()</code> of
<code>*this</code> with that of <code>x</code>.</li>
<li>Complexity: Constant time.</li>
</ol>
<h4>22.3.14.5 Operations [hive.operations]</h4>
<code style="font-weight:bold">void splice(hive &x);</code>
<ol>
<li>Preconditions: &x != this.</li>
<li>Effects: Inserts the contents of <code>x</code> into <code>*this</code>
and <code>x</code> becomes empty. Pointers and references to the moved
elements of <code>x</code> now refer to those same elements but as members
of <code>*this</code>. Iterators referring to the moved elements will
continue to refer to their elements, but they now behave as iterators into
<code>*this</code>, not into <code>x</code>.</li>
<li>Complexity: Constant time.</li>
<li>Throws: <code>length_error</code> if any of <code>x</code>'s element memory block capacities are outside the current minimum and maximum element
memory block capacity limits of <code>*this</code>.<sup><a href="#r223">223</a></sup></li>
</ol>
<br>
<code style="font-weight:bold">std::hive_limits block_capacity_limits() const noexcept;</code>
<ol start="4">
<li>Effects: Returns a std::hive_limits struct with the <code>min</code> and
<code>max</code> members set to the current minimum and maximum element
memory block capacity limits of <code>*this</code>.</li>
<li>Complexity: Constant time.</li>
</ol>
<br>
<code style="font-weight:bold">void reshape(std::hive_limits block_capacity_limits);</code><br>
<ol start="6">
<li>Preconditions: <code>T</code> shall be <i>Cpp17MoveInsertable</i> into
<code>*this</code>.<br>
</li>
<li>Effects: Sets minimum and maximum element memory block capacities to the
min and max members of the supplied std::hive_limits struct. If the hive is
not empty, adjusts existing memory block capacities to conform to the new
minimum and maximum block capacities, where necessary. If existing memory
block capacities are within the supplied minimum/maximum range, no
reallocation of elements takes place. If they are not within the supplied
range, elements are reallocated to new memory blocks which fit within the
supplied range and the old memory blocks are deallocated. Order of elements
is not guaranteed to be stable.</li>
<li>Complexity: If no reallocation occurs, constant time. If reallocation
occurs, complexity is linear in the number of elements reallocated.</li>
<li>Throws: <code>length_error</code> if <code>block_capacities.min</code> or
<code>block_capacities.max</code> are outside the implementation's minimum
and maximum element memory block capacity limits, or if
<code>block_capacities.min > block_capacities.max</code>.<sup><a href="#r223">223</a></sup></li>
<li>Remarks: The order of elements post-operation is not guaranteed to be
stable (16.5.5.8).</li>
</ol>
<br>
<code style="font-weight:bold">iterator get_iterator(pointer p) noexcept;<br>
const_iterator get_iterator(const_pointer p) const noexcept;</code>
<ol start="11">
<li>Effects: Returns an iterator or const_iterator pointing to the same element as the
pointer or const_pointer. If <code>p</code> does not point to an element in
<code>*this</code>, <code>end()</code> is returned.</li>
</ol>
<br>
<pre><code style="font-weight:bold">void sort();
template <class Compare>
void sort(Compare comp);</code></pre>
<ol start="12">
<li>Preconditions: <code>T</code> is <i>Cpp17MoveInsertable</i> into
<code>*this</code>.</li>
<li>Effects: Sorts the hive according to the <code>operator <</code> or
a <code>Compare</code> function object. If an exception is thrown, the
order of the elements in <code>*this</code> is unspecified. Iterators and
references may be invalidated.</li>
<li>Complexity: Approximately N log N comparisons, where <code>N == size()</code>.</li>
<li>Throws: <code>bad_alloc</code> if it fails to allocate any memory necessary for the sort process.</li>
<li>Remarks: Not required to be stable (16.5.5.8). May allocate memory.</li>
</ol>
<h4>22.3.14.6 Specialized algorithms [hive.special]</h4>
<pre><code style="font-weight:bold">friend void swap(hive &x, hive &y) noexcept(noexcept(x.swap(y)));</code></pre>
<ol>
<li>Effects: As if by <code>x.swap(y)</code>.</li>
<li>Remarks: This function is to be found via argument-dependent
lookup only.</li>
</ol>
<br>
<pre><code style="font-weight:bold">friend bool operator== (const hive &x, const hive &y);
friend bool operator!= (const hive &x, const hive &y);</code></pre>
<ol start="3">
<li>Returns: For ==, returns <code>True</code> if both containers have the same elements in the same iterative sequence, otherwise <code>False</code>.
For !=, returns <code>True</code> if both containers do not have the same elements in the same iterative sequence, otherwise <code>False</code>.</li>
<li>Remarks: These functions are to be found via argument-dependent lookup only.</li>
</ol>
<pre><code style="font-weight:bold">
class iterator
{
friend void advance(iterator &it, Distance n);
friend iterator next(iterator it, difference_type distance = 1);
friend iterator prev(iterator it, difference_type distance = 1);
friend difference_type distance(iterator first, iterator last);
}
class const_iterator
{
friend void advance(const_iterator &it, Distance n);
friend const_iterator next(const_iterator it, difference_type distance = 1);
friend const_iterator prev(const_iterator it, difference_type distance = 1);
friend difference_type distance(const_iterator first, const_iterator last);
}
class reverse_iterator
{
friend void advance(reverse_iterator &it, Distance n);
friend reverse_iterator next(reverse_iterator it, difference_type distance = 1);
friend reverse_iterator prev(reverse_iterator it, difference_type distance = 1);
friend difference_type distance(reverse_iterator first, reverse_iterator last);
}
class const_reverse_iterator
{
friend void advance(const_reverse_iterator &it, Distance n);
friend const_reverse_iterator next(const_reverse_iterator it, difference_type distance = 1);
friend const_reverse_iterator prev(const_reverse_iterator it, difference_type distance = 1);
friend difference_type distance(const_reverse_iterator first, const_reverse_iterator last);
}
</code></pre>
<ol start="5">
<li>Complexity: Constant in best case and linear in the number of elements traversed in worst case, approximating logarithmic in the number of elements traversed on average.</li>
<li>Remarks: These functions are to be found via argument-dependent lookup only.</li>
</ol>
<br>
<h4>22.3.14.7 Erasure [hive.erasure]</h4>
<pre><code style="font-weight:bold">template <class U>
friend size_type erase(hive& c, const U& value);</code></pre>
<ol>
<li>Effects: All elements in the container which are equal to <code>value</code> are erased. Invalidates all references and iterators to the erased elements.</li>
<li>Remarks: This function is to be found via argument-dependent lookup only.</li>
</ol>
<br>
<pre><code style="font-weight:bold">template <class Predicate>
friend size_type erase_if(hive& c, Predicate pred);</code></pre>
<ol start="3">
<li>Effects: All elements in the container which match predicate <code>pred</code> are erased. Invalidates all references and iterators to the erased elements.</li>
<li>Remarks: This function is to be found via argument-dependent lookup only.</li>
</ol>
<h2><a id="acknowledgements"></a>VII. Acknowledgements</h2>
<p>Matt would like to thank: Glen Fernandes and Ion Gaztanaga for restructuring
advice, Robert Ramey for documentation advice, various Boost and SG14 members for support, critiques and corrections, Baptiste Wicht for teaching me how to construct decent benchmarks, Jonathan Wakely, Sean Middleditch, Jens Maurer (very nearly a co-author at this point really),
Patrice Roy and Guy Davidson for standards-compliance advice and critiques, support, representation at meetings and bug reports, Henry Miller for getting me to clarify why the instrusive list/free list approach to memory location reuse is the most appropriate, Ville Voutilainen and Gasper Azman for help with the colony/hive rename paper, that ex-Lionhead guy for annoying me enough to force me to implement the original skipfield pattern, Jon Blow for some initial advice and Mike Acton for some influence, the community at large for giving me feedback and bug reports on the reference implementation.<br>
Also Nico Josuttis for doing such a great job in terms of explaining the general format of the structure to the committee.</p>
<h2>VIII. Appendices</h2>
<h3><a id="basicusage"></a>Appendix A - Basic usage examples</h3>
<p>Using <a href="https://plflib.org/colony.htm">reference implementation</a>.</p>
<div
style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;">
<pre style="margin: 0; line-height: 125%"><code><span style="color: #557799">#include <iostream></span>
<span style="color: #557799">#include <numeric></span>
<span style="color: #557799">#include "plf_hive.h"</span>
<span style="color: #333399; font-weight: bold">int</span> <span style="color: #0066BB; font-weight: bold">main</span>(<span style="color: #333399; font-weight: bold">int</span> argc, <span style="color: #333399; font-weight: bold">char</span> <span style="color: #333333">**</span>argv)
{
plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span><span style="color: #333333">></span> i_hive;
<span style="color: #888888">// Insert 100 ints:</span>
<span style="color: #008800; font-weight: bold">for</span> (<span style="color: #333399; font-weight: bold">int</span> i <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>; i <span style="color: #333333">!=</span> <span style="color: #0000DD; font-weight: bold">100</span>; <span style="color: #333333">++</span>i)
{
i_hive.insert(i);
}
<span style="color: #888888">// Erase half of them:</span>
<span style="color: #008800; font-weight: bold">for</span> (plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span><span style="color: #333333">>::</span>iterator it <span style="color: #333333">=</span> i_hive.begin(); it <span style="color: #333333">!=</span> i_hive.end(); <span style="color: #333333">++</span>it)
{
it <span style="color: #333333">=</span> i_hive.erase(it);
}
std<span style="color: #333333">::</span>cout <span style="color: #333333"><<</span> <span style="background-color: #fff0f0">"Total: "</span> <span style="color: #333333"><<</span> std::accumulate(i_hive.begin(), i_hive.end(), 0) <span style="color: #333333"><<</span> std<span style="color: #333333">::</span>endl;
std<span style="color: #333333">::</span>cin.get();
<span style="color: #008800; font-weight: bold">return</span> <span style="color: #0000DD; font-weight: bold">0</span>;
} </code></pre>
</div>
<h4>Example demonstrating pointer stability</h4>
<div
style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;">
<pre style="margin: 0; line-height: 125%"><code><span style="color: #557799">#include <iostream></span>
<span style="color: #557799">#include "plf_hive.h"</span>
<span style="color: #333399; font-weight: bold">int</span> <span style="color: #0066BB; font-weight: bold">main</span>(<span style="color: #333399; font-weight: bold">int</span> argc, <span style="color: #333399; font-weight: bold">char</span> <span style="color: #333333">**</span>argv)
{
plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span><span style="color: #333333">></span> i_hive;
plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span><span style="color: #333333">>::</span>iterator it;
plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span> <span style="color: #333333">*></span> p_hive;
plf<span style="color: #333333">::</span>hive<span style="color: #333333"><</span><span style="color: #333399; font-weight: bold">int</span> <span style="color: #333333">*>::</span>iterator p_it;
<span style="color: #888888">// Insert 100 ints to i_hive and pointers to those ints to p_hive:</span>
<span style="color: #008800; font-weight: bold">for</span> (<span style="color: #333399; font-weight: bold">int</span> i <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>; i <span style="color: #333333">!=</span> <span style="color: #0000DD; font-weight: bold">100</span>; <span style="color: #333333">++</span>i)
{
it <span style="color: #333333">=</span> i_hive.insert(i);
p_hive.insert(<span style="color: #333333">&</span>(<span style="color: #333333">*</span>it));
}
<span style="color: #888888">// Erase half of the ints:</span>
<span style="color: #008800; font-weight: bold">for</span> (it <span style="color: #333333">=</span> i_hive.begin(); it <span style="color: #333333">!=</span> i_hive.end(); <span style="color: #333333">++</span>it)
{
it <span style="color: #333333">=</span> i_hive.erase(it);
}
<span style="color: #888888">// Erase half of the int pointers:</span>
<span style="color: #008800; font-weight: bold">for</span> (p_it <span style="color: #333333">=</span> p_hive.begin(); p_it <span style="color: #333333">!=</span> p_hive.end(); <span style="color: #333333">++</span>p_it)
{
p_it <span style="color: #333333">=</span> p_hive.erase(p_it);
}
<span style="color: #888888">// Total the remaining ints via the pointer hive (pointers will still be valid even after insertions and erasures):</span>
<span style="color: #333399; font-weight: bold">int</span> total <span style="color: #333333">=</span> <span style="color: #0000DD; font-weight: bold">0</span>;
<span style="color: #008800; font-weight: bold">for</span> (p_it <span style="color: #333333">=</span> p_hive.begin(); p_it <span style="color: #333333">!=</span> p_hive.end(); <span style="color: #333333">++</span>p_it)
{
total <span style="color: #333333">+=</span> <span style="color: #333333">*</span>(<span style="color: #333333">*</span>p_it);
}
std<span style="color: #333333">::</span>cout <span style="color: #333333"><<</span> <span style="background-color: #fff0f0">"Total: "</span> <span style="color: #333333"><<</span> total <span style="color: #333333"><<</span> std<span style="color: #333333">::</span>endl;
<span style="color: #008800; font-weight: bold">if</span> (total <span style="color: #333333">==</span> <span style="color: #0000DD; font-weight: bold">2500</span>)
{
std<span style="color: #333333">::</span>cout <span style="color: #333333"><<</span> <span style="background-color: #fff0f0">"Pointers still valid!"</span> <span style="color: #333333"><<</span> std<span style="color: #333333">::</span>endl;
}
std<span style="color: #333333">::</span>cin.get();
<span style="color: #008800; font-weight: bold">return</span> <span style="color: #0000DD; font-weight: bold">0</span>;
} </code></pre>
</div>
<h3><a id="benchmarks"></a>Appendix B - Reference implementation benchmarks</h3>
<p>Benchmark results for the hive reference implementation under GCC on an Intel Xeon E3-1241 (Haswell) are <a
href="https://plflib.org/benchmarks_haswell_gcc.htm">here</a>.</p>
<p>Old benchmark results for an earlier version of hive under MSVC 2015
update 3, on an Intel Xeon E3-1241 (Haswell) are <a
href="https://plflib.org/benchmarks_haswell_msvc.htm">here</a>. There is no
commentary for the MSVC results.</p>
<h3><a id="faq"></a>Appendix C - Frequently Asked Questions</h3>
<ol>
<li><h4>Where is it worth using a hive in place of other std::
containers?</h4>
<p>As mentioned, it is worthwhile for performance reasons in situations
where the order of container elements is not important and:</p>
<ol type="a">
<li>Insertion order is unimportant</li>
<li>Insertions and erasures to the container occur frequently in
performance-critical code, <i><b>and</b></i> </li>
<li>Links to non-erased container elements may not be invalidated by
insertion or erasure.</li>
</ol>
<p>Under these circumstances a hive will generally out-perform other
std:: containers. In addition, because it never invalidates pointer
references to container elements (except when the element being pointed to
has been previously erased) it may make many programming tasks involving
inter-relating structures in an object-oriented or modular environment much
faster, and could be considered in those circumstances.</p>
</li>
<li><h4>What are some examples of situations where a hive might improve
performance?</h4>
<p>Some ideal situations to use a hive: cellular/atomic simulation,
persistent octtrees/quadtrees, game entities or destructible-objects in a
video game, particle physics, anywhere where objects are being created and
destroyed continuously. Also, anywhere where a vector of pointers to
dynamically-allocated objects or a std::list would typically end up being
used in order to preserve pointer stability but where order is
unimportant.</p>
</li>
<li><h4>Is it similar to a deque?</h4>
<p>A deque is reasonably dissimilar to a hive - being a double-ended
queue, it requires a different internal framework. In addition, being a
random-access container, having a growth factor for memory blocks in a
deque is problematic (though not impossible). A deque and hive have no
comparable performance characteristics except for insertion (assuming a
good deque implementation). Deque erasure performance varies wildly
depending on the implementation, but is generally similar to vector erasure
performance. A deque invalidates pointers to subsequent container elements
when erasing elements, which a hive does not, and guarantees ordered
insertion.</p>
</li>
<li><h4>What are the thread-safe guarantees?</h4>
<p>Unlike a std::vector, a hive can be read from and inserted into at the
same time (assuming different locations for read and write), however it
cannot be iterated over and written to at the same time. If we look at a
(non-concurrent implementation of) std::vector's thread-safe matrix to see
which basic operations can occur at the same time, it reads as follows
(please note push_back() is the same as insertion in this regard):</p>
<table border="1" cellspacing="3">
<tbody>
<tr>
<td><b>std::vector</b></td>
<td>Insertion</td>
<td>Erasure</td>
<td>Iteration</td>
<td>Read</td>
</tr>
<tr>
<td>Insertion</td>
<td>No</td>
<td>No</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Erasure</td>
<td>No</td>
<td>No</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Iteration</td>
<td>No</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Read</td>
<td>No</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p>In other words, multiple reads and iterations over iterators can happen
simultaneously, but the potential reallocation and pointer/iterator
invalidation caused by insertion/push_back and erasure means those
operations cannot occur at the same time as anything else. </p>
<p>hive on the other hand does not invalidate pointers/iterators to
non-erased elements during insertion and erasure, resulting in the
following matrix:</p>
<table border="1" cellspacing="3">
<tbody>
<tr>
<td><b>hive</b></td>
<td>Insertion</td>
<td>Erasure</td>
<td>Iteration</td>
<td>Read</td>
</tr>
<tr>
<td>Insertion</td>
<td>No</td>
<td>No</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Erasure</td>
<td>No</td>
<td>No</td>
<td>No</td>
<td>Mostly*</td>
</tr>
<tr>
<td>Iteration</td>
<td>No</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Read</td>
<td>Yes</td>
<td>Mostly*</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p><span style="font-size: 10pt">* Erasures will not invalidate iterators
unless the iterator points to the erased element.</span></p>
<p>In other words, reads may occur at the same time as insertions and
erasures (provided that the element being erased is not the element being
read), multiple reads and iterations may occur at the same time, but
iterations may not occur at the same time as an erasure or insertion, as
either of these may change the state of the skipfield which is being
iterated over, if a skipfield is used in the implementation. Note that iterators pointing to end() may be invalidated by
insertion.</p>
<p>So, hive could be considered more inherently thread-safe than a
(non-concurrent implementation of) std::vector, but still has some areas
which would require mutexes or atomics to navigate in a multithreaded
environment.</p>
</li>
<li><h4>Any pitfalls to watch out for?</h4>
<p>Because erased-element memory locations may be reused by
<code>insert()</code> and <code>emplace()</code>, insertion position is
essentially random unless no erasures have been made, or an equal number of
erasures and insertions have been made.</p>
</li>
<li><h4>What is the purpose of limiting memory block minimum and maximum
sizes?</h4>
<p>One reason might be to ensure that memory blocks match a certain
processor's cache or memory pathway sizes. Another reason to do this is
that it is slightly slower to obtain an erased-element location from the
list of groups-with-erasures (subsequently utilising that group's free list
of erased locations) and to reuse that space than to insert a new element
to the back of the hive (the default behavior when there are no
previously-erased elements). If there are any erased elements in active memory blocks at the moment of insertion, hive will recycle those memory locations.</p>
<p>So if a block size is large, and many erasures occur but the block is
not completely emptied, iterative performance might suffer due to large
memory gaps between any two non-erased elements and subsequent drop in data
locality and cache performance. In that scenario you may want to experiment
with benchmarking and limiting the minimum/maximum sizes of the blocks,
such that memory blocks are freed earlier and find the optimal size for the
given use case.</p>
</li>
<li><h4>What is hive's Abstract Data Type (ADT)?</h4>
<p>Though I am happy to be proven wrong I suspect hives/colonies/bucket arrays
are their own abstract data type. Some have suggested it's ADT is of type
bag, I would somewhat dispute this as it does not have typical bag
functionality such as <a href="http://www.austincc.edu/akochis/cosc1320/bag.htm">searching based on
value</a> (you can use std::find but it's o(n)) and adding this
functionality would slow down other performance characteristics. <a
href="https://en.wikipedia.org/wiki/Set_(abstract_data_type)#Multiset">Multisets/bags</a>
are also not sortable (by means other than automatically by key value).
hive does not utilize key values, is sortable, and does not provide the
sort of functionality frequently associated with a bag (e.g. counting the
number of times a specific value occurs).</p>
</li>
<li><h4><a id="remove_when_empty"></a>Why must blocks be removed from the iterative sequence when empty?</h4>
<p>Two reasons:</p>
<ol type="a">
<li>Standards compliance: if blocks aren't removed then <code>++</code>
and <code>--</code> iterator operations become undefined in terms of
time complexity, making them non-compliant with the C++ standard. At
the moment they are O(1) amortized, in the reference implementation this constitutes typically one update for both
skipfield and element pointers, but two if a skipfield jump takes the
iterator beyond the bounds of the current block and into the next
block. But if empty blocks are allowed, there could be anywhere between
1 and <code>std::numeric_limits<size_type>::max</code> empty
blocks between the current element and the next. Essentially you get
the same scenario as you do when iterating over a boolean skipfield. It
would be possible to move these to the back of the hive as trailing
blocks, or house them in a separate list or vector for future usage,
but this may create performance issues if any of the blocks are not at
their maximum size (see below).</li>
<li>Performance: iterating over empty blocks is slower than them not
being present, of course - but also if you have to allow for empty
blocks while iterating, then you have to include a while loop in every
iteration operation, which increases cache misses and code size. The
strategy of removing blocks when they become empty also statistically
removes (assuming randomized erasure patterns) smaller blocks from the
hive before larger blocks, which has a net result of improving
iteration, because with a larger block, more iterations within the
block can occur before the end-of-block condition is reached and a jump
to the next block (and subsequent cache miss) occurs. Lastly, pushing
to the back of a hive, provided there is still space and no new block
needs to be allocated, will be faster than recycling memory locations
as each subsequent insertion occurs in a subsequent memory location
(which is cache-friendlier) and also less computational work is
necessary. If a block is removed from the iterative sequence its recyclable memory locations are
also not usable, hence subsequent insertions are more likely to
be pushed to the back of the hive.</li>
</ol>
</li>
<li><h4>Why not reserve all empty memory blocks for future use during erasure, or None, rather than leaving this decision
undefined by the specification?</h4>
<p>The default scenario, for reasons of predictability, should be to free
the memory block in most cases. However for the reasons described in the design decisions section on erase(), retaining the back block at least has performance and latency benefits.
Therefore retaining no memory blocks is non-optimal in cases where the user is not using a custom allocator. Meanwhile, retaining All memory blocks is bad for performance as many small memory blocks will be retained, which decreases iterative performance due to lower cache locality.
However, one perspective is that if a scenario calls for
retaining memory blocks instead of deallocating them, this should be left
to an allocator to manage. Otherwise you get unpredictable memory behavior
across implementations, and this is one of the things that SG14 members
have complained about consistently with STL implementations. This is currently an open topic for discussion.</p>
</li>
<li><h4>Memory block sizes - what are they based on, how do they expand,
etc</h4>
<p>While implementations are free to chose their own limits and strategies here,
in the reference implementation memory block sizes start from either the
dynamically-defined default minimum size (8 elements, larger if the type stored is small) or an
amount defined by the end user (with a minimum of 3 elements, as there is enough metadata per-block that less than 3 elements is generally a waste of memory unless the value_type is extremely large).
Subsequent block sizes then increase the <i>total capacity</i> of the hive by a
factor of 2 (so, 1st block 8 elements, 2nd 8 elements, 3rd 16 elements, 4th
32 elements etcetera) until the maximum block size is reached. The default
maximum block size in the reference implementation is the maximum possible number that the skipfield
bitdepth is capable of representing (std::numeric_limits<skipfield_type>::max()). By default the
skipfield bitdepth is 16 so the maximum size of a block would be 65535
elements in that context.</p>
<p>The skipfield bitdepth was initially a template parameter which could be set to
any unsigned integer - unsigned char, unsigned int, Uint_64, etc. Unsigned
short (guaranteed to be at least 16 bit, equivalent to C++11's
uint_least16_t type) was found to have the best performance in real-world
testing on x86 and x86_64 platforms due to the balance between memory contiguousness, memory waste and
the number of allocations. unsigned char was found to have better performance below 1000 elements and of course lower memory use. Other platforms have not been tested. Since only two values were considered useful, they've been replaced in newer versions by a <code>priority</code> parameter, which specifies whether the priority of the instantiation is memory use or performance. While this is not strictly true in the sense that unsigned char will also have better performance for under 1000 elements, it is a compromise in order to have the implementation reflect a standard which may enable other implementations which do not share the same performance characteristics.</p>
</li>
<li><h4><a id="simd"></a>Can a hive be used with SIMD instructions?</h4>
<p>No and yes. Yes if you're careful, no if you're not.<br>
On platforms which support scatter and gather operations via hardware (e.g.
AVX512) you can use hive with SIMD as much as you want, using gather to
load elements from disparate or sequential locations, directly into a SIMD
register, in parallel. Then use scatter to push the post-SIMD-process
values elsewhere after. On platforms which do not support this in hardware,
you would need to manually implement a scalar gather-and-scatter operation
which may be significantly slower.</p>
<p>In situations where gather and scatter operations are too expensive,
which require elements to be contiguous in memory for SIMD processing, this
is more complicated. When you have a bunch of erasures in a hive, there's
no guarantee that your objects will be contiguous in memory, even though
they are sequential during iteration. Some of them may also be in different
memory blocks to each other. In these situations if you want to use SIMD
with hive, you must do the following:</p>
<ul>
<li>Set your minimum and maximum group sizes to multiples of the width of
your target processor's SIMD instruction size. If it supports 8
elements at once, set the group sizes to multiples of 8.</li>
<li>Either never erase from the hive, or:<br>
<ol>
<li>Shrink-to-fit after you erase (will invalidate all pointers to
elements within the hive).</li>
<li>Only erase from the back or front of the hive, and only erase
elements in multiples of the width of your SIMD instruction e.g. 8
consecutive elements at once. This will ensure that the
end-of-memory-block boundaries line up with the width of the SIMD
instruction, provided you've set your min/max block sizes as
above.</li>
</ol>
</li>
</ul>
<p>Generally if you want to use SIMD without gather/scatter, it's probably
preferable to use a vector or an array.</p>
</li>
</ol>
<h3><a id="responses" name="responses"></a>Appendix D - Specific responses to
previous committee feedback</h3>
<ol>
<li><h4>Naming</h4>
<p>See D2332R0.</p>
</li>
<li><h4>"Unordered and no associative lookup, so this only supports use cases
where you're going to do something to every element."</h4>
<p>As noted the container was originally designed for highly
object-oriented situations where you have many elements in different
containers linking to many other elements in other containers. This linking
can be done with pointers or iterators in hive (insert returns an
iterator which can be dereferenced to get a pointer, pointers can be
converted into iterators with the supplied functions (for erase etc)) and
because pointers/iterators stay stable regardless of insertion/erasure,
this usage is unproblematic. You could say the pointer is equivalent to a
key in this case (but without the overhead). That is the first access
pattern, the second is straight iteration over the container, as you say.
Secondly, the container does have (typically better than O(n))
advance/next/prev implementations, so multiple elements can be skipped.</p>
</li>
<li><h4>"Do we really need the Priority template parameter?"</h4>
<p>While technically a non-binding request, this parameter promotes the use of the container in heavily
memory-constrained environments like embedded programming. In the context of the reference implementation this means switching the skipfield type from unsigned short to unsigned char, in other implementations it could mean something else.
See more explanation in V. Technical Specifications.
Unfortunately this parameter also means <code>operator=</code>, swap and some
other functions won't work between hives of the same type but with differing priorities.</p>
</li>
<li><h4>"Prove this is not an allocator"</h4>
<p>I'm not really sure how to answer this, as I don't see the resemblance,
unless you count maps, vectors etc as being allocators also. The only
aspect of it which resembles what an allocator might do, is the memory
re-use mechanism. It would be impossible for an allocator to perform a
similar function while still allowing the container to iterate over the
data linearly in memory, preserving locality, in the manner described in
this document.</p>
</li>
<li><h4>"If this is for games, won't game devs just write their own versions
for specific types in order to get a 1% speed increase anyway?"</h4>
<p>This is true for many/most AAA game companies who are on the bleeding
edge, but they also do this for vector etc, so they aren't the target
audience of std:: for the most part; sub-AAA game companies are more likely
to use third party/pre-existing tools. As mentioned earlier, this structure
(bucket-array-like) crops up in <a
href="https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1iWHyVnsLBQ">many,
many fields</a>, not just game dev. So the target audience is probably
everyone other than AAA gaming, but even then, it facilitates communication
across fields and companies as to this type of container, giving it a
standardized name and understanding.</p>
</li>
<li><h4>"Is there active research in this problem space? Is it likely to
change in future?"</h4>
<p>The only current analysis has been around the question of whether it's
possible for this specification to fail to allow for a better
implementation in future. This is unlikely given the container's
requirements and how this impacts on implementation. Bucket arrays have
been around since the 1990s, there's been no significant innovation in them
until now. I've been researching/working on hive since early 2015, and
while I can't say for sure that a better implementation might not be
possible, I am confident that no change should be necessary to the
specification to allow for future implementations, if it is done correctly.
</p>
<p>The requirement of allowing no reallocations upon insertion or erasure,
truncates possible implementation strategies significantly. Memory blocks
have to be independently allocated so that they can be removed (when empty)
without triggering reallocation of subsequent elements. There's limited
numbers of ways to do that and keep track of the memory blocks at the same
time. Erased element locations must be recorded (for future re-use by
insertion) in a way that doesn't create allocations upon erasure, and
there's limited numbers of ways to do this also. Multiple consecutive
erased elements have to be skipped in O(1) time, and again there's limits
to how many ways you can do that. That covers the three core aspects upon
which this specification is based. See <a href="#design"></a>IV. Design
Decisions for the various ways these aspects can be designed.</p>
<p>The time complexity of updates to whatever erased-element skipping mechanism is used should, I think, be left
implementation-defined, as defining time complexity may obviate better
solutions which are faster but are not necessarily O(1). These updates
would likely occur during erasure, insertion, splicing and container copying.</p>
</li>
<li><h4>Why not iterate across the memory blocks backwards to find the first block with erasures to reuse, during insert?</h4>
<p>While this would statistically ensure that smaller blocks get deallocated first due to becoming empty faster than later blocks, it introduces uncertain latency issues during insert, particularly when custom memory block sizes are used and the number of elements is large. With the current implementation there is an intrusive list of blocks with erasures, and within each block's metadata there's a free list of skipblocks. When reusing the current head of the intrusive list determines the block, and the current head of that block's free list determines the skipblock to be reused. This means that the most recently erased element will be the first to reused. This works out well for two reasons: currently-contiguous sequences of elements will tend to stay that way, helping cache coherence, and when elements are erased and inserted in sequence those erased memory locations will tend to be already in the cache when inserting. Lastly, this structure involves a minimum of branching and checks, resulting in minimal latency during insertion and erasure.</p>
</ol>
<h3><a id="sg14gameengine"></a>Appendix E - Typical game engine
requirements</h3>
<p>Here are some more specific requirements with regards to game engines,
verified by game developers within SG14:</p>
<ol type="a">
<li>Elements within data collections refer to elements within other data
collections (through a variety of methods - indices, pointers, etc). These
references must stay valid throughout the course of the game/level. Any
container which causes pointer or index invalidation creates difficulties
or necessitates workarounds.</li>
<li>Order is unimportant for the most part. The majority of data is simply
iterated over, transformed, referred to and utilized with no regard to
order.</li>
<li>Erasing or otherwise "deactivating" objects occurs frequently in
performance-critical code. For this reason methods of erasure which create
strong performance penalties are avoided.</li>
<li>Inserting new objects in performance-critical code (during gameplay) is
also common - for example, a tree drops leaves, or a player spawns in an
online multiplayer game.</li>
<li>It is not always clear in advance how many elements there will be in a
container at the beginning of development, or at the beginning of a level
during play. Genericized game engines in particular have to adapt to
considerably different user requirements and scopes. For this reason
extensible containers which can expand and contract in realtime are
necessary.</li>
<li>Due to the effects of cache on performance, memory storage which is
more-or-less contiguous is preferred.</li>
<li>Memory waste is avoided.</li>
</ol>
<p>std::vector in its default state does not meet these requirements due to:
</p>
<ol>
<li>Poor (non-fill) single insertion performance (regardless of insertion
position) due to the need for reallocation upon reaching capacity</li>
<li>Insert invalidates pointers/iterators to all elements </li>
<li>Erase invalidates pointers/iterators/indexes to all elements after the
erased element</li>
</ol>
<p>Game developers therefore either develop custom solutions for each scenario
or implement workarounds for vector. The most common workarounds are most
likely the following or derivatives:</p>
<ol>
<li>Using a boolean flag or similar to indicate the inactivity of an object
(as opposed to actually erasing from the vector). Elements flagged as
inactive are skipped during iteration.<br>
<br>
Advantages: Fast "deactivation". Easy to manage in multi-access
environments.<br>
Disadvantages: Can be slower to iterate due to branching.</li>
<li>Using a vector of data and a secondary vector of indexes. When erasing,
the erasure occurs only in the vector of indexes, not the vector of data.
When iterating it iterates over the vector of indexes and accesses the data
from the vector of data via the remaining indexes.<br>
<br>
Advantages: Fast iteration.<br>
Disadvantages: Erasure still incurs some reallocation cost which can
increase jitter.</li>
<li>Combining a swap-with-back-element-and-pop approach to erasure with some form of
dereferenced lookup system to enable contiguous element iteration
(sometimes called a 'packed array': <a href="http://bitsquid.blogspot.ca/2011/09/managing-decoupling-part-4-id-lookup.html">http://bitsquid.blogspot.ca/2011/09/managing-decoupling-part-4-id-lookup.html</a>).
<br>
Advantages: Iteration is at standard vector speed.<br>
Disadvantages: Erasure will be slow if objects are large and/or
non-trivially copyable, thereby making swap costs large. All link-based
access to elements incur additional costs due to the dereferencing system.
</li>
</ol>
<p>hive brings a more generic solution to these contexts. While some
developers, particularly AAA developers, will almost always develop a custom
solution for specific use-cases within their engine, I believe most sub-AAA and
indie developers are more likely to rely on third party solutions. Regardless,
standardising the container will allow for greater cross-discipline
communication.</p>
<h3><a id="timecomplexityexplanations"></a>Appendix F - Time complexity
requirement explanations</h3>
<h5>Insert (single): O(1)</h5>
<p>One of the requirements of hive is that pointers to non-erased elements
stay valid regardless of insertion/erasure within the container. For this
reason the container must use multiple memory blocks. If a single memory block
were used, like in a std::vector, reallocation of elements would occur when the
container expanded (and the elements were copied to a larger memory block).
Instead, hive will insert into existing memory blocks when able, and create a
new memory block when all existing memory blocks are full. This keeps insertion
at O(1).</p>
<h5>Insert (multiple): O(N)</h5>
<p>Multiple insertions may allow an implementation to reserve suitably-sized
memory blocks in advance, reducing the number of allocations necessary (whereas
singular insertion would generally follow the implementation's block growth
pattern, possibly allocating more than necessary). However when it comes to
time complexity it has no advantages over singular insertion, is linear to the
number elements inserted.</p>
<h5>Erase (single): O(1)</h5>
<p>Erasure is a simple matter of destructing the element in question and
updating whatever data is associated with the erased-element skipping mechanism eg. the skipfield. Since we use a skipping mechanism to avoid erasures during
iterator, no reallocation of subsequent elements is necessary and the process
is O(1). Additionally, when using a Low-complexity jump-counting pattern the
skipfield update is also always O(1).</p>
<p>Note: When a memory block becomes empty of non-erased elements it must be
freed to the OS (or reserved for future insertions, depending on implementation)
and removed from the hive's sequence of memory blocks. It it was not, we
would end up with non-O(1) iteration, since there would be no way to predict
how many empty memory blocks there would be between the current memory block
being iterated over, and the next memory block with non-erased (active)
elements in it.</p>
<h5>Erase (multiple): O(N) for non-trivially-destructible types, for
trivially-destructible types between O(1) and O(N) depending on range
start/end, approximating O(log n) average</h5>
<p>In this case, where the element is non-trivially destructible, the time
complexity is O(N), with infrequent deallocation necessary from the removal of
an empty memory block as noted above. However where the elements are
trivially-destructible, if the range spans an entire memory block at any point,
that block and it's metadata can simply be removed without doing any
individual writes to it's metadata or individual destruction of elements,
potentially making this a O(1) operation.</p>
<p>In addition (when dealing with trivially-destructible types) for those
memory blocks where only a portion of elements are erased by the range, if no
prior erasures have occurred in that memory block you may be able to erase that range in
O(1) time, as, for example, if you are using a skipfield there will be no need to check the skipfield within the range for
previously erased elements. The reason you would need to check for previously
erased elements within that portion's range is so you can update the metadata
for that memory block to accurately reflect how many non-erased elements remain
within the block. The non-erased element-count metadata is necessary because
there is no other way to ascertain when a memory block is empty of non-erased
elements, and hence needs to be removed from the hive's iteration sequence.
The reasoning for why empty memory blocks must be removed is included in the
Erase(single) section, above.</p>
<p>However in most cases the erase range will not perfectly match the size of
all memory blocks, and with typical usage of a hive there is usually some
prior erasures in most memory blocks. So, for example, when dealing with a
hive of a trivially-destructible type, you might end up with a tail portion
of the first memory block in the erasure range being erased in O(N) time, the
second and intermediary memory block being completely erased and freed in O(1)
time, and only a small front portion of the third and final memory block in the
range being erased in O(N) time. Hence the time complexity for
trivially-destructible elements approximates O(log n) on average, being between
O(1) and O(N) depending on the start and end of the erasure range.</p>
<h5>std::find: O(N)</h5>
<p>This relies on basic iteration so is O(N).</p>
<h5>splice: O(1)</h5>
<p>hive only does full-container splicing, not partial-container splicing
(use range-insert with std::make_move_iterator to achieve the latter, albiet
with the loss of pointer validity to the moved range). When splicing, the
memory blocks from the source hive are transferred to the destination hive
without processing the individual elements. These blocks may either be placed
at the front of the hive or the end, depending on how full the source back
block is compared to the destination back block. If the destination back block
is more full ie. there is less unused space in it, it is better to put it at
the beginning of the source block - as otherwise this creates a larger gap to
skip during iteration which in turn affects cache locality. If there are unused
element memory spaces at the back of the destination container (ie. the final
memory block is not full) and a skipfield is used, the skipfield nodes corresponding to those empty
spaces must be altered to indicate that these are skipped elements.</p>
<h5>Iterator operators ++ and --: O(1) amortized</h5>
<p>Generally the time complexity is O(1), and if a skipfield pattern is used it must
allow for O(1) skipping of multiple erased elements. However every so often
iteration will involve a transistion to the next/previous memory block in the
hive's sequence of blocks, depending on whether we are doing ++ or --. At
this point a read of the next/previous memory block's corresponding skipfield would be
necessary, in case the front/back element(s) in that memory block are erased
and hence skipped. So for every block transition, 2 reads of the skipfield are
necessary instead of 1. Hence the time complexity is O(1) amortized.</p>
<p>If skipfields are used they must be per-element-memory-block and independent of subsequent/previous memory blocks, as
otherwise you end up with a vector for a skipfield, which would need a
range erased every time a memory block was removed from the hive (see notes
under Erase, above), and reallocation to a larger skipfield memory block when a
hive expanded. Both of these procedures carry reallocation costs, meaning you
could have thousands of skipfield nodes needing to be reallocated based on a
single erasure (from within a memory block which only had one non-erased
element left and hence would need to be removed from the hive). This is
unacceptable latency for any field involving high timing sensitivity (all of <a
href="https://lists.isocpp.org/mailman/listinfo.cgi/sg14/">SG14</a>).</p>
<h5>begin()/end(): O(1)</h5>
<p>For any implementation these should generally be stored as member variables
and so returning them is O(1).</p>
<h5>advance/next/prev: between O(1) and O(n), depending on current iterator
location, distance and implementation. Average for reference implementation
approximates O(log N).</h5>
<p>The reasoning for this is similar to that of Erase(multiple), above.
Complexity is dependent on state of hive, position of iterator and length of
<code>distance</code>, but in many cases will be less than linear. It is
necessary in a hive to store metadata both about the capacity of each block
(for the purpose of iteration) and how many non-erased elements are present
within the block (for the purpose of removing blocks from the iterative chain
once they become empty). For this reason, intermediary blocks between the
iterator's initial block and its final destination block (if these are not the
same block, and if the initial block and final block are not immediately
adjacent) can be skipped rather than iterated linearly across, by subtracting
the "number of non-erased elements" metadata from <code>distance</code> for
those blocks.</p>
<p>This means that the only linear time operations are any iterations within
the initial block and the final block. However if either the initial or final
block have no erased elements (as determined by comparing whether the block's
capacity metadata and the block's "number of non-erased elements" metadata are
equal), linear iteration can be skipped for that block and pointer/index math
used instead to determine distances, reducing complexity to constant time.
Hence the best case for this operation is constant time, the worst is linear to
the distance.</p>
<h5>distance: between O(1) and O(n), depending on current iterator location,
distance and implementation. Average for reference implementation approximates
O(log N).</h5>
<p>The same considerations which apply to advance, prev and next also apply to
distance - intermediary blocks between iterator1 and iterator2's blocks can be
skipped in constant time, if they exist. iterator1's block and iterator2's
block (if these are not the same block) must be linearly iterated across using
++ unless either block has no erased elements, in which case the operation
becomes pointer/index math and is reduced to constant time for that block. In
addition, if iterator1's block is not the same as iterator2's block, and
iterator2 is equal to end() or (end() - 1), or is the last element in that
block, iterator2's block's elements can also counted from the metadata rather
than iteration.</p>
<h3><a id="yunoconstexpr"></a>Appendix G - Why not constexpr?</h3>
<p>I am somewhat awkwardly forced into a position where I have to question and push back against the currently-unsubstantiated enthusiasm around constexpr containers and functions. At the time of writing there are no compilers which both support constexpr non-trivial destructors and also have a working implementation of a constexpr container. And until that is remedied, we won't really know what we're dealing with. My own testing in terms of making hive constexpr has not been encouraging. 2% performance decrease in un-altered benchmark code is common, and I suspect the common cause of this is caching values from compile-time when it is cheaper to calculate them on-the-fly than to return them from main memory. This suspicion is based on the substantial increases in executable size in the constexpr versions.</p>
<p>For an example of the latter, think about size() in std::vector. This can be calculated in most implementations by (vector.end_iterator.pointer - vector.memory_block), both of which will most likely be in cache at the time of calling size(). That's if size isn't a member variable or something.
Calculating a minus operation on stuff that's already in cache is about 100x faster than making a call out to main memory for a compile-time-stored value of this function, if that is necessary. Hence calculating size() will typically be faster than storing it, but a constexpr implementation and compiler currently won't make that distinction.</p>
<p>None of which is an issue if a container is being entirely used within a constexpr function which has been determined to be evaluated at compile time. The problems occur when constexpr containers are used in runtime code, but certain functions such as size() are determined to be able to be evaluated at compile time, and therefore have their results cached. This is not an okay situation. If there were a mechanism which specified that for a given class instance, it's constexpr functions may <b>not</b> be evaluated at compile time, then I would give the go-ahead. Similarly if there were a rule which stated that a class instance's member functions may only be evaluated at compile time if the class instance is instantiated and destructed at compile time, I would give the go-ahead. This is not the situation we have, and I can't support it.</p>
<p>Constexpr function calls:</p>
<ol type="a">
<li>shift the responsibility of storage of pre-calculated results from the programmer to the compiler, and remove the ability for the programmer to think about the cache locality/optimal storage of precalculated results</li>
<li>shift the decision of whether or not to evaluate at runtime/compile-time to the compiler, where in some cases doing it at compile-time and storing the result may decrease performance (see the <a href="https://fabiensanglard.net/doom3_documentation/DOOM-3-BFG-Technical-Note.pdf">Doom 3 BFG edition technical notes</a> for their experience with pre-calc'ing meshes vs calculating them on the fly)</li>
<li>may dramatically increase code size/file size in some cases if the return results of constexpr functions are large</li>
<li>may dramatically increase compile times</li>
<li>create the potential for cache pollution when constexpr functions return large amounts of data, or when functions returning small amounts of data are called many times</li>
</ol>
<p>Given this, and the performance issues mentioned above, I am reluctant to make hive constexpr-by-default. Time may sort these issues out, but I am personally happier for std::array and std::vector to be the "canaries in the coalmine" here. Certainly I won't be giving the go-ahead on any change that produces, or can produce, on current compilers, a 2% performance decrease in runtime code. Though I acknowledge the functionality of constexpr code may be useful to many.</p>
<h3><a id="referencediff"></a>Appendix H - Reference implementation differences and link</h3>
<p>The <a href="https://plflib.org/colony.htm">reference implementation</a> has a couple of key differences from the proposal, one is that it is named 'colony' by default, for historical and userbase reasons, and typedef'd to hive for optional usage under that name. This is only possible with C++11 and above due to the limits on template typedefs under C++98/03. Likewise the template parameter 'hive_priority' is a regular enum in the reference implementation, instead of a scoped enum, in order to be usable with C++98/03, and is 'colony_priority' by default with a typedef to hive_priority. Lastly the struct 'colony_limits' is also typedef'd to 'hive_limits'. Otherwise the reference implementation is or should be identical with the std::hive proposal.</p>
</body>
</html>
================================================
FILE: Docs/Proposals/Fixed_Point_Library_Proposal.md
================================================
**Document number**: LEWG, EWG, SG14, SG6: P0037R0
**Date**: 2015-09-28
**Project**: Programming Language C++, Library Evolution WG, SG14
**Reply-to**: John McFarlane, [fixed-point@john.mcfarlane.name](mailto:fixed-point@john.mcfarlane.name)
# Fixed-Point Real Numbers
## I. Introduction
This proposal introduces a system for performing binary fixed-point
arithmetic using built-in integral types.
## II. Motivation
Floating-point types are an exceedingly versatile and widely supported
method of expressing real numbers on modern architectures.
However, there are certain situations where fixed-point arithmetic is
preferable. Some systems lack native floating-point registers and must
emulate them in software. Many others are capable of performing some
or all operations more efficiently using integer arithmetic. Certain
applications can suffer from the variability in precision which comes
from a dynamic radix point [\[1\]](http://www.pathengine.com/Contents/Overview/FundamentalConcepts/WhyIntegerCoordinates/page.php).
In situations where a variable exponent is not desired, it takes
valuable space away from the significand and reduces precision.
Built-in integer types provide the basis for an efficient
representation of binary fixed-point real numbers. However, laborious,
error-prone steps are required to normalize the results of certain
operations and to convert to and from fixed-point types.
A set of tools for defining and manipulating fixed-point types is
proposed. These tools are designed to make work easier for those who
traditionally use integers to perform low-level, high-performance
fixed-point computation.
## III. Impact On the Standard
This proposal is a pure library extension. It does not require
changes to any standard classes, functions or headers.
## IV. Design Decisions
The design is driven by the following aims in roughly descending
order:
1. to automate the task of using integer types to perform low-level
binary fixed-point arithmetic;
2. to facilitate a style of code that is intuitive to anyone who is
comfortable with integer and floating-point arithmetic;
3. to avoid type promotion, implicit conversion or other behavior that
might lead to surprising results and
4. to preserve significant digits at the expense of insignificant
digits, i.e. to prefer underflow to overflow.
### Class Template
Fixed-point numbers are specializations of
template <class ReprType, int Exponent>
class fixed_point;
where the template parameters are described as follows.
#### `ReprType` Type Template Parameter
This parameter identifies the capacity and signedness of the
underlying type used to represent the value. In other words, the size
of the resulting type will be `sizeof(ReprType)` and it will be
signed iff `is_signed<ReprType>::value` is true. The default is
`int`.
`ReprType` must be a fundamental integral type and should not be the
largest size. Suitable types include: `std::int8_t`, `std::uint8_t`,
`std::int16_t`, `std::uint16_t`, `std::int32_t` and `std::uint32_t`.
In limited situations, `std::int64_t` and `std::uint64_t` can be used.
The reasons for these limitations relate to the difficulty in finding
a type that is suitable for performing lossless integer
multiplication.
#### `Exponent` Non-Type Template Parameter
The exponent of a fixed-point type is the equivalent of the exponent
field in a floating-point type and shifts the stored value by the
requisite number of bits necessary to produce the desired range. The
default value of `Exponent` is zero, giving `fixed_point<T>` the same
range as `T`.
The resolution of a specialization of `fixed_point` is
pow(2, Exponent)
and the minimum and maximum values are
std::numeric_limits<ReprType>::min() * pow(2, Exponent)
and
std::numeric_limits<ReprType>::max() * pow(2, Exponent)
respectively.
Any usage that results in values of `Exponent` which lie outside the
range, (`INT_MIN / 2`, `INT_MAX / 2`), may result in undefined
behavior and/or overflow or underflow. This range of exponent values
is far in excess of the largest built-in floting-point type and should
be adequate for all intents and purposes.
### `make_fixed` and `make_ufixed` Helper Type
The `Exponent` template parameter is versatile and concise. It is an
intuitive scale to use when considering the full range of positive and
negative exponents a fixed-point type might possess. It also
corresponds to the exponent field of built-in floating-point types.
However, most fixed-point formats can be described more intuitively by
the cardinal number of integer and/or fractional digits they contain.
Most users will prefer to distinguish fixed-point types using these
parameters.
For this reason, two aliases are defined in the style of
`make_signed`.
These aliases are declared as:
template <unsigned IntegerDigits, unsigned FractionalDigits = 0, bool IsSigned = true>
using make_fixed;
and
template <unsigned IntegerDigits, unsigned FractionalDigits = 0>
using make_ufixed;
They resolve to a `fixed_point` specialization with the given
signedness and number of integer and fractional digits. They may
contain additional integer and fractional digits.
For example, one could define and initialize an 8-bit, unsigned,
fixed-point variable with four integer digits and four fractional
digits:
make_ufixed<4, 4> value { 15.9375 };
or a 32-bit, signed, fixed-point number with two integer digits and 29
fractional digits:
make_fixed<2, 29> value { 3.141592653 };
### Conversion
Fixed-point numbers can be explicitly converted to and from built-in
arithmetic types.
While effort is made to ensure that significant digits are not lost
during conversion, no effort is made to avoid rounding errors.
Whatever would happen when converting to and from an integer type
largely applies to `fixed_point` objects also. For example:
make_ufixed<4, 4>(.006) == make_ufixed<4, 4>(0)
...equates to `true` and is considered a acceptable rounding error.
### Operator Overloads
Any operators that might be applied to integer types can also be
applied to fixed-point types. A guiding principle of operator
overloads is that they perform as little run-time computation as is
practically possible.
With the exception of shift and comparison operators, binary operators
can take any combination of:
* one or two fixed-point arguments and
* zero or one arguments of any arithmetic type, i.e. a type for which
`is_arithmetic` is true.
Where the inputs are not identical fixed-point types, a simple set of
promotion-like rules are applied to determine the return type:
1. If both arguments are fixed-point, a type is chosen which is the
size of the larger type, is signed if either input is signed and
has the maximum integer bits of the two inputs, i.e. cannot lose
high-significance bits through conversion alone.
2. If one of the arguments is a floating-point type, then the type of
the result is the smallest floating-point type of equal or greater
size than the inputs.
3. If one of the arguments is an integral type, then the result is the
other, fixed-point type.
Some examples:
make_ufixed<5, 3>{8} + make_ufixed<4, 4>{3} == make_ufixed<5, 3>{11};
make_ufixed<5, 3>{8} + 3 == make_ufixed<5, 3>{11};
make_ufixed<5, 3>{8} + float{3} == float{11};
The reasoning behind this choice is a combination of predictability
and performance. It is explained for each rule as follows:
1. ensures that the least computation is performed where fixed-point
types are used exclusively. Aside from multiplication and division
requiring shift operations, should require similar computational
costs to equivalent integer operations;
2. loosely follows the promotion rules for mixed-mode arithmetic,
ensures values with exponents far beyond the range of the
fixed-point type are catered for and avoids costly conversion from
floating-point to integer and
3. preserves the input fixed-point type whose range is far more likely
to be of deliberate importance to the operation.
Shift operator overloads require an integer type as the right-hand
parameter and return a type which is adjusted to accommodate the new
value without risk of overflow or underflow.
Comparison operators convert the inputs to a common result type
following the rules above before performing a comparison and returning
`true` or `false`.
#### Overflow
Because arithmetic operators return a result of equal capacity to
their inputs, they carry a risk of overflow. For instance,
make_fixed<4, 3>(15) + make_fixed<4, 3>(1)
causes overflow because because a type with 4 integer bits cannot
store a value of 16.
Overflow of any bits in a signed or unsigned fixed-point type is
classed as undefined behavior. This is a minor deviation from
built-in integer arithmetic where only signed overflow results in
undefined behavior.
#### Underflow
The other typical cause of lost bits is underflow where, for example,
make_fixed<7, 0>(15) / make_fixed<7, 0>(2)
results in a value of 7. This results in loss of precision but is
generally considered acceptable.
However, when all bits are lost due to underflow, the value is said
to be flushed and this is classed as undefined behavior.
### Dealing With Overflow and Flushes
Errors resulting from overflow and flushes are two of the biggest
headaches related to fixed-point arithmetic. Integers suffer the same
kinds of errors but are somewhat easier to reason about as they lack
fractional digits. Floating-point numbers are largely shielded from
these errors by their variable exponent and implicit bit.
Three strategies for avoiding overflow in fixed-point types are
presented:
1. simply leave it to the user to avoid overflow;
2. promote the result to a larger type to ensure sufficient capacity
or
3. adjust the exponent of the result upward to ensure that the top
limit of the type is sufficient to preserve the most significant
digits at the expense of the less significant digits.
For arithmetic operators, choice 1) is taken because it most closely
follows the behavior of integer types. Thus it should cause the least
surprise to the fewest users. This makes it far easier to reason
about in code where functions are written with a particular type in
mind. It also requires the least computation in most cases.
Choices 2) and 3) are more robust to overflow events. However, they
represent different trade-offs and neither one is the best fit in all
situations. For these reasons, they are presented as named functions.
#### Type Promotion
Function template, `promote`, borrows a term from the language
feature which avoids integer overflow prior to certain operations. It
takes a `fixed_point` object and returns the same value represented
by a larger `fixed_point` specialization.
For example,
promote(make_fixed<5, 2>(15.5))
is equivalent to
make_fixed<11, 4>(15.5)
Complimentary function template, `demote`, reverses the process,
returning a value of a smaller type.
#### Named Arithmetic Functions
The following named function templates can be used as a hassle-free
alternative to arithmetic operators in situations where the aim is
to avoid overflow.
Unary functions:
trunc_reciprocal, trunc_square, trunc_sqrt,
promote_reciprocal, promote_square
Binary functions:
trunc_add, trunc_subtract, trunc_multiply, trunc_divide
trunc_shift_left, trunc_shift_right,
promote_add, promote_sub, promote_multiply, promote_divide
Some notes:
1. The `trunc_` functions return the result as a type no larger than
the inputs and with an exponent adjusted to avoid overflow;
2. the `promote_` functions return the result as a type large enough
to avoid overflow and underflow;
3. the `_multiply` and `_square` functions are not guaranteed to be
available for 64-bit types;
4. the `_multiply` and `_square` functions produce undefined behavior
when all input parameters are the *most negative number*;
5. the `_square` functions return an unsigned type;
6. the `_add`, `_subtract`, `_multiply` and `_divide` functions take
heterogeneous `fixed_point` specializations;
7. the `_divide` and `_reciprocal` functions in no way guard against
divide-by-zero errors;
8. the `trunc_shift_` functions return results of the same type as
their first input parameter and
9. the list is by no means complete.
### Example
The following example calculates the magnitude of a 3-dimensional vector.
template <class Fp>
constexpr auto magnitude(const Fp & x, const Fp & y, const Fp & z)
-> decltype(trunc_sqrt(trunc_add(trunc_square(x), trunc_square(y), trunc_square(z))))
{
return trunc_sqrt(trunc_add(trunc_square(x), trunc_square(y), trunc_square(z)));
}
Calling the above function as follows
static_cast<double>(magnitude(
make_ufixed<4, 12>(1),
make_ufixed<4, 12>(4),
make_ufixed<4, 12>(9)));
returns the value, 9.890625.
## V. Technical Specification
### Header \<fixed_point\> Synopsis
namespace std {
template <class ReprType, int Exponent> class fixed_point;
template <unsigned IntegerDigits, unsigned FractionalDigits = 0, bool IsSigned = true>
using make_fixed;
template <unsigned IntegerDigits, unsigned FractionalDigits = 0>
using make_ufixed;
template <class ReprType, int IntegerDigits>
using make_fixed_from_repr;
template <class FixedPoint>
using promote_result;
template <class FixedPoint>
promote_result<FixedPoint>
constexpr promote(const FixedPoint & from) noexcept;
template <class FixedPoint>
using demote_result;
template <class FixedPoint>
demote_result<FixedPoint>
constexpr demote(const FixedPoint & from) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator==(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator!=(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator<(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator>(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator>=(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr bool operator<=(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent> operator-(
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent> operator+(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent> operator-(
const fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
fixed_point<ReprType, Exponent> & operator+=(
fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
fixed_point<ReprType, Exponent> & operator-=(
fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
fixed_point<ReprType, Exponent> & operator*=(
fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class ReprType, int Exponent>
fixed_point<ReprType, Exponent> & operator/=(
fixed_point<ReprType, Exponent> & lhs,
const fixed_point<ReprType, Exponent> & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator==(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator!=(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator<(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator>(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator>=(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator<=(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator+(
const Lhs & lhs,
const Rhs & rhs) noexcept;
template <class Lhs, class Rhs>
constexpr auto operator-(
const Lhs & lhs,
const Rhs & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class RhsReprType, int RhsExponent>
constexpr auto operator*(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class RhsReprType, int RhsExponent>
constexpr auto operator/(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class Integer>
constexpr auto operator*(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const Integer & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class Integer>
constexpr auto operator/(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const Integer & rhs) noexcept;
template <class Integer, class RhsReprType, int RhsExponent>
constexpr auto operator*(
const Integer & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class Integer, class RhsReprType, int RhsExponent>
constexpr auto operator/(
const Integer & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class Float>
constexpr auto operator*(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const Float & rhs) noexcept;
template <class LhsReprType, int LhsExponent, class Float>
constexpr auto operator/(
const fixed_point<LhsReprType, LhsExponent> & lhs,
const Float & rhs) noexcept;
template <class Float, class RhsReprType, int RhsExponent>
constexpr auto operator*(
const Float & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class Float, class RhsReprType, int RhsExponent>
constexpr auto operator/(
const Float & lhs,
const fixed_point<RhsReprType, RhsExponent> & rhs) noexcept;
template <class LhsReprType, int Exponent, class Rhs>
fixed_point<LhsReprType, Exponent> & operator+=(fixed_point<LhsReprType, Exponent> & lhs, const Rhs & rhs) noexcept;
template <class LhsReprType, int Exponent, class Rhs>
fixed_point<LhsReprType, Exponent> & operator-=(fixed_point<LhsReprType, Exponent> & lhs, const Rhs & rhs) noexcept;
template <class LhsReprType, int Exponent>
template <class Rhs, typename std::enable_if<std::is_arithmetic<Rhs>::value, int>::type Dummy>
fixed_point<LhsReprType, Exponent> &
fixed_point<LhsReprType, Exponent>::operator*=(const Rhs & rhs) noexcept;
template <class LhsReprType, int Exponent>
template <class Rhs, typename std::enable_if<std::is_arithmetic<Rhs>::value, int>::type Dummy>
fixed_point<LhsReprType, Exponent> &
fixed_point<LhsReprType, Exponent>::operator/=(const Rhs & rhs) noexcept;
template <class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent>
sqrt(const fixed_point<ReprType, Exponent> & x) noexcept;
template <class FixedPoint, unsigned N = 2>
using trunc_add_result;
template <class FixedPoint, class ... Tail>
trunc_add_result<FixedPoint, sizeof...(Tail) + 1>
constexpr trunc_add(const FixedPoint & addend1, const Tail & ... addend_tail);
template <class Lhs, class Rhs = Lhs>
using trunc_subtract_result;
template <class Lhs, class Rhs>
trunc_subtract_result<Lhs, Rhs>
constexpr trunc_subtract(const Lhs & minuend, const Rhs & subtrahend);
template <class Lhs, class Rhs = Lhs>
using trunc_multiply_result;
template <class Lhs, class Rhs>
trunc_multiply_result<Lhs, Rhs>
constexpr trunc_multiply(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class FixedPointDividend, class FixedPointDivisor = FixedPointDividend>
using trunc_divide_result;
template <class FixedPointDividend, class FixedPointDivisor>
trunc_divide_result<FixedPointDividend, FixedPointDivisor>
constexpr trunc_divide(const FixedPointDividend & lhs, const FixedPointDivisor & rhs) noexcept;
template <class FixedPoint>
using trunc_reciprocal_result;
template <class FixedPoint>
trunc_reciprocal_result<FixedPoint>
constexpr trunc_reciprocal(const FixedPoint & fixed_point) noexcept;
template <class FixedPoint>
using trunc_square_result;
template <class FixedPoint>
trunc_square_result<FixedPoint>
constexpr trunc_square(const FixedPoint & root) noexcept;
template <class FixedPoint>
using trunc_sqrt_result;
template <class FixedPoint>
trunc_sqrt_result<FixedPoint>
constexpr trunc_sqrt(const FixedPoint & square) noexcept;
template <int Integer, class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent + Integer>
trunc_shift_left(const fixed_point<ReprType, Exponent> & fp) noexcept;
template <int Integer, class ReprType, int Exponent>
constexpr fixed_point<ReprType, Exponent - Integer>
trunc_shift_right(const fixed_point<ReprType, Exponent> & fp) noexcept;
template <class FixedPoint, unsigned N = 2>
using promote_add_result;
template <class FixedPoint, class ... Tail>
promote_add_result<FixedPoint, sizeof...(Tail) + 1>
constexpr promote_add(const FixedPoint & addend1, const Tail & ... addend_tail);
template <class Lhs, class Rhs = Lhs>
using promote_subtract_result
template <class Lhs, class Rhs>
promote_subtract_result<Lhs, Rhs>
constexpr promote_subtract(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs = Lhs>
using promote_multiply_result;
template <class Lhs, class Rhs>
promote_multiply_result<Lhs, Rhs>
constexpr promote_multiply(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class Lhs, class Rhs = Lhs>
using promote_divide_result;
template <class Lhs, class Rhs>
promote_divide_result<Lhs, Rhs>
constexpr promote_divide(const Lhs & lhs, const Rhs & rhs) noexcept;
template <class FixedPoint>
using promote_square_result;
template <class FixedPoint>
promote_square_result<FixedPoint>
constexpr promote_square(const FixedPoint & root) noexcept;
}
#### `fixed_point<>` Class Template
template <class ReprType = int, int Exponent = 0>
class fixed_point
{
public:
using repr_type = ReprType;
constexpr static int exponent;
constexpr static int digits;
constexpr static int integer_digits;
constexpr static int fractional_digits;
fixed_point() noexcept;
template <class S, typename std::enable_if<_impl::is_integral<S>::value, int>::type Dummy = 0>
explicit constexpr fixed_point(S s) noexcept;
template <class S, typename std::enable_if<std::is_floating_point<S>::value, int>::type Dummy = 0>
explicit constexpr fixed_point(S s) noexcept;
template <class FromReprType, int FromExponent>
explicit constexpr fixed_point(const fixed_point<FromReprType, FromExponent> & rhs) noexcept;
template <class S, typename std::enable_if<_impl::is_integral<S>::value, int>::type Dummy = 0>
fixed_point & operator=(S s) noexcept;
template <class S, typename std::enable_if<std::is_floating_point<S>::value, int>::type Dummy = 0>
fixed_point & operator=(S s) noexcept;
template <class FromReprType, int FromExponent>
fixed_point & operator=(const fixed_point<FromReprType, FromExponent> & rhs) noexcept;
template <class S, typename std::enable_if<_impl::is_integral<S>::value, int>::type Dummy = 0>
explicit constexpr operator S() const noexcept;
template <class S, typename std::enable_if<std::is_floating_point<S>::value, int>::type Dummy = 0>
explicit constexpr operator S() const noexcept;
explicit constexpr operator bool() const noexcept;
template <class Rhs, typename std::enable_if<std::is_arithmetic<Rhs>::value, int>::type Dummy = 0>
fixed_point &operator*=(const Rhs & rhs) noexcept;
template <class Rhs, typename std::enable_if<std::is_arithmetic<Rhs>::value, int>::type Dummy = 0>
fixed_point & operator/=(const Rhs & rhs) noexcept;
constexpr repr_type data() const noexcept;
static constexpr fixed_point from_data(repr_type repr) noexcept;
};
## VI. Future Issues
### Library Support
Because the aim is to provide an alternative to existing arithmetic
types which are supported by the standard library, it is conceivable
that a future proposal might specialize existing class templates and
overload existing functions.
Possible candidates for overloading include the functions defined in
\<cmath\> and a templated specialization of `numeric_limits`. A new type
trait, `is_fixed_point`, would also be useful.
While `fixed_point` is intended to provide drop-in replacements to
existing built-ins, it may be preferable to deviate slightly from the
behavior of certain standard functions. For example, overloads of
functions from \<cmath\> will be considerably less concise, efficient
and versatile if they obey rules surrounding error cases. In
particular, the guarantee of setting `errno` in the case of an error
prevents a function from being defined as pure. This highlights a
wider issue surrounding the adoption of the functional approach and
compile-time computation that is beyond the scope of this document.
### Alternatives to Built-in Integer Types
The reason that `ReprType` is restricted to built-in integer types
is that a number of features require the use of a higher - or
lower-capacity type. Supporting alias templates are defined to
provide `fixed_point` with the means to invoke integer types of
specific capacity and signedness at compile time.
There is no general purpose way of deducing a higher or
lower-capacity type given a source type in the same manner as
`make_signed` and `make_unsigned`. If there were, this might be
adequate to allow alternative choices for `ReprType`.
### Bounded Integers
The bounded::integer library [\[2\]](http://doublewise.net/c++/bounded/)
exemplifies the benefits of keeping track of ranges of values in
arithmetic types at compile time.
To a limited extent, the `trunc_` functions defined here also keep
track of - and modify - the limits of values. However, a combination
of techniques is capable of producing superior results.
For instance, consider the following expression:
make_ufixed<2, 6> three(3);
auto n = trunc_square(trunc_square(three));
The type of `n` is `make_ufixed<8, 0>` but its value does not
exceed 81. Hence, an integer bit has been wasted. It may be possible
to track more accurate limits in the same manner as the
bounded::integer library in order to improve the precision of types
returned by `trunc_` functions. For this reason, the exact value of
the exponents of these return types is not given.
Notes:
* Bounded::integer is already supported by fixed-point library,
fp [\[3\]](https://github.com/mizvekov/fp).
* A similar library is the boost constrained_value library
[\[4\]](http://rk.hekko.pl/constrained_value/).
### Alternative Policies
The behavior of the types specialized from `fixed_point` represent
one sub-set of all potentially desirable behaviors. Alternative
characteristics include:
* different rounding strategies - other than truncation;
* overflow and underflow checks - possibly throwing exceptions;
* operator return type - adopting `trunc_` or `promote_` behavior;
* default-initialize to zero - currently uninitialized and
* saturation arithmetic - as opposed to modular arithmetic.
One way to extend `fixed_point` to cover these alternatives would be
to add non-type template parameters containing bit flags or enumerated
types. The default set of values would reflect `fixed_point` as it
stands currently.
## VII. Prior Art
Many examples of fixed-point support in C and C++ exist. While almost
all of them aim for low run-time cost and expressive alternatives to
raw integer manipulation, they vary greatly in detail and in terms of
their interface.
One especially interesting dichotomy is between solutions which offer
a discrete selection of fixed-point types and libraries which contain
a continuous range of exponents through type parameterization.
### N1169
One example of the former is found in proposal N1169
[\[5\]](http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1169.pdf),
the intent of which is to expose features found in certain embedded
hardware. It introduces a succinct set of language-level fixed-point
types and impose constraints on the number of integer or fractional
digits each can possess.
As with all examples of discrete-type fixed-point support, the limited
choice of exponents is a considerable restriction on the versatility
and expressiveness of the API.
Nevertheless, it may be possible to harness performance gains provided
by N1169 fixed-point types through explicit template specialization.
This is likely to be a valuable proposition to potential users of the
library who find themselves targeting platforms which support
fixed-point arithmetic at the hardware level.
### N3352
There are many other C++ libraries available which fall into the
latter category of continuous-range fixed-point arithmetic
[\[3\]](https://github.com/mizvekov/fp)
[\[6\]](http://www.codeproject.com/Articles/37636/Fixed-Point-Class)
[\[7\]](https://github.com/viboes/fixed_point). In particular, an
existing library proposal, N3352 [\[8\]](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html),
aims to achieve very similar goals through similar means and warrants
closer comparison than N1169.
N3352 introduces four class templates covering the quadrant of signed
versus unsigned and fractional versus integer numeric types. It is
intended to replace built-in types in a wide variety of situations and
accordingly, is highly compile-time configurable in terms of how
rounding and overflow are handled. Parameters to these four class
templates include the storage in bits and - for fractional types - the
resolution.
The `fixed_point` class template could probably - with a few caveats -
be generated using the two fractional types, `nonnegative` and
`negatable`, replacing the `ReprType` parameter with the integer bit
count of `ReprType`, specifying `fastest` for the rounding mode and
specifying `undefined` as the overflow mode.
However, fixed_point more closely and concisely caters to the needs of
users who already use integer types and simply desire a more concise,
less error-prone form. It more closely follows the four design aims of
the library and - it can be argued - more closely follows the spirit
of the standard in its pursuit of zero-cost abstraction.
Some aspects of the design of the N3352 API which back up these
conclusion are that:
* the result of arithmetic operations closely resemble the `trunc_`
function templates and are potentially more costly at run-time;
* the nature of the range-specifying template parameters - through
careful framing in mathematical terms - abstracts away valuable
information regarding machine-critical type size information;
* the breaking up of duties amongst four separate class templates
introduces four new concepts and incurs additional mental load for
relatively little gain while further detaching the interface from
vital machine-level details and
* the absence of the most negative number from signed types reduces
the capacity of all types by one.
The added versatility that the N3352 API provides regarding rounding
and overflow handling are of relatively low priority to users who
already bear the scars of battles with raw integer types.
Nevertheless, providing them as options to be turned on or off at
compile time is an ideal way to leave the choice in the hands of the
user.
Many high-performance applications - in which fixed-point is of
potential value - favor run-time checks during development which are
subsequently deactivated in production builds. The N3352 interface is
highly conducive to this style of development. It is an aim of the
fixed_point design to be similarly extensible in future revisions.
## VIII. Acknowledgements
Subgroup: Guy Davidson, Michael Wong
Contributors: Ed Ainsley, Billy Baker, Lance Dyson, Marco Foco,
Clément Grégoire, Nicolas Guillemot, Matt Kinzelman, Joël Lamotte,
Sean Middleditch, Patrice Roy, Peter Schregle, Ryhor Spivak
## IX. References
1. Why Integer Coordinates?, <http://www.pathengine.com/Contents/Overview/FundamentalConcepts/WhyIntegerCoordinates/page.php>
2. C++ bounded::integer library, <http://doublewise.net/c++/bounded/>
3. fp, C++14 Fixed Point Library, <https://github.com/mizvekov/fp>
4. Boost Constrained Value Libarary, <http://rk.hekko.pl/constrained_value/>
5. N1169, Extensions to support embedded processors, <http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1169.pdf>
6. fpmath, Fixed Point Math Library, <http://www.codeproject.com/Articles/37636/Fixed-Point-Class>
7. Boost fixed_point (proposed), Fixed point integral and fractional types, <https://github.com/viboes/fixed_point>
8. N3352, C++ Binary Fixed-Point Arithmetic, <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html>
9. fixed_point, Reference Implementation of P0037, <https://github.com/johnmcfarlane/fixed_point>
## X. Appendix 1: Reference Implementation
An in-development implementation of the fixed_point class template and
its essential supporting functions and types is available
[\[9\]](https://github.com/johnmcfarlane/fixed_point). It includes a
utility header containing such things as math and trigonometric
functions and a partial `numeric_limits` specialization. Compile-time
and run-time tests are included as well as benchmarking support. It is
the source of examples and measurements cited here.
## XI. Appendix 2: Performance
Despite a focus on usable interface and direct translation from
integer-based fixed-point operations, there is an overwhelming
expectation that the source code result in minimal instructions and
clock cycles. A few preliminary numbers are presented to give a very
early idea of how the API might perform.
Some notes:
* A few test functions were run, ranging from single arithmetic
operations to basic geometric functions, performed against integer,
floating-point and fixed-point types for comparison.
* Figures were taken from a single CPU, OS and compiler, namely:
Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: x86_64-pc-linux-gnu
Thread model: posix
* Fixed inputs were provided to each function, meaning that branch
prediction rarely fails. Results may also not represent the full
range of inputs.
* Details of the test harness used can be found in the source
project mentioned in Appendix 1;
* Times are in nanoseconds;
* Code has not yet been optimized for performance.
### Types
Where applicable various combinations of integer, floating-point and
fixed-point types were tested with the following identifiers:
* `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`,
`uint64_t` and `int64_t` built-in integer types;
* `float`, `double` and `long double` built-in floating-point types;
* s3:4, u4:4, s7:8, u8:8, s15:16, u16:16, s31:32 and u32:32 format
fixed-point types.
### Basic Arithmetic
Plus, minus, multiplication and division were tested in isolation
using a number of different numeric types with the following results:
name cpu_time
add(float) 1.78011
add(double) 1.73966
add(long double) 3.46011
add(u4_4) 1.87726
add(s3_4) 1.85051
add(u8_8) 1.85417
add(s7_8) 1.82057
add(u16_16) 1.94194
add(s15_16) 1.93463
add(u32_32) 1.94674
add(s31_32) 1.94446
add(int8_t) 2.14857
add(uint8_t) 2.12571
add(int16_t) 1.9936
add(uint16_t) 1.88229
add(int32_t) 1.82126
add(uint32_t) 1.76
add(int64_t) 1.76
add(uint64_t) 1.83223
sub(float) 1.96617
sub(double) 1.98491
sub(long double) 3.55474
sub(u4_4) 1.77006
sub(s3_4) 1.72983
sub(u8_8) 1.72983
sub(s7_8) 1.72983
sub(u16_16) 1.73966
sub(s15_16) 1.85051
sub(u32_32) 1.88229
sub(s31_32) 1.87063
sub(int8_t) 1.76
sub(uint8_t) 1.74994
sub(int16_t) 1.82126
sub(uint16_t) 1.83794
sub(int32_t) 1.89074
sub(uint32_t) 1.85417
sub(int64_t) 1.83703
sub(uint64_t) 2.04914
mul(float) 1.9376
mul(double) 1.93097
mul(long double) 102.446
mul(u4_4) 2.46583
mul(s3_4) 2.09189
mul(u8_8) 2.08
mul(s7_8) 2.18697
mul(u16_16) 2.12571
mul(s15_16) 2.10789
mul(u32_32) 2.10789
mul(s31_32) 2.10789
mul(int8_t) 1.76
mul(uint8_t) 1.78011
mul(int16_t) 1.8432
mul(uint16_t) 1.76914
mul(int32_t) 1.78011
mul(uint32_t) 2.19086
mul(int64_t) 1.7696
mul(uint64_t) 1.79017
div(float) 5.12
div(double) 7.64343
div(long double) 8.304
div(u4_4) 3.82171
div(s3_4) 3.82171
div(u8_8) 3.84
div(s7_8) 3.8
div(u16_16) 9.152
div(s15_16) 11.232
div(u32_32) 30.8434
div(s31_32) 34
div(int8_t) 3.82171
div(uint8_t) 3.82171
div(int16_t) 3.8
div(uint16_t) 3.82171
div(int32_t) 3.82171
div(uint32_t) 3.81806
div(int64_t) 10.2286
div(uint64_t) 8.304
Among the slowest types are `long double`. It is likely that they are
emulated in software. The next slowest operations are fixed-point
multiply and divide operations - especially with 64-bit types. This is
because values need to be promoted temporarily to double-width types.
This is a known fixed-point technique which inevitably experiences
slowdown where a 128-bit type is required on a 64-bit system.
Here is a section of the disassembly of the s15:16 multiply call:
30: mov %r14,%rax
mov %r15,%rax
movslq -0x28(%rbp),%rax
movslq -0x30(%rbp),%rcx
imul %rax,%rcx
shr $0x10,%rcx
mov %ecx,-0x38(%rbp)
mov %r12,%rax
4c: movzbl (%rbx),%eax
cmp $0x1,%eax
↓ jne 68
54: mov 0x8(%rbx),%rax
lea 0x1(%rax),%rcx
mov %rcx,0x8(%rbx)
cmp 0x38(%rbx),%rax
↑ jb 30
The two 32-bit numbers are multiplied together and the result shifted
down - much as it would if raw `int` values were used. The efficiency
of this operation varies with the exponent. An exponent of zero should
mean no shift at all.
### 3-Dimensional Magnitude Squared
A fast `sqrt` implementation has not yet been tested with
`fixed_point`. (The naive implementation takes over 300ns.) For this
reason, a magnitude-squared function is measured, combining multiply
and add operations:
template <typename FP>
constexpr FP magnitude_squared(const FP & x, const FP & y, const FP & z)
{
return x * x + y * y + z * z;
}
Only real number formats are tested:
float 2.42606
double 2.08
long double 4.5056
s3_4 2.768
s7_8 2.77577
s15_16 2.752
s31_32 4.10331
Again, the size of the type seems to have the largest impact.
### Circle Intersection
A similar operation includes a comparison and branch:
template <typename Real>
bool circle_intersect_generic(Real x1, Real y1, Real r1, Real x2, Real y2, Real r2)
{
auto x_diff = x2 - x1;
auto y_diff = y2 - y1;
auto distance_squared = x_diff * x_diff + y_diff * y_diff;
auto touch_distance = r1 + r2;
auto touch_distance_squared = touch_distance * touch_distance;
return distance_squared <= touch_distance_squared;
}
float 3.46011
double 3.48
long double 6.4
s3_4 3.88
s7_8 4.5312
s15_16 3.82171
s31_32 5.92
Again, fixed-point and native performance are comparable.
================================================
FILE: Docs/Proposals/p0037.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Fixed_Point_Library_Proposal</title>
<style>.markdown-preview, .markdown-preview[data-use-github-style] { font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif; font-size: 16px; line-height: 1.6; word-wrap: break-word; overflow: scroll; box-sizing: border-box; padding: 20px; background-color: rgb(255, 255, 255); }
.markdown-preview > :first-child, .markdown-preview[data-use-github-style] > :first-child { margin-top: 0px !important; }
.markdown-preview > :last-child, .markdown-preview[data-use-github-style] > :last-child { margin-bottom: 0px !important; }
.markdown-preview a:not([href]), .markdown-preview[data-use-github-style] a:not([href]) { color: inherit; text-decoration: none; }
.markdown-preview .absent, .markdown-preview[data-use-github-style] .absent { color: rgb(204, 0, 0); }
.markdown-preview .anchor, .markdown-preview[data-use-github-style] .anchor { position: absolute; top: 0px; left: 0px; display: block; padding-right: 6px; padding-left: 30px; margin-left: -30px; }
.markdown-preview .anchor:focus, .markdown-preview[data-use-github-style] .anchor:focus { outline: none; }
.markdown-preview h1, .markdown-preview[data-use-github-style] h1, .markdown-preview h2, .markdown-preview[data-use-github-style] h2, .markdown-preview h3, .markdown-preview[data-use-github-style] h3, .markdown-preview h4, .markdown-preview[data-use-github-style] h4, .markdown-preview h5, .markdown-preview[data-use-github-style] h5, .markdown-preview h6, .markdown-preview[data-use-github-style] h6 { position: relative; margin-top: 1em; margin-bottom: 16px; font-weight: bold; line-height: 1.4; }
.markdown-preview h1 .octicon-link, .markdown-preview[data-use-github-style] h1 .octicon-link, .markdown-preview h2 .octicon-link, .markdown-preview[data-use-github-style] h2 .octicon-link, .markdown-preview h3 .octicon-link, .markdown-preview[data-use-github-style] h3 .octicon-link, .markdown-preview h4 .octicon-link, .markdown-preview[data-use-github-style] h4 .octicon-link, .markdown-preview h5 .octicon-link, .markdown-preview[data-use-github-style] h5 .octicon-link, .markdown-preview h6 .octicon-link, .markdown-preview[data-use-github-style] h6 .octicon-link { display: none; color: rgb(0, 0, 0); vertical-align: middle; }
.markdown-preview h1:hover .anchor, .markdown-preview[data-use-github-style] h1:hover .anchor, .markdown-preview h2:hover .anchor, .markdown-preview[data-use-github-style] h2:hover .anchor, .markdown-preview h3:hover .anchor, .markdown-preview[data-use-github-style] h3:hover .anchor, .markdown-preview h4:hover .anchor, .markdown-preview[data-use-github-style] h4:hover .anchor, .markdown-preview h5:hover .anchor, .markdown-preview[data-use-github-style] h5:hover .anchor, .markdown-preview h6:hover .anchor, .markdown-preview[data-use-github-style] h6:hover .anchor { padding-left: 8px; margin-left: -30px; text-decoration: none; }
.markdown-preview h1:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h1:hover .anchor .octicon-link, .markdown-preview h2:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h2:hover .anchor .octicon-link, .markdown-preview h3:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h3:hover .anchor .octicon-link, .markdown-preview h4:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h4:hover .anchor .octicon-link, .markdown-preview h5:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h5:hover .anchor .octicon-link, .markdown-preview h6:hover .anchor .octicon-link, .markdown-preview[data-use-github-style] h6:hover .anchor .octicon-link { display: inline-block; }
.markdown-preview h1 tt, .markdown-preview[data-use-github-style] h1 tt, .markdown-preview h2 tt, .markdown-preview[data-use-github-style] h2 tt, .markdown-preview h3 tt, .markdown-preview[data-use-github-style] h3 tt, .markdown-preview h4 tt, .markdown-preview[data-use-github-style] h4 tt, .markdown-preview h5 tt, .markdown-preview[data-use-github-style] h5 tt, .markdown-preview h6 tt, .markdown-preview[data-use-github-style] h6 tt, .markdown-preview h1 code, .markdown-preview[data-use-github-style] h1 code, .markdown-preview h2 code, .markdown-preview[data-use-github-style] h2 code, .markdown-preview h3 code, .markdown-preview[data-use-github-style] h3 code, .markdown-preview h4 code, .markdown-preview[data-use-github-style] h4 code, .markdown-preview h5 code, .markdown-preview[data-use-github-style] h5 code, .markdown-preview h6 code, .markdown-preview[data-use-github-style] h6 code { font-size: inherit; }
.markdown-preview h1, .markdown-preview[data-use-github-style] h1 { padding-bottom: 0.3em; font-size: 2.25em; line-height: 1.2; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(238, 238, 238); }
.markdown-preview h1 .anchor, .markdown-preview[data-use-github-style] h1 .anchor { line-height: 1; }
.markdown-preview h2, .markdown-preview[data-use-github-style] h2 { padding-bottom: 0.3em; font-size: 1.75em; line-height: 1.225; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(238, 238, 238); }
.markdown-preview h2 .anchor, .markdown-preview[data-use-github-style] h2 .anchor { line-height: 1; }
.markdown-preview h3, .markdown-preview[data-use-github-style] h3 { font-size: 1.5em; line-height: 1.43; }
.markdown-preview h3 .anchor, .markdown-preview[data-use-github-style] h3 .anchor { line-height: 1.2; }
.markdown-preview h4, .markdown-preview[data-use-github-style] h4 { font-size: 1.25em; }
.markdown-preview h4 .anchor, .markdown-preview[data-use-github-style] h4 .anchor { line-height: 1.2; }
.markdown-preview h5, .markdown-preview[data-use-github-style] h5 { font-size: 1em; }
.markdown-preview h5 .anchor, .markdown-preview[data-use-github-style] h5 .anchor { line-height: 1.1; }
.markdown-preview h6, .markdown-preview[data-use-github-style] h6 { font-size: 1em; color: rgb(119, 119, 119); }
.markdown-preview h6 .anchor, .markdown-preview[data-use-github-style] h6 .anchor { line-height: 1.1; }
.markdown-preview p, .markdown-preview[data-use-github-style] p, .markdown-preview blockquote, .markdown-preview[data-use-github-style] blockquote, .markdown-preview ul, .markdown-preview[data-use-github-style] ul, .markdown-preview ol, .markdown-preview[data-use-github-style] ol, .markdown-preview dl, .markdown-preview[data-use-github-style] dl, .markdown-preview table, .markdown-preview[data-use-github-style] table, .markdown-preview pre, .markdown-preview[data-use-github-style] pre { margin-top: 0px; margin-bottom: 16px; }
.markdown-preview hr, .markdown-preview[data-use-github-style] hr { height: 4px; padding: 0px; margin: 16px 0px; border: 0px none; background-color: rgb(231, 231, 231); }
.markdown-preview ul, .markdown-preview[data-use-github-style] ul, .markdown-preview ol, .markdown-preview[data-use-github-style] ol { padding-left: 2em; }
.markdown-preview ul.no-list, .markdown-preview[data-use-github-style] ul.no-list, .markdown-preview ol.no-list, .markdown-preview[data-use-github-style] ol.no-list { padding: 0px; list-style-type: none; }
.markdown-preview ul ul, .markdown-preview[data-use-github-style] ul ul, .markdown-preview ul ol, .markdown-preview[data-use-github-style] ul ol, .markdown-preview ol ol, .markdown-preview[data-use-github-style] ol ol, .markdown-preview ol ul, .markdown-preview[data-use-github-style] ol ul { margin-top: 0px; margin-bottom: 0px; }
.markdown-preview li > p, .markdown-preview[data-use-github-style] li > p { margin-top: 16px; }
.markdown-preview dl, .markdown-preview[data-use-github-style] dl { padding: 0px; }
.markdown-preview dl dt, .markdown-preview[data-use-github-style] dl dt { padding: 0px; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: bold; }
.markdown-preview dl dd, .markdown-preview[data-use-github-style] dl dd { padding: 0px 16px; margin-bottom: 16px; }
.markdown-preview blockquote, .markdown-preview[data-use-github-style] blockquote { padding: 0px 15px; color: rgb(119, 119, 119); border-left-width: 4px; border-left-style: solid; border-left-color: rgb(221, 221, 221); }
.markdown-preview blockquote > :first-child, .markdown-preview[data-use-github-style] blockquote > :first-child { margin-top: 0px; }
.markdown-preview blockquote > :last-child, .markdown-preview[data-use-github-style] blockquote > :last-child { margin-bottom: 0px; }
.markdown-preview table, .markdown-preview[data-use-github-style] table { display: block; width: 100%; overflow: auto; word-break: normal; }
.markdown-preview table th, .markdown-preview[data-use-github-style] table th { font-weight: bold; }
.markdown-preview table th, .markdown-preview[data-use-github-style] table th, .markdown-preview table td, .markdown-preview[data-use-github-style] table td { padding: 6px 13px; border: 1px solid rgb(221, 221, 221); }
.markdown-preview table tr, .markdown-preview[data-use-github-style] table tr { border-top-width: 1px; border-top-style: solid; border-top-color: rgb(204, 204, 204); background-color: rgb(255, 255, 255); }
.markdown-preview table tr:nth-child(2n), .markdown-preview[data-use-github-style] table tr:nth-child(2n) { background-color: rgb(248, 248, 248); }
.markdown-preview img, .markdown-preview[data-use-github-style] img { max-width: 100%; box-sizing: border-box; }
.markdown-preview .emoji, .markdown-preview[data-use-github-style] .emoji { max-width: none; }
.markdown-preview span.frame, .markdown-preview[data-use-github-style] span.frame { display: block; overflow: hidden; }
.markdown-preview span.frame > span, .markdown-preview[data-use-github-style] span.frame > span { display: block; float: left; width: auto; padding: 7px; margin: 13px 0px 0px; overflow: hidden; border: 1px solid rgb(221, 221, 221); }
.markdown-preview span.frame span img, .markdown-preview[data-use-github-style] span.frame span img { display: block; float: left; }
.markdown-preview span.frame span span, .markdown-preview[data-use-github-style] span.frame span span { display: block; padding: 5px 0px 0px; clear: both; color: rgb(51, 51, 51); }
.markdown-preview span.align-center, .markdown-preview[data-use-github-style] span.align-center { display: block; overflow: hidden; clear: both; }
.markdown-preview span.align-center > span, .markdown-preview[data-use-github-style] span.align-center > span { display: block; margin: 13px auto 0px; overflow: hidden; text-align: center; }
.markdown-preview span.align-center span img, .markdown-preview[data-use-github-style] span.align-center span img { margin: 0px auto; text-align: center; }
.markdown-preview span.align-right, .markdown-preview[data-use-github-style] span.align-right { display: block; overflow: hidden; clear: both; }
.markdown-preview span.align-right > span, .markdown-preview[data-use-github-style] span.align-right > span { display: block; margin: 13px 0px 0px; overflow: hidden; text-align: right; }
.markdown-preview span.align-right span img, .markdown-preview[data-use-github-style] span.align-right span img { margin: 0px; text-align: right; }
.markdown-preview span.float-left, .markdown-preview[data-use-github-style] span.float-left { display: block; float: left; margin-right: 13px; overflow: hidden; }
.markdown-preview span.float-left span, .markdown-preview[data-use-github-style] span.float-left span { margin: 13px 0px 0px; }
.markdown-preview span.float-right, .markdown-preview[data-use-github-style] span.float-right { display: block; float: right; margin-left: 13px; overflow: hidden; }
.markdown-preview span.float-right > span, .markdown-preview[data-use-github-style] span.float-right > span { display: block; margin: 13px auto 0px; overflow: hidden; text-align: right; }
.markdown-preview code, .markdown-preview[data-use-github-style] code, .markdown-preview tt, .markdown-preview[data-use-github-style] tt { padding: 0.2em 0px; margin: 0px; font-size: 85%; border-radius: 3px; background-color: rgba(0, 0, 0, 0.0392157); }
.markdown-preview code::before, .markdown-preview[d
gitextract_rzo9ruc3/
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── Docs/
│ ├── Proposals/
│ │ ├── D0447R16 - Introduction of hive to the Standard Library.html
│ │ ├── Fixed_Point_Library_Proposal.md
│ │ ├── p0037.html
│ │ ├── rawstorage.html
│ │ ├── ring_proposal_r5.tex
│ │ ├── uninitialized.html
│ │ └── unstable_remove.html
│ ├── fixed_point.md
│ └── plf_licensing.txt
├── README.md
├── SG14/
│ ├── algorithm_ext.h
│ ├── flat_map.h
│ ├── flat_set.h
│ ├── inplace_function.h
│ ├── plf_colony.h
│ ├── ring.h
│ └── slot_map.h
└── SG14_test/
├── SG14_test.h
├── flat_map_test.cpp
├── flat_set_test.cpp
├── inplace_function_test.cpp
├── main.cpp
├── plf_colony_test.cpp
├── ring_test.cpp
├── slot_map_test.cpp
├── uninitialized_test.cpp
└── unstable_remove_test.cpp
SYMBOL INDEX (371 symbols across 17 files)
FILE: SG14/algorithm_ext.h
function namespace (line 8) | namespace stdext
FILE: SG14/flat_map.h
function namespace (line 39) | namespace stdext {
function explicit (line 258) | explicit iter(KeyIt&& kit, MappedIt&& vit)
type sorted_unique_t (line 274) | struct sorted_unique_t { explicit sorted_unique_t() = default; }
function sorted_unique_t (line 277) | inline
function class (line 323) | class value_compare {
type containers (line 336) | struct containers {
function flat_map (line 344) | flat_map(Compare()) {}
function explicit (line 414) | explicit flat_map(const Compare& comp)
function compare_ (line 415) | compare_(comp) {}
function iterator (line 540) | iterator begin() noexcept { return flatmap_detail::make_iterator(c_.keys...
function const_iterator (line 541) | const_iterator begin() const noexcept { return flatmap_detail::make_iter...
function const_iterator (line 543) | const_iterator end() const noexcept { return flatmap_detail::make_iterat...
function const_iterator (line 546) | const_iterator cend() const noexcept { return flatmap_detail::make_itera...
function const_reverse_iterator (line 549) | const_reverse_iterator rbegin() const noexcept { return const_reverse_it...
function const_reverse_iterator (line 551) | const_reverse_iterator rend() const noexcept { return const_reverse_iter...
function empty (line 557) | [[nodiscard]]
function size_type (line 561) | size_type max_size() const noexcept { return std::min<size_type>(c_.keys...
function mapped_reference (line 567) | mapped_reference operator[](Key&& x) {
function mapped_reference (line 571) | mapped_reference at(const Key& k) {
function const_mapped_reference (line 579) | const_mapped_reference at(const Key& k) const {
function iterator (line 617) | iterator insert(const_iterator position, const value_type& x) {
function iterator (line 621) | iterator insert(const_iterator position, value_type&& x) {
function insert (line 666) | void insert(std::initializer_list<value_type> il) {
function insert (line 670) | void insert(stdext::sorted_unique_t s, std::initializer_list<value_type>...
function containers (line 675) | containers extract() && {
function size_type (line 892) | size_type count(const Key& k) const {
function contains (line 902) | bool contains(const Key& k) const {
function iterator (line 912) | iterator lower_bound(const Key& k) {
function const_iterator (line 920) | const_iterator lower_bound(const Key& k) const {
function iterator (line 948) | iterator upper_bound(const Key& k) {
function const_iterator (line 956) | const_iterator upper_bound(const Key& k) const {
function kit1 (line 985) | auto kit1 = std::partition_point(c_.keys.begin(), c_.keys.end(), [&](con...
function kit2 (line 988) | auto kit2 = std::partition_point(kit1, c_.keys.end(), [&](const auto& el...
function kit1 (line 1000) | auto kit1 = std::partition_point(c_.keys.begin(), c_.keys.end(), [&](con...
function kit2 (line 1003) | auto kit2 = std::partition_point(kit1, c_.keys.end(), [&](const auto& el...
function kit1 (line 1017) | auto kit1 = std::partition_point(c_.keys.begin(), c_.keys.end(), [&](con...
function kit2 (line 1020) | auto kit2 = std::partition_point(kit1, c_.keys.end(), [&](const auto& el...
function kit2 (line 1037) | auto kit2 = std::partition_point(kit1, c_.keys.end(), [&](const auto& el...
FILE: SG14/flat_set.h
function namespace (line 39) | namespace stdext {
type sorted_unique_t (line 129) | struct sorted_unique_t { explicit sorted_unique_t() = default; }
function sorted_unique_t (line 132) | inline
function flat_set (line 165) | flat_set(Compare()) {}
function explicit (line 167) | explicit flat_set(KeyContainer ctr)
function explicit (line 246) | explicit flat_set(const Compare& comp)
function iterator (line 353) | iterator begin() noexcept { return c_.begin(); }
function iterator (line 355) | iterator end() noexcept { return c_.end(); }
function reverse_iterator (line 361) | reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
function const_reverse_iterator (line 362) | const_reverse_iterator rbegin() const noexcept { return const_reverse_it...
function const_reverse_iterator (line 364) | const_reverse_iterator rend() const noexcept { return const_reverse_iter...
function empty (line 370) | [[nodiscard]]
function iterator (line 415) | iterator insert(const_iterator, const Key& t) {
function iterator (line 420) | iterator insert(const_iterator, Key&& t) {
function insert (line 448) | void insert(std::initializer_list<Key> il) {
function insert (line 452) | void insert(sorted_unique_t s, std::initializer_list<Key> il) {
function KeyContainer (line 456) | KeyContainer extract() && {
function replace (line 462) | void replace(KeyContainer&& ctr) {
function iterator (line 466) | iterator erase(iterator position) {
function iterator (line 470) | iterator erase(const_iterator position) {
function size_type (line 474) | size_type erase(const Key& t) {
function iterator (line 483) | iterator erase(const_iterator first, const_iterator last) {
function clear (line 497) | void clear() noexcept {
function iterator (line 504) | iterator find(const Key& t) {
function const_iterator (line 512) | const_iterator find(const Key& t) const {
function size_type (line 540) | size_type count(const Key& x) const {
function contains (line 550) | bool contains(const Key& x) const {
function iterator (line 560) | iterator lower_bound(const Key& t) {
function const_iterator (line 566) | const_iterator lower_bound(const Key& t) const {
function iterator (line 588) | iterator upper_bound(const Key& t) {
function const_iterator (line 594) | const_iterator upper_bound(const Key& t) const {
function lo (line 617) | auto lo = std::partition_point(this->begin(), this->end(), [&](const Key...
function hi (line 620) | auto hi = std::partition_point(lo, this->end(), [&](const Key& elt) {
function lo (line 627) | auto lo = std::partition_point(this->begin(), this->end(), [&](const Key...
function hi (line 630) | auto hi = std::partition_point(lo, this->end(), [&](const Key& elt) {
function lo (line 639) | auto lo = std::partition_point(this->begin(), this->end(), [&](const Key...
function hi (line 642) | auto hi = std::partition_point(lo, this->end(), [&](const Key& elt) {
function hi (line 654) | auto hi = std::partition_point(lo, this->end(), [&](const Key& elt) {
FILE: SG14/inplace_function.h
function namespace (line 37) | namespace stdext {
function true_type (line 142) | struct is_valid_inplace_dst : std::true_type
function false_type (line 157) | struct is_invocable_r_impl : std::false_type {}
function namespace (line 195) | namespace inplace_function_detail {
function vtable_t (line 245) | static const vtable_t vt{inplace_function_detail::wrapper<C>{}}
function C (line 248) | new (std::addressof(storage_)) C{std::forward<T>(closure)};
function vtable_ptr_ (line 271) | inplace_function(std::nullptr_t) noexcept :
function R (line 317) | R operator() (Args... args) const
function operator (line 325) | constexpr bool operator== (std::nullptr_t) const noexcept
function operator (line 330) | constexpr bool operator!= (std::nullptr_t) const noexcept
function swap (line 340) | void swap(inplace_function& other) noexcept
FILE: SG14/plf_colony.h
function namespace (line 243) | namespace plf
type typename (line 273) | typedef typename choose<priority == plf::performance, unsigned short, un...
type typename (line 280) | typedef typename std::aligned_storage<sizeof(element_type), (sizeof(elem...
type element_type (line 282) | typedef element_type aligned_element_type;
type typename (line 286) | typedef typename std::allocator_traits<allocator_type>::size_type siz...
type typename (line 287) | typedef typename std::allocator_traits<allocator_type>::difference_type ...
type element_type (line 288) | typedef element_type & reference;
type element_type (line 289) | typedef const element_type & const_reference;
type typename (line 290) | typedef typename std::allocator_traits<allocator_type>::pointer poin...
type typename (line 291) | typedef typename std::allocator_traits<allocator_type>::const_pointer c...
type typename (line 293) | typedef typename allocator_type::size_type size_type;
type typename (line 294) | typedef typename allocator_type::difference_type difference_type;
type typename (line 295) | typedef typename allocator_type::reference reference;
type typename (line 296) | typedef typename allocator_type::const_reference const_reference;
type typename (line 297) | typedef typename allocator_type::pointer pointer;
type typename (line 298) | typedef typename allocator_type::const_pointer const_pointer;
type colony_iterator (line 304) | typedef colony_iterator<false> iterator;
type colony_iterator (line 305) | typedef colony_iterator<true> const_iterator;
type colony_reverse_iterator (line 310) | typedef colony_reverse_iterator<false> reverse_iterator;
type colony_reverse_iterator (line 311) | typedef colony_reverse_iterator<true> const_reverse_iterator;
type alignas (line 320) | struct alignas
type aligned_allocation_struct (line 327) | struct aligned_allocation_struct
type group (line 337) | struct group
type item_index_tuple (line 338) | struct item_index_tuple
type typename (line 342) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 343) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 344) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 345) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 346) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 347) | typedef typename std::allocator_traits<allocator_type>::template
type typename (line 349) | typedef typename std::allocator_traits<aligned_element_allocator_type>::...
type typename (line 350) | typedef typename std::allocator_traits<group_allocator_type>::pointer ...
type typename (line 351) | typedef typename std::allocator_traits<skipfield_allocator_type>::pointe...
type typename (line 352) | typedef typename std::allocator_traits<aligned_struct_allocator_type>::p...
type typename (line 353) | typedef typename std::allocator_traits<tuple_allocator_type>::pointer ...
type typename (line 355) | typedef typename allocator_type::template rebind<aligned_element_type>::...
type typename (line 356) | typedef typename allocator_type::template rebind<group>::other gro...
type typename (line 357) | typedef typename allocator_type::template rebind<skipfield_type>::other ...
type typename (line 358) | typedef typename allocator_type::template rebind<char>::other alig...
type typename (line 359) | typedef typename allocator_type::template rebind<item_index_tuple>::othe...
type typename (line 360) | typedef typename allocator_type::template rebind<unsigned char>::other ...
type typename (line 362) | typedef typename aligned_element_allocator_type::pointer aligned_pointer...
type typename (line 363) | typedef typename group_allocator_type::pointer group_pointer_type;
type typename (line 364) | typedef typename skipfield_allocator_type::pointer skipfield_pointer_t...
type typename (line 365) | typedef typename aligned_struct_allocator_type::pointer aligned_struct_p...
type typename (line 366) | typedef typename tuple_allocator_type::pointer tuple_pointer_type;
function aligned_struct_allocator_type (line 372) | struct group : private aligned_struct_allocator_type // ebco - inherit a...
function PLF_FORCE_INLINE (line 1955) | inline PLF_FORCE_INLINE iterator begin() PLF_NOEXCEPT
function PLF_FORCE_INLINE (line 1962) | inline PLF_FORCE_INLINE const_iterator begin() const PLF_NOEXCEPT
function PLF_FORCE_INLINE (line 1976) | inline PLF_FORCE_INLINE const_iterator end() const PLF_NOEXCEPT
function PLF_FORCE_INLINE (line 1990) | inline PLF_FORCE_INLINE const_iterator cend() const PLF_NOEXCEPT
function reverse_iterator (line 2004) | inline reverse_iterator rend() PLF_NOEXCEPT
function const_reverse_iterator (line 2011) | inline const_reverse_iterator crbegin() const PLF_NOEXCEPT
function deallocate_group (line 2058) | inline void deallocate_group(group_pointer_type const the_group) PLF_NOE...
function destroy_all_data (line 2066) | void destroy_all_data() PLF_NOEXCEPT
function initialize (line 2115) | void initialize(const skipfield_type first_group_size)
function update_skipblock (line 2125) | void update_skipblock(const iterator &new_location, const skipfield_type...
function iterator (line 2251) | iterator new_location(groups_with_erasures_list_head, groups_with_erasur...
function iterator (line 2293) | iterator insert(element_type &&element) // The move-insert function is n...
FILE: SG14/ring.h
function namespace (line 8) | namespace sg14
FILE: SG14/slot_map.h
function namespace (line 38) | namespace stdext {
function set_index (line 72) | constexpr void set_index(Key& k, Integral value) { auto& [idx, gen] = k;...
function increment_generation (line 73) | static constexpr void increment_generation(Key& k) { auto& [idx, gen] = ...
function set_index (line 77) | constexpr void set_index(Key& k, Integral value) { using std::get; get<0...
function increment_generation (line 78) | static constexpr void increment_generation(Key& k) { using std::get; ++g...
function reference (line 116) | constexpr reference at(const key_type& key) {
function const_reference (line 123) | constexpr const_reference at(const key_type& key) const {
function reference (line 135) | constexpr reference operator[](const key_type& key) { retur...
function const_reference (line 136) | constexpr const_reference operator[](const key_type& key) const { retur...
function iterator (line 142) | constexpr iterator find(const key_type& key) {
function const_iterator (line 154) | constexpr const_iterator find(const key_type& key) const {
function iterator (line 170) | constexpr iterator find_unchecked(const key_type& key) {
function const_iterator (line 175) | constexpr const_iterator find_unchecked(const key_type& key) const {
function iterator (line 183) | constexpr iterator begin() { return values_.begi...
function iterator (line 184) | constexpr iterator end() { return values_.end(...
function reverse_iterator (line 189) | constexpr reverse_iterator rbegin() { return values_.rbeg...
function reverse_iterator (line 190) | constexpr reverse_iterator rend() { return values_.rend...
function reserve (line 206) | constexpr void reserve(size_type n) {
function reserve_slots (line 221) | constexpr void reserve_slots(size_type n) {
function key_type (line 241) | constexpr key_type insert(const mapped_type& value) { return this->emp...
function key_type (line 242) | constexpr key_type insert(mapped_type&& value) { return this->emp...
function iterator (line 269) | constexpr iterator erase(iterator pos) { return this->erase(const_iterat...
function iterator (line 270) | constexpr iterator erase(iterator first, iterator last) { return this->e...
function iterator (line 271) | constexpr iterator erase(const_iterator pos) {
function iterator (line 275) | constexpr iterator erase(const_iterator first, const_iterator last) {
function size_type (line 286) | constexpr size_type erase(const key_type& key) {
function clear (line 300) | constexpr void clear() {
function swap (line 310) | constexpr void swap(slot_map& rhs) {
function Container (line 323) | constexpr const Container<mapped_type>& c() const& noexcept { return val...
function Container (line 325) | constexpr const Container<mapped_type>&& c() const&& noexcept { return s...
function iterator (line 333) | constexpr iterator erase_slot_iter(slot_iterator slot_iter) {
function key_index_type (line 363) | key_index_type next_available_slot_index_{}
function key_index_type (line 364) | key_index_type last_available_slot_index_{}
FILE: SG14_test/SG14_test.h
function namespace (line 6) | namespace sg14_test
FILE: SG14_test/flat_map_test.cpp
type AmbiguousEraseWidget (line 15) | struct AmbiguousEraseWidget {
method AmbiguousEraseWidget (line 16) | explicit AmbiguousEraseWidget(const char *s) : s_(s) {}
method AmbiguousEraseWidget (line 19) | AmbiguousEraseWidget(T) : s_("notfound") {}
type InstrumentedWidget (line 29) | struct InstrumentedWidget {
method InstrumentedWidget (line 31) | InstrumentedWidget() = delete;
method InstrumentedWidget (line 32) | InstrumentedWidget(const char *s) : s_(s) {}
method InstrumentedWidget (line 33) | InstrumentedWidget(InstrumentedWidget&& o) noexcept : s_(std::move(o.s...
method InstrumentedWidget (line 34) | InstrumentedWidget(const InstrumentedWidget& o) : s_(o.s_) { copy_ctor...
method InstrumentedWidget (line 35) | InstrumentedWidget& operator=(InstrumentedWidget&& o) noexcept {
method InstrumentedWidget (line 40) | InstrumentedWidget& operator=(const InstrumentedWidget&) = default;
method str (line 45) | std::string str() const { return s_; }
function AmbiguousEraseTest (line 54) | static void AmbiguousEraseTest()
function ExtractDoesntSwapTest (line 69) | static void ExtractDoesntSwapTest()
function MoveOperationsPilferOwnership (line 93) | static void MoveOperationsPilferOwnership()
function SortedUniqueConstructionTest (line 130) | static void SortedUniqueConstructionTest()
function TryEmplaceTest (line 147) | static void TryEmplaceTest()
function VectorBoolSanityTest (line 205) | static void VectorBoolSanityTest()
function free_function_less (line 242) | static bool free_function_less(const int& a, const int& b) {
function flatmap_is_ctadable_from (line 247) | static auto flatmap_is_ctadable_from(int, Args&&... args)
function flatmap_is_ctadable_from (line 254) | static auto flatmap_is_ctadable_from(long, Args&&...)
function DeductionGuideTests (line 262) | static void DeductionGuideTests()
function ConstructionTest (line 498) | static void ConstructionTest()
function InsertOrAssignTest (line 566) | static void InsertOrAssignTest()
function SpecialMemberTest (line 603) | static void SpecialMemberTest()
function ComparisonOperatorsTest (line 621) | static void ComparisonOperatorsTest()
function SearchTest (line 659) | static void SearchTest()
function main (line 758) | int main()
FILE: SG14_test/flat_set_test.cpp
type AmbiguousEraseWidget (line 14) | struct AmbiguousEraseWidget {
method AmbiguousEraseWidget (line 22) | explicit AmbiguousEraseWidget(const char *s) : s_(s) {}
method AmbiguousEraseWidget (line 23) | AmbiguousEraseWidget(iterator) : s_("notfound") {}
method AmbiguousEraseWidget (line 24) | AmbiguousEraseWidget(const_iterator) : s_("notfound") {}
type InstrumentedWidget (line 30) | struct InstrumentedWidget {
method InstrumentedWidget (line 32) | InstrumentedWidget(const char *s) : s_(s) {}
method InstrumentedWidget (line 33) | InstrumentedWidget(InstrumentedWidget&& o) : s_(std::move(o.s_)) { mov...
method InstrumentedWidget (line 34) | InstrumentedWidget(const InstrumentedWidget& o) : s_(o.s_) { copy_ctor...
method InstrumentedWidget (line 35) | InstrumentedWidget& operator=(InstrumentedWidget&&) = default;
method InstrumentedWidget (line 36) | InstrumentedWidget& operator=(const InstrumentedWidget&) = default;
function AmbiguousEraseTest (line 48) | static void AmbiguousEraseTest()
function ExtractDoesntSwapTest (line 65) | static void ExtractDoesntSwapTest()
type ThrowingSwapException (line 89) | struct ThrowingSwapException {}
type ComparatorWithThrowingSwap (line 91) | struct ComparatorWithThrowingSwap {
method ComparatorWithThrowingSwap (line 94) | ComparatorWithThrowingSwap(std::function<bool(int, int)> f) : cmp_(f) {}
method swap (line 95) | void swap(ComparatorWithThrowingSwap& a, ComparatorWithThrowingSwap& b) {
function ThrowingSwapDoesntBreakInvariants (line 106) | static void ThrowingSwapDoesntBreakInvariants()
function VectorBoolSanityTest (line 129) | static void VectorBoolSanityTest()
type VectorBoolEvilComparator (line 152) | struct VectorBoolEvilComparator {
function VectorBoolEvilComparatorTest (line 163) | static void VectorBoolEvilComparatorTest()
function MoveOperationsPilferOwnership (line 176) | static void MoveOperationsPilferOwnership()
function ConstructionTest (line 214) | static void ConstructionTest()
function SpecialMemberTest (line 268) | static void SpecialMemberTest()
function main (line 330) | int main()
FILE: SG14_test/inplace_function_test.cpp
type Functor (line 18) | struct Functor {
method Functor (line 19) | Functor() {}
method Functor (line 20) | Functor(const Functor&) { copied += 1; }
method Functor (line 21) | Functor(Functor&&) noexcept { moved += 1; }
type ConstFunctor (line 25) | struct ConstFunctor {
method ConstFunctor (line 26) | ConstFunctor() {}
method ConstFunctor (line 27) | ConstFunctor(const ConstFunctor&) { copied += 1; }
method ConstFunctor (line 28) | ConstFunctor(ConstFunctor&&) noexcept { moved += 1; }
function Foo (line 32) | void Foo(int i)
function GlobalFunction (line 44) | static double GlobalFunction(const std::string& s, int i)
function FunctionPointer (line 51) | static void FunctionPointer()
function Lambda (line 68) | static void Lambda()
function Bind (line 83) | static void Bind()
type AnotherFunctor (line 98) | struct AnotherFunctor
method AnotherFunctor (line 105) | AnotherFunctor() { mConstructorCalls++; }
method AnotherFunctor (line 106) | AnotherFunctor(AnotherFunctor&&) noexcept { mConstructorCalls++; }
method AnotherFunctor (line 107) | AnotherFunctor(const AnotherFunctor&) { mConstructorCalls++; }
function FunctorDestruction (line 114) | static void FunctorDestruction()
function Swapping (line 143) | static void Swapping()
function Copying (line 169) | static void Copying()
function ContainingStdFunction (line 193) | static void ContainingStdFunction()
function SimilarTypeCopy (line 213) | static void SimilarTypeCopy()
function AssignmentDifferentFunctor (line 239) | static void AssignmentDifferentFunctor()
type ThrowingFunctor (line 264) | struct ThrowingFunctor {
method reset (line 269) | static void reset(int k) {
method check_countdown (line 275) | static void check_countdown() {
method ThrowingFunctor (line 280) | ThrowingFunctor() { check_countdown(); ++constructed; }
method ThrowingFunctor (line 281) | ThrowingFunctor(const ThrowingFunctor&) {
method ThrowingFunctor (line 285) | ThrowingFunctor(ThrowingFunctor&&) noexcept { ++constructed; }
method ThrowingFunctor (line 286) | ThrowingFunctor& operator=(const ThrowingFunctor&) = delete;
method ThrowingFunctor (line 287) | ThrowingFunctor& operator=(ThrowingFunctor&&) = delete;
function test_exception_safety (line 296) | static void test_exception_safety()
function expected_alignment_for_capacity (line 338) | constexpr size_t expected_alignment_for_capacity()
function test_struct_layout (line 349) | static void test_struct_layout()
function test_nullptr (line 360) | static void test_nullptr()
type oon_functor (line 380) | struct oon_functor {
method oon_functor (line 383) | oon_functor(int i) : dummy(i) {}
function test_overloaded_operator_new (line 392) | static void test_overloaded_operator_new()
function test_move_construction_is_noexcept (line 403) | static void test_move_construction_is_noexcept()
function test_move_construction_from_smaller_buffer_is_noexcept (line 415) | static void test_move_construction_from_smaller_buffer_is_noexcept()
type test_bug_32072_C (line 426) | struct test_bug_32072_C
type test_bug_32072 (line 427) | struct test_bug_32072 {
function RvalueRefParameter (line 433) | static void RvalueRefParameter()
function test_is_convertible (line 447) | static void test_is_convertible()
function test_convertibility_with_qualified_call_operators (line 455) | static void test_convertibility_with_qualified_call_operators()
function test_convertibility_with_lambdas (line 475) | static void test_convertibility_with_lambdas()
type InstrumentedCopyConstructor (line 519) | struct InstrumentedCopyConstructor {
method InstrumentedCopyConstructor (line 522) | InstrumentedCopyConstructor() = default;
method InstrumentedCopyConstructor (line 523) | InstrumentedCopyConstructor(const InstrumentedCopyConstructor&) {
method InstrumentedCopyConstructor (line 526) | InstrumentedCopyConstructor(InstrumentedCopyConstructor&&) noexcept {
function test_return_by_move (line 534) | static void test_return_by_move()
function test_is_invocable (line 560) | static void test_is_invocable()
function overloaded_function (line 600) | static int overloaded_function(stdext::inplace_function<int()>) { return...
function overloaded_function (line 601) | static int overloaded_function(stdext::inplace_function<int(int)>) { ret...
function test_overloading_on_arity (line 602) | static void test_overloading_on_arity()
function overloaded_function2 (line 608) | static int overloaded_function2(stdext::inplace_function<int(int)>) { re...
function overloaded_function2 (line 609) | static int overloaded_function2(stdext::inplace_function<int(int*)>) { r...
function test_overloading_on_parameter_type (line 610) | static void test_overloading_on_parameter_type()
function overloaded_function3 (line 616) | static int overloaded_function3(stdext::inplace_function<int(int)>) { re...
function overloaded_function3 (line 617) | static int overloaded_function3(stdext::inplace_function<int*(int)>) { r...
function test_overloading_on_return_type (line 618) | static void test_overloading_on_return_type()
function main (line 719) | int main()
FILE: SG14_test/main.cpp
function main (line 9) | int main(int, char *[])
FILE: SG14_test/plf_colony_test.cpp
function failpass (line 89) | inline void failpass(const char* test_type, bool condition)
function message (line 99) | void message(const char *)
function title1 (line 104) | void title1(const char*)
function title2 (line 108) | void title2(const char*)
type perfect_forwarding_test (line 115) | struct perfect_forwarding_test
method perfect_forwarding_test (line 119) | perfect_forwarding_test(int&& /*perfect1*/, int& perfect2)
method perfect_forwarding_test (line 126) | perfect_forwarding_test(T&& /*imperfect1*/, U&& /*imperfect2*/)
type small_struct (line 133) | struct small_struct
method small_struct (line 142) | small_struct(const int num) : number(num) {}
class non_copyable_type (line 147) | class non_copyable_type
method non_copyable_type (line 154) | non_copyable_type(int a) : i(a) {}
type small_struct_non_trivial (line 164) | struct small_struct_non_trivial
method small_struct_non_trivial (line 173) | small_struct_non_trivial(const int num) : number(num) {}
type plf (line 182) | namespace plf
function rand (line 189) | unsigned int rand()
type sg14_test (line 205) | namespace sg14_test
function plf_colony_test (line 208) | void plf_colony_test()
function main (line 2021) | int main()
FILE: SG14_test/ring_test.cpp
function basic_test (line 10) | static void basic_test()
function filter_test (line 52) | static void filter_test()
function iterator_regression_test (line 72) | static void iterator_regression_test()
function copy_popper_test (line 110) | static void copy_popper_test()
function reverse_iterator_test (line 123) | static void reverse_iterator_test()
function main (line 178) | int main()
FILE: SG14_test/slot_map_test.cpp
type TestKey (line 15) | namespace TestKey {
type key_16_8_t (line 16) | struct key_16_8_t {
type key_11_5_t (line 20) | struct key_11_5_t { // C++17 only
function get (line 26) | auto get(const K& k) { return get(k, std::integral_constant<int, I>{}); }
type TestContainer (line 36) | namespace TestContainer {
type Vector (line 39) | struct Vector {
method Vector (line 51) | Vector() = default;
method Vector (line 53) | Vector(const Vector& rhs) { *this = rhs; }
method Vector (line 54) | Vector(Vector&& rhs) { *this = std::move(rhs); }
method size (line 65) | unsigned size() const { return static_cast<unsigned int>(size_); }
method T (line 66) | T *begin() { return data_.get(); }
method T (line 67) | T *end() { return data_.get() + size_; }
method T (line 68) | const T *begin() const { return data_.get(); }
method T (line 69) | const T *end() const { return data_.get() + size_; }
method pop_back (line 70) | void pop_back() {
method emplace_back (line 77) | void emplace_back(U t) {
method clear (line 84) | void clear() {
method swap (line 88) | void swap(Vector& a, Vector& b) {
type Monad (line 100) | struct Monad {
method T (line 102) | static T value_of(const T& i) { return i; }
method T (line 103) | static T from_value(const U& v) { return static_cast<T>(v); }
type Monad<std::unique_ptr<T_>> (line 106) | struct Monad<std::unique_ptr<T_>> {
method T_ (line 108) | static T_ value_of(const T& ptr) { return *ptr; }
method T (line 109) | static T from_value(const U& v) { return std::make_unique<T_>(v); }
function print_slot_map (line 113) | void print_slot_map(const stdext::slot_map<T, Key, Container>& sm)
function U (line 133) | static U move_if_necessary(T& value) { return static_cast<U>(value); }
function BasicTests (line 136) | static void BasicTests(T t1, T t2)
function FullContainerStressTest (line 172) | static void FullContainerStressTest(TGen t)
function InsertEraseStressTest (line 198) | static void InsertEraseStressTest(TGen t)
function EraseInLoopTest (line 228) | static void EraseInLoopTest()
function EraseRangeTest (line 253) | static void EraseRangeTest()
function ReserveTest (line 290) | static void ReserveTest()
function VerifyCapacityExists (line 313) | static void VerifyCapacityExists(bool expected)
function VerifyCapacityExists (line 326) | void VerifyCapacityExists(Bool expected)
function TypedefTests (line 334) | static void TypedefTests()
function BoundsCheckingTest (line 431) | void BoundsCheckingTest()
function GenerationsDontSkipTest (line 449) | static void GenerationsDontSkipTest()
function IndexesAreUsedEvenlyTest (line 476) | static void IndexesAreUsedEvenlyTest()
function main (line 655) | int main()
FILE: SG14_test/uninitialized_test.cpp
type lifetest (line 13) | struct lifetest
method lifetest (line 18) | lifetest()
method lifetest (line 22) | lifetest(lifetest&& /*in*/) noexcept
method reset (line 30) | static void reset()
method test (line 39) | static void test(uint64_t, uint64_t, uint64_t)
method test (line 44) | static void test(uint64_t inconstruct, uint64_t indestruct, uint64_t i...
function value (line 57) | void value()
function def (line 77) | void def()
function main (line 108) | int main()
FILE: SG14_test/unstable_remove_test.cpp
type foo (line 10) | struct foo
method foo (line 13) | static foo make()
function main (line 94) | int main()
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (865K chars).
[
{
"path": ".gitignore",
"chars": 1108,
"preview": "# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$"
},
{
"path": ".travis.yml",
"chars": 3067,
"preview": "language: cpp\n\nbefore_install:\n - if [[ \"$TRAVIS_OS_NAME\" == \"osx\" ]]; then\n brew update;\n brew upgrade cmake"
},
{
"path": "CMakeLists.txt",
"chars": 3618,
"preview": "cmake_minimum_required(VERSION 3.10)\nproject(sg14 CXX)\n\nfind_package(Threads REQUIRED)\n\n# Prefer C++17, downgrade if it "
},
{
"path": "Docs/Proposals/D0447R16 - Introduction of hive to the Standard Library.html",
"chars": 137902,
"preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\r\n<html>\r\n<head>\r\n <meta name=\"viewport\"\r\n content=\"widt"
},
{
"path": "Docs/Proposals/Fixed_Point_Library_Proposal.md",
"chars": 41396,
"preview": "**Document number**: LEWG, EWG, SG14, SG6: P0037R0 \n**Date**: 2015-09-28 \n**Project**: Programming Language C++, Libra"
},
{
"path": "Docs/Proposals/p0037.html",
"chars": 122725,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <title>Fixed_Point_Library_Proposal</title>\n <"
},
{
"path": "Docs/Proposals/rawstorage.html",
"chars": 3191,
"preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<HTML><HEAD><TITLE>Extending raw_storage_iterator</TITLE>"
},
{
"path": "Docs/Proposals/ring_proposal_r5.tex",
"chars": 16232,
"preview": "Document number: P0059R4\nDate: 2017-05-15\nReply-to: Guy Davidson, guy@hatcat.com\nReply-to: Arthur O’Dwyer, arthur.j.odwy"
},
{
"path": "Docs/Proposals/uninitialized.html",
"chars": 5262,
"preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<HTML><HEAD><TITLE>Extending memory management tools</TIT"
},
{
"path": "Docs/Proposals/unstable_remove.html",
"chars": 4706,
"preview": "LEWG, SG14: D0041R0<br>\n11-9-2015<br>\nBrent Friedman<br>\nfourthgeek@gmail.com\n<h1>Unstable remove algorithms</h1>\n<h2>I."
},
{
"path": "Docs/fixed_point.md",
"chars": 582,
"preview": "# Fixed-Point Real Numbers\n\n[John McFarlane](https://groups.google.com/a/isocpp.org/forum/#!profile/sg14/APn2wQdoie4ys78"
},
{
"path": "Docs/plf_licensing.txt",
"chars": 909,
"preview": "All plf:: modules are provided under a zlib license:\n\nThis software is provided 'as-is', without any express or implied\n"
},
{
"path": "README.md",
"chars": 876,
"preview": "# SG14\n[](https://travis-ci.org/WG21-SG14/SG14)\n\n"
},
{
"path": "SG14/algorithm_ext.h",
"chars": 3627,
"preview": "#pragma once\n\n#include <algorithm>\n#include <iterator>\n#include <memory>\n#include <utility>\n\nnamespace stdext\n{\n\ttemplat"
},
{
"path": "SG14/flat_map.h",
"chars": 56815,
"preview": "/*\n * Boost Software License - Version 1.0 - August 17th, 2003\n *\n * Permission is hereby granted, free of charge, to an"
},
{
"path": "SG14/flat_set.h",
"chars": 30316,
"preview": "/*\n * Boost Software License - Version 1.0 - August 17th, 2003\n *\n * Permission is hereby granted, free of charge, to an"
},
{
"path": "SG14/inplace_function.h",
"chars": 12289,
"preview": "/*\n * Boost Software License - Version 1.0 - August 17th, 2003\n *\n * Permission is hereby granted, free of charge, to an"
},
{
"path": "SG14/plf_colony.h",
"chars": 180417,
"preview": "// Copyright (c) 2021, Matthew Bentley (mattreecebentley@gmail.com) www.plflib.org\r\n\r\n// zLib license (https://www.zlib."
},
{
"path": "SG14/ring.h",
"chars": 15431,
"preview": "#pragma once\n\n#include <cstddef>\n#include <type_traits>\n#include <iterator>\n#include <cassert>\n\nnamespace sg14\n{\n\ttempla"
},
{
"path": "SG14/slot_map.h",
"chars": 17225,
"preview": "/*\n * Boost Software License - Version 1.0 - August 17th, 2003\n *\n * Permission is hereby granted, free of charge, to an"
},
{
"path": "SG14_test/SG14_test.h",
"chars": 349,
"preview": "#if !defined SG14_TEST_2015_06_11_18_24\n#define SG14_TEST_2015_06_11_18_24\n\n#undef NDEBUG\n\nnamespace sg14_test\n{\n voi"
},
{
"path": "SG14_test/flat_map_test.cpp",
"chars": 29993,
"preview": "#include \"SG14_test.h\"\n#include \"flat_map.h\"\n#include <assert.h>\n#include <deque>\n#include <functional>\n#include <list>\n"
},
{
"path": "SG14_test/flat_set_test.cpp",
"chars": 10770,
"preview": "#include \"SG14_test.h\"\n#include \"flat_set.h\"\n#include <assert.h>\n#include <deque>\n#include <functional>\n#if __has_includ"
},
{
"path": "SG14_test/inplace_function_test.cpp",
"chars": 26720,
"preview": "#include \"SG14_test.h\"\n#include \"inplace_function.h\"\n#include <cassert>\n#include <memory>\n#include <string>\n#include <ty"
},
{
"path": "SG14_test/main.cpp",
"chars": 444,
"preview": "#if defined(_MSC_VER)\n#include <SDKDDKVer.h>\n#endif\n\n#include <stdio.h>\n\n#include \"SG14_test.h\"\n\nint main(int, char *[])"
},
{
"path": "SG14_test/plf_colony_test.cpp",
"chars": 50457,
"preview": "#define PLF_COLONY_TEST_DEBUG\r\n\r\n#if defined(_MSC_VER)\r\n\t#if _MSC_VER >= 1600\r\n\t\t#define PLF_TEST_MOVE_SEMANTICS_SUPPORT"
},
{
"path": "SG14_test/ring_test.cpp",
"chars": 5538,
"preview": "#include \"SG14_test.h\"\n\n#include \"ring.h\"\n\n#include <array>\n#include <numeric>\n#include <string>\n#include <vector>\n\nstat"
},
{
"path": "SG14_test/slot_map_test.cpp",
"chars": 27001,
"preview": "#include \"SG14_test.h\"\n#include \"slot_map.h\"\n#include <assert.h>\n#include <inttypes.h>\n#include <algorithm>\n#include <de"
},
{
"path": "SG14_test/uninitialized_test.cpp",
"chars": 2051,
"preview": "#include \"SG14_test.h\"\n#include <vector>\n#include <array>\n#include <ctime>\n#include <iostream>\n#include <algorithm>\n#inc"
},
{
"path": "SG14_test/unstable_remove_test.cpp",
"chars": 2465,
"preview": "#include \"SG14_test.h\"\n#include \"algorithm_ext.h\"\n#include <algorithm>\n#include <array>\n#include <chrono>\n#include <iost"
}
]
About this extraction
This page contains the full source code of the WG21-SG14/SG14 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (794.4 KB), approximately 205.9k tokens, and a symbol index with 371 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.