Add til::static_map, a constexpr key-value store (#7323)
This is based on (cribbed almost directly from) code written by the inimitable @StephanTLavavej on one of our mailing lists. This is a nice generic version of the approach used in JsonUtils::EnumMapper and CodepointWidthDetector: a static array of key-value pairs that we binary-search at runtime (or at compile time, as the case may be.) Keys are not required to be sorted, as we're taking advantage of constexpr std::sort (VS 16.6+) to get the compiler to do it for us. How cool is that? static_map presents an operator[] or at much like std::map/std::unordered_map does. I've added some tests, but they're practically fully-solveable at compile time so they pretty much act like `VERIFY_IS_TRUE(true)`.
This commit is contained in:
parent
93d266925c
commit
66fd9c367d
74
src/inc/til/static_map.h
Normal file
74
src/inc/til/static_map.h
Normal file
|
@ -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<typename K, typename V, typename Compare = std::less<K>, size_t N = 0>
|
||||
class static_map
|
||||
{
|
||||
public:
|
||||
using const_iterator = typename std::array<std::pair<K, V>, N>::const_iterator;
|
||||
|
||||
template<typename... Args>
|
||||
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<std::pair<K, V>, 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<typename First, typename... Rest>
|
||||
static_map(First, Rest...)->static_map<std::conditional_t<std::conjunction_v<std::is_same<First, Rest>...>, typename First::first_type, void>, typename First::second_type, std::less<typename First::first_type>, 1 + sizeof...(Rest)>;
|
||||
}
|
98
src/til/ut_til/StaticMapTests.cpp
Normal file
98
src/til/ut_til/StaticMapTests.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include <til/static_map.h>
|
||||
|
||||
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<const char *, ...>
|
||||
// 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)
|
||||
};
|
|
@ -14,6 +14,7 @@
|
|||
<ClCompile Include="BitmapTests.cpp" />
|
||||
<ClCompile Include="OperatorTests.cpp" />
|
||||
<ClCompile Include="PointTests.cpp" />
|
||||
<ClCompile Include="StaticMapTests.cpp" />
|
||||
<ClCompile Include="MathTests.cpp" />
|
||||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="SizeTests.cpp" />
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<ClCompile Include="SizeTests.cpp" />
|
||||
<ClCompile Include="ColorTests.cpp" />
|
||||
<ClCompile Include="PointTests.cpp" />
|
||||
<ClCompile Include="StaticMapTests.cpp" />
|
||||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="BitmapTests.cpp" />
|
||||
<ClCompile Include="OperatorTests.cpp" />
|
||||
|
|
Loading…
Reference in a new issue