Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# https://releases.llvm.org/15.0.0/tools/clang/docs/ClangFormatStyleOptions.html

AlignConsecutiveMacros: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowShortLambdasOnASingleLine: Empty
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^.*(precomp|pch|stdafx)'
Priority: -1
- Regex: '^".*"'
Priority: 1
- Regex: '^<.*>'
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseBlocks : true
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 4
IndentWrappedFunctionNames: true
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 4
UseTab: Never
...
7 changes: 3 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,10 @@ add_library(frozen::frozen-headers ALIAS frozen-headers)
target_link_libraries(frozen-headers INTERFACE frozen::frozen)

#
# frozen requires C++ 14 support, at a minimum. Setting the `cxx_std_14` compile
# features ensures that the corresponding C++ standard flag is populated in
# targets linking to frozen
# frozen requires C++23. Setting the `cxx_std_23` compile feature ensures that
# the corresponding C++ standard flag is populated in all targets linking to frozen.
#
target_compile_features(frozen INTERFACE cxx_std_14)
target_compile_features(frozen INTERFACE cxx_std_23)

#
# The include directory for frozen can be expected to vary between build
Expand Down
28 changes: 28 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"configurePresets": [
{
"name": "ninja-msvc",
"displayName": "Ninja Multi-Config",
"description": "Default build using Ninja Multi-Config generator",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/build"
},
{
"name": "ninja-clang",
"displayName": "Ninja Multi-Config (Clang)",
"description": "Build using Clang toolchain with Ninja Multi-Config generator",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_RC_COMPILER": "llvm-rc"
}
}
]
}
64 changes: 57 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,30 @@ template<std::size_t N>
std::enable_if_t< frozen::set<int, 3>{{1,11,111}}.count(N), int> foo();
```

String support is built-in:
String support is built-in via `std::string_view`:

```cpp
#include <frozen/unordered_map.h>
#include <frozen/string.h>
#include <frozen/string.h> // provides frozen::string (= std::string_view)
#include <frozen/bits/elsa_std.h> // hash specialization for std::string_view

constexpr frozen::unordered_map<frozen::string, int, 2> olaf = {
constexpr frozen::unordered_map<std::string_view, int, 2> olaf = {
{"19", 19},
{"31", 31},
};
constexpr auto val = olaf.at("19");
```

`frozen::string` is a type alias for `std::string_view`. You can use either interchangeably. Include `<frozen/bits/elsa_std.h>` when using `std::string_view` or `std::string` as container keys — it provides the hash specialization.

The associative containers have different functionality with and without `constexpr`. With `constexpr`, frozen maps have immutable keys and values. Without `constexpr`, the values can be updated in runtime (the keys, however, remain immutable):

```cpp
#include <frozen/unordered_map.h>
#include <frozen/string.h>
#include <frozen/bits/elsa_std.h>

static constinit frozen::unordered_map<frozen::string, frozen::string, 2> voice = {
static constinit frozen::unordered_map<std::string_view, std::string_view, 2> voice = {
{"Anna", "???"},
{"Elsa", "???"}
};
Expand All @@ -113,7 +117,33 @@ For compatibility with STL's API, Frozen may eventually throw exceptions, as in

## Extending

Just like the regular C++23 container, you can specialize the hash function, the key equality comparator for `unordered_*` containers, and the comparison functions for the ordered version.
Just like the regular C++23 containers, you can specialize the hash function, the key equality comparator for `unordered_*` containers, and the comparison functions for the ordered version.

### Heterogeneous Lookup (Default)

All frozen containers now use transparent comparators by default (`std::less<>` for ordered containers, `std::equal_to<>` for unordered containers). This means you can look up keys with any type that is comparable to the key type, without explicit conversion:

```cpp
#include <frozen/map.h>
#include <frozen/bits/elsa_std.h>

constexpr frozen::map<std::string_view, int, 2> m = {
{"hello", 1}, {"world", 2}
};

// All of these work — no std::string_view construction needed:
std::string key = "hello";
m.count(key); // lookup with std::string
m.count("hello"); // lookup with const char*
m.at(std::string_view{"hello"}); // lookup with string_view
```

For ordered containers with numeric keys, you can also look up with implicitly convertible types:

```cpp
constexpr frozen::set<long, 3> s = {10L, 20L, 30L};
static_assert(s.count(20) == 1); // int lookup in long set
```

It's also possible to specialize the `frozen::elsa` structure used for hashing. Note that unlike `std::hash`, the hasher also takes a seed in addition to the value being hashed.

Expand All @@ -124,6 +154,8 @@ template <class T> struct elsa {
};
```

Hash specializations for `std::string_view` and `std::string` are provided in `<frozen/bits/elsa_std.h>`. Include this header when using standard string types as container keys.

Ideally, the hash function should have nice statistical properties like *pairwise-independence*:

If `x` and `y` are different values, the chance that `elsa<T>{}(x, seed) == elsa<T>{}(y, seed)` should be very low for a random value of `seed`.
Expand All @@ -143,10 +175,10 @@ Then either you've got a very big container and you should increase Clang's thre

```cpp
struct olaf {
constexpr std::size_t operator()(frozen::string const &value, std::size_t seed) const { return seed ^ value[0];}
constexpr std::size_t operator()(std::string_view value, std::size_t seed) const { return seed ^ value[0];}
};

constexpr frozen::unordered_set<frozen::string, 2, olaf/*custom hash*/> hans = { "a", "b" };
constexpr frozen::unordered_set<std::string_view, 2, olaf/*custom hash*/> hans = { "a", "b" };
```

## Tests and Benchmarks
Expand Down Expand Up @@ -197,6 +229,24 @@ cmake --build . --target benchmark

## Credits

## Migration from v1.x

This version requires **C++23** and includes the following breaking changes:

| v1.x | v2.x (this version) |
|---|---|
| `frozen::string` (custom type) | `frozen::string` = `std::string_view` (alias) |
| Explicit `std::less<void>` for transparent lookup | `std::less<>` is now the default comparator |
| Explicit `std::equal_to<void>` for transparent lookup | `std::equal_to<>` is now the default equality |
| `#include <frozen/string.h>` for string keys | Also need `#include <frozen/bits/elsa_std.h>` for hash |
| C++14/17 with feature-detection macros | C++23 required, no feature-detection macros |
| `FROZEN_LETITGO_HAS_DEDUCTION_GUIDES` | Deduction guides are always available |
| `FROZEN_LETITGO_HAS_STRING_VIEW` | `std::string_view` is always available |
| `FROZEN_LETITGO_HAS_CHAR8T` | `char8_t` is always available |
| `FROZEN_LETITGO_HAS_CONSTEVAL` | `consteval` is always available |

## Credits

Thanks to Serge sans Paille for creating the original [frozen](https://github.com/serge-sans-paille/frozen) library.

In the original frozen library, thanks to Jérôme Dumesnil for his high-quality reviews, and to Chris Beck for his contributions on perfect hashing.
Expand Down
49 changes: 13 additions & 36 deletions cmake/detect_cxx_version.cmake
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
set(cxx17_minimum_msvc_version 19.14)
set(cxx17_minimum_gcc_version 7.0)
set(cxx17_minimum_clang_version 4.0)
set(cxx17_minimum_appleclang_version 6.0)
set(cxx23_minimum_msvc_version 19.38)
set(cxx23_minimum_gcc_version 13.0)
set(cxx23_minimum_clang_version 16.0)
set(cxx23_minimum_appleclang_version 15.0)

set(cxx20_minimum_msvc_version 19.22)
set(cxx20_minimum_gcc_version 9.0)
set(cxx20_minimum_clang_version 7.0)
set(cxx20_minimum_appleclang_version 10.0)
set(cxx_23_supported OFF)

set(cxx_17_supported OFF)

if(MSVC AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx17_minimum_msvc_version})
set(cxx_17_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx17_minimum_gcc_version})
set(cxx_17_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx17_minimum_clang_version})
set(cxx_17_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx17_minimum_appleclang_version})
set(cxx_17_supported ON)
endif()

