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)
- no pointers stored (data layout is purely self-referential and can be serialized directly)
- instances can be used as non-type template parameters
FixedVector
- Vector implementation withstd::vector
API and "fixed container" propertiesFixedMap
/FixedSet
- Red-Black Tree map/set implementation withstd::map
/std::set
API and "fixed container" properties.EnumMap
/EnumSet
- For enum keys only, Map/Set implementation withstd::map
/std::set
API and "fixed container" properties. O(1) lookups.FixedDeque
- Deque implementation withstd::deque
API and "fixed container" propertiesFixedQueue
- Queue implementation withstd::queue
API and "fixed container" propertiesFixedStack
- Stack implementation withstd::stack
API and "fixed container" propertiesFixedCircularDeque
- Circular buffer implementation withstd::deque
API and "fixed container" propertiesFixedCircularQueue
- Circular buffer implementation withstd::queue
API and "fixed container" propertiesFixedString
- String implementation withstd::string
API and "fixed container" propertiesStringLiteral
- Compile-time null-terminated literal string.EnumArray
- For enum keys only, similar tostd::array
but with typed accessors and "fixed container" properties- Rich enums -
enum
&class
hybrid.
- 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 const 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.
-
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);
-
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.capacity() == 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.capacity() == 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::BLUE, 42}, {Color::YELLOW, 7}, {Color::BLUE, 42}, });
-
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
-
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.capacity() == 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);
-
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
-
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);
-
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>(); }
find_package(fixed_containers CONFIG REQUIRED)
target_link_libraries(<your_binary> fixed_containers::fixed_containers)
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"],
)
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 range-v3
- 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
- Build separately (optional)
CC=clang++-13 bazel build --config=clang ...
- Run tests
CC=clang++-13 bazel test --config=clang :all_tests
- Build separately (optional)
CC=g++-11 bazel build ...
- Run tests
CC=g++-11 bazel test :all_tests
- Clang 13
- GCC 11
- MSVC++ 14.29 / Visual Studio 2019