Awesome
C++ Fixed Containers
Header-only C++20 library that provides containers with the following properties:
- Fixed-capacity, declared at compile-time, no dynamic allocations
- constexpr - can be used at both compile-time and runtime (including mutation)
- containers retain the properties of T (e.g. if T is trivially copyable, then so is FixedVector<T>)
- no pointers stored (data layout is purely self-referential and can be serialized directly)
- instances can be used as non-type template parameters
Features
-
The following table shows the available fixed containers and their equivalent std-containers. The fixed-container types have identical APIs to their std:: equivalents, so you can refer to the traditional C++ docs for how to use them.
fixed-container std-container equivalent FixedVector
std::vector
FixedDeque
std::deque
FixedList
std::list
FixedQueue
std::queue
FixedStack
std::stack
FixedCircularDeque
std::deque
API with Circular Buffer semanticsFixedCircularQueue
std::queue
API with Circular Buffer semanticsFixedString
std::string
FixedMap
std::map
FixedSet
std::set
FixedUnorderedMap
std::unordered_map
FixedUnorderedSet
std::unordered_set
EnumMap
std::map
for enum keys onlyEnumSet
std::set
for enum keys onlyEnumArray
std::array
but with typed accessors -
StringLiteral
- Compile-time null-terminated literal string. -
Rich enums -
enum
&class
hybrid.
Rich enum features
- Rich enums behave like an enum (compile-time known values, can be used in switch-statements, template parameters as well as
EnumMap
/EnumSet
etc). - Can have member functions and fields.
- Readily available
count()
,to_string()
. - Conversion from string, ordinal.
- Implicit
std::optional
-like semantics. - Avoid the need for error-prone sentinel values like
UNKNOWN
,UNINITIALIZED
,COUNT
etc. - Avoid Undefined Behavior from uninitialized state. Default constructor can be disabled altogether.
EnumAdapter<T>
can adapt any enum-like class to the rich enum API.
static_assert(fixed_containers::rich_enums::is_rich_enum<Color>); // Type-trait `concept`
inline constexpr Color COLOR = Color::RED(); // Note the parens
static_assert("RED" == COLOR.to_string()); // auto-provided member
static_assert(COLOR.is_primary()); // Custom member
static_assert(COLOR == Color::value_of("RED").value()); // auto-provided
static_assert(4 == Color::count()); // auto-provided
More examples can be found here.
Examples
-
FixedVector
constexpr auto v1 = []() { FixedVector<int, 11> v{}; v.push_back(0); v.emplace_back(1); v.push_back(2); return v; }(); static_assert(v1[0] == 0); static_assert(v1[1] == 1); static_assert(v1[2] == 2); static_assert(v1.size() == 3); static_assert(v1.capacity() == 11);
-
FixedList
constexpr auto v1 = []() { FixedList<int, 11> v{}; v.push_back(0); v.emplace_back(1); v.push_front(2); return v; }(); static_assert(*v1.begin() == 2); static_assert(v1.size() == 3); static_assert(v1.max_size() == 11);
-
FixedDeque
constexpr auto v1 = []() { FixedDeque<int, 11> v{}; v.push_back(0); v.emplace_back(1); v.push_front(2); return v; }(); static_assert(v1[0] == 2); static_assert(v1[1] == 0); static_assert(v1[2] == 1); static_assert(v1.size() == 3); static_assert(v1.max_size() == 11);
-
FixedQueue
constexpr auto s1 = []() { FixedQueue<int, 3> v1{}; v1.push(77); v1.push(88); v1.push(99); return v1; }(); static_assert(s1.front() == 77); static_assert(s1.back() == 99); static_assert(s1.size() == 3);
-
FixedStack
constexpr auto s1 = []() { FixedStack<int, 3> v1{}; int my_int = 77; v1.push(my_int); v1.push(99); return v1; }(); static_assert(s1.top() == 99); static_assert(s1.size() == 2);
-
FixedCircularDeque
constexpr auto v1 = []() { FixedCircularDeque<int, 3> v{}; v.push_back(2); v.emplace_back(3); v.push_front(1); v.emplace_front(0); v.push_back(4); return v; }(); static_assert(v1[0] == 1); static_assert(v1[1] == 2); static_assert(v1[2] == 4); static_assert(v1.size() == 3);
-
FixedCircularQueue
constexpr auto s1 = []() { FixedCircularQueue<int, 3> v1{}; v1.push(55); v1.push(66); v1.push(77); v1.push(88); v1.push(99); return v1; }(); static_assert(s1.front() == 77); static_assert(s1.back() == 99); static_assert(s1.size() == 3);
-
FixedString
constexpr auto v1 = []() { FixedString<11> v{"012"}; v.at(1) = 'b'; return v; }(); static_assert(v1.at(0) == '0'); static_assert(v1.at(1) == 'b'); static_assert(v1.at(2) == '2'); static_assert(v1.size() == 3);
-
FixedMap
constexpr auto m1 = []() { FixedMap<int, int, 11> m{}; m.insert({2, 20}); m[4] = 40; return m; }(); static_assert(!m1.contains(1)); static_assert(m1.contains(2)); static_assert(m1.at(4) == 40); static_assert(m1.size() == 2); static_assert(m1.max_size() == 11);
-
FixedSet
constexpr auto s1 = []() { FixedSet<int, 11> s{}; s.insert(2); s.insert(4); return s; }(); static_assert(!s1.contains(1)); static_assert(s1.contains(2)); static_assert(s1.size() == 2); static_assert(s1.max_size() == 11);
-
FixedUnorderedMap
constexpr auto m1 = []() { FixedUnorderedMap<int, int, 11> m{}; m.insert({2, 20}); m[4] = 40; return m; }(); static_assert(!m1.contains(1)); static_assert(m1.contains(2)); static_assert(m1.at(4) == 40); static_assert(m1.size() == 2); static_assert(m1.max_size() == 11);
-
FixedUnorderedSet
constexpr auto s1 = []() { FixedUnorderedSet<int, 11> s{}; s.insert(2); s.insert(4); return s; }(); static_assert(!s1.contains(1)); static_assert(s1.contains(2)); static_assert(s1.size() == 2); static_assert(s1.max_size() == 11);
-
EnumMap
enum class Color { RED, YELLOW, BLUE}; constexpr auto m1 = []() { EnumMap<Color, int> m{}; m.insert({Color::RED, 20}); m[Color::YELLOW] = 40; return m; }(); static_assert(!m1.contains(Color::BLUE)); static_assert(m1.contains(Color::RED)); static_assert(m1.at(Color::YELLOW) == 40); static_assert(m1.size() == 2); // Ensures all keys are specified, at compile-time constexpr auto m2 = EnumMap<Color, int>::create_with_all_entries({ {Color::RED, 42}, {Color::YELLOW, 7}, {Color::BLUE, 6}, });
-
EnumSet
enum class Color { RED, YELLOW, BLUE}; constexpr auto s1 = []() { EnumSet<Color> s{}; s.insert(Color::RED); s.insert(Color::YELLOW); return s; }(); static_assert(!s1.contains(Color::BLUE)); static_assert(s1.contains(Color::RED)); static_assert(s1.size() == 2); constexpr auto s2 = EnumSet<Color>::all(); // full set constexpr auto s3 = EnumSet<Color>::none(); // empty set constexpr auto s4 = EnumSet<Color>::complement_of(s2); // empty set
-
EnumArray
constexpr EnumArray<TestEnum1, int> s1{{TestEnum1::ONE, 10}, {TestEnum1::FOUR, 40}}; static_assert(4 == s1.max_size()); static_assert(s1.at(TestEnum1::ONE) == 10); static_assert(s1.at(TestEnum1::TWO) == 0); static_assert(s1.at(TestEnum1::THREE) == 0); static_assert(s1.at(TestEnum1::FOUR) == 40);
-
StringLiteral
static constexpr const char* s = "blah"; // strlen==4, sizeof==8 static constexpr const char s[5] = "blah"; // strlen==4, sizeof==5 (null terminator) static constexpr StringLiteral s = "blah"; // constexpr .size()==4
-
Using instances as non-type template parameters
// Similarly to simple types like ints/enums and std::array, // fixed_container instances can be used as template parameters template <FixedVector<int, 5> /*MY_VEC*/> constexpr void fixed_vector_instance_can_be_used_as_a_template_parameter() { } void test() { static constexpr FixedVector<int, 5> VEC1{}; fixed_vector_instance_can_be_used_as_a_template_parameter<VEC1>(); }
Integration
cmake
find_package(fixed_containers CONFIG REQUIRED)
target_link_libraries(<your_binary> fixed_containers::fixed_containers)
bazel
If you are managing dependencies with the newer bzlmod system, use the following in your MODULE.bazel
file:
bazel_dep(name = "fixed_containers")
archive_override(
module_name = "fixed_containers",
strip_prefix = "fixed-containers-<commit>",
urls = ["https://github.com/teslamotors/fixed-containers/archive/<commit>.tar.gz"],
)
If you are managing dependencies with the older WORKSPACE
system, use the following in your WORKSPACE
file:
http_archive(
name = "fixed_containers",
urls = ["https://github.com/teslamotors/fixed-containers/archive/<commit>.tar.gz"],
strip_prefix = "fixed-containers-<commit>",
)
load("@fixed_containers//:fixed_containers_deps.bzl", "fixed_containers_deps")
fixed_containers_deps()
Then use in your targets like this:
cc_test(
name = "test",
srcs = ["test.cpp"],
deps = [
"@fixed_containers//:fixed_vector",
"@fixed_containers//:enum_map",
"@fixed_containers//:enum_set",
],
copts = ["-std=c++20"],
)
Alternative
Since this is a header-only library, you can also:
- Add the
include/
folder to your includes - Get the dependencies. For example, with vcpkg:
vcpkg install magic-enum
Running the tests
cmake
- Build with the vcpkg toolchain file
mkdir build && cd build
cmake .. -DCMAKE_C_COMPILER=/bin/clang-13 -DCMAKE_CXX_COMPILER=/bin/clang++-13 -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build .
- Run tests
ctest -C Debug
bazel
clang
- Build separately (optional)
CC=clang++-13 bazel build --config=clang ...
- Run tests
CC=clang++-13 bazel test --config=clang :all_tests
gcc
- Build separately (optional)
CC=g++-11 bazel build --config=gcc ...
- Run tests
CC=g++-11 bazel test --config=gcc :all_tests
Tested Compilers
- Clang 13
- GCC 11
- MSVC++ 14.29 / Visual Studio 2019