set(cxx_20_supported OFF)

if(MSVC AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx20_minimum_msvc_version})
set(cxx_20_supported ON)
if(MSVC AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx23_minimum_msvc_version})
set(cxx_23_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx20_minimum_gcc_version})
set(cxx_20_supported ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx23_minimum_gcc_version})
set(cxx_23_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx20_minimum_clang_version})
set(cxx_20_supported ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx23_minimum_clang_version})
set(cxx_23_supported ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx20_minimum_appleclang_version})
set(cxx_20_supported ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${cxx23_minimum_appleclang_version})
set(cxx_23_supported ON)
endif()
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set(frozen.examples.srcs enum_to_string enum_to_string_hash pixel_art static_assert value_modification)
set(frozen.examples.srcs enum_to_string enum_to_string_hash pixel_art static_assert value_modification string_view_lookup constinit_config)

if ("${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang")
list(APPEND frozen.examples.srcs html_entities_map)
Expand Down
47 changes: 47 additions & 0 deletions examples/constinit_config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// @file constinit_config.cpp
/// Demonstrates a constinit frozen::unordered_map as a configuration table
/// with compile-time-selected keys and mutable runtime values.

#include <frozen/bits/elsa_std.h>
#include <frozen/string.h>
#include <frozen/unordered_map.h>

#include <iostream>
#include <string_view>

// The keys are fixed at compile time (perfect hash, zero collision).
// Values start with defaults and can be mutated at runtime.
// constinit ensures the map is fully constructed during static initialization
// with no runtime overhead and no static-init-order fiasco.
constinit frozen::unordered_map<std::string_view, int, 4> config = {
{ "max_connections", 100 },
{ "port", 8080 },
{ "timeout_ms", 5000 },
{ "retries", 3 },
};

void print_config()
{
std::cout << "Current configuration:\n";
for (const auto& [key, value]: config)
{
std::cout << " " << key << " = " << value << "\n";
}
}

int main()
{
print_config();

// Update values at runtime — keys remain immutable
config.at("port") = 443;
config.at("timeout_ms") = 30000;
config.at("retries") = 5;

std::cout << "\nAfter update:\n";
print_config();

// Lookup with const char* (heterogeneous, transparent equal_to<>)
const char* key = "max_connections";
std::cout << "\n" << key << " = " << config.at(key) << "\n";
}
Loading
Loading