diff --git a/src/inc/til/static_map.h b/src/inc/til/static_map.h new file mode 100644 index 00000000000..452e2dab36b --- /dev/null +++ b/src/inc/til/static_map.h @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +// static_map implements a very simple std::map-like type +// that is entirely (compile-time-)constant. +// There is no requirement that keys be sorted, as it will +// use constexpr std::sort during construction. + +namespace til // Terminal Implementation Library. Also: "Today I Learned" +{ + template, size_t N = 0> + class static_map + { + public: + using const_iterator = typename std::array, N>::const_iterator; + + template + constexpr explicit static_map(const Args&... args) : + _predicate{}, + _array{ { args... } } + { + static_assert(sizeof...(Args) == N); + const auto compareKeys = [&](const auto& p1, const auto& p2) { return _predicate(p1.first, p2.first); }; + std::sort(_array.begin(), _array.end(), compareKeys); // compile-time sorting! + } + + [[nodiscard]] constexpr const_iterator find(const K& key) const noexcept + { + const auto compareKey = [&](const auto& p) { return _predicate(p.first, key); }; + const auto iter{ std::partition_point(_array.begin(), _array.end(), compareKey) }; + + if (iter == _array.end() || _predicate(key, iter->first)) + { + return _array.end(); + } + + return iter; + } + + [[nodiscard]] constexpr const_iterator end() const noexcept + { + return _array.end(); + } + + [[nodiscard]] constexpr const V& at(const K& key) const + { + const auto iter{ find(key) }; + + if (iter == end()) + { + throw std::runtime_error("key not found"); + } + + return iter->second; + } + + [[nodiscard]] constexpr const V& operator[](const K& key) const + { + return at(key); + } + + private: + Compare _predicate; + std::array, N> _array; + }; + + // this is a deduction guide that ensures two things: + // 1. static_map's member types are all the same + // 2. static_map's fourth template argument (otherwise undeduced) is how many pairs it contains + template + static_map(First, Rest...)->static_map...>, typename First::first_type, void>, typename First::second_type, std::less, 1 + sizeof...(Rest)>; +} diff --git a/src/til/ut_til/StaticMapTests.cpp b/src/til/ut_til/StaticMapTests.cpp new file mode 100644 index 00000000000..3dade824a32 --- /dev/null +++ b/src/til/ut_til/StaticMapTests.cpp @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +using namespace std::string_view_literals; + +class StaticMapTests +{ + TEST_CLASS(StaticMapTests); + + TEST_METHOD(Basic) + { + til::static_map intIntMap{ + std::pair{ 1, 100 }, + std::pair{ 3, 300 }, + std::pair{ 5, 500 }, + }; + + VERIFY_ARE_EQUAL(100, intIntMap.at(1)); + VERIFY_ARE_EQUAL(300, intIntMap.at(3)); + VERIFY_ARE_EQUAL(500, intIntMap.at(5)); + + int unused{}; + VERIFY_THROWS(unused = intIntMap.at(0), std::runtime_error); + VERIFY_THROWS(unused = intIntMap.at(7), std::runtime_error); + } + + TEST_METHOD(Unsorted) + { + til::static_map intIntMap{ + std::pair{ 5, 500 }, + std::pair{ 3, 300 }, + std::pair{ 1, 100 }, + }; + + VERIFY_ARE_EQUAL(100, intIntMap.at(1)); + VERIFY_ARE_EQUAL(300, intIntMap.at(3)); + VERIFY_ARE_EQUAL(500, intIntMap.at(5)); + + int unused{}; + VERIFY_THROWS(unused = intIntMap.at(0), std::runtime_error); + VERIFY_THROWS(unused = intIntMap.at(7), std::runtime_error); + } + + TEST_METHOD(StringViewKeys) + { + // We have to use the string view literal type here, as leaving the strings unviewed + // will result in a static_map + // Deduction guides are only applied when *no* template arguments are specified, + // which means we would need to specify them all, including the comparator and number of entries. + til::static_map stringIntMap{ + std::pair{ "xylophones"sv, 100 }, + std::pair{ "apples"sv, 200 }, + std::pair{ "grapes"sv, 300 }, + std::pair{ "pears"sv, 400 }, + }; + + VERIFY_ARE_EQUAL(100, stringIntMap.at("xylophones")); + VERIFY_ARE_EQUAL(300, stringIntMap.at("grapes")); + VERIFY_ARE_EQUAL(400, stringIntMap.at("pears")); + VERIFY_ARE_EQUAL(200, stringIntMap.at("apples")); + + int unused{}; + VERIFY_THROWS(unused = stringIntMap.at("0_hello"), std::runtime_error); + VERIFY_THROWS(unused = stringIntMap.at("z_world"), std::runtime_error); + } + + TEST_METHOD(Find) + { + til::static_map intIntMap{ + std::pair{ 5, 500 }, + }; + + VERIFY_ARE_NOT_EQUAL(intIntMap.end(), intIntMap.find(5)); + VERIFY_ARE_EQUAL(intIntMap.end(), intIntMap.find(7)); + } + +#pragma warning(push) +#pragma warning(disable : 26446) // Suppress bounds.4 check for subscript operator. + TEST_METHOD(Subscript) + { + til::static_map intIntMap{ + std::pair{ 5, 500 }, + }; + + VERIFY_ARE_EQUAL(500, intIntMap[5]); + int unused{}; + VERIFY_THROWS(unused = intIntMap[7], std::runtime_error); + } +#pragma warning(pop) +}; diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index d25d9147f02..1b9975ec83b 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -14,6 +14,7 @@ + diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index 5633f453b7d..924ee638336 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -10,6 +10,7 @@ +