-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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)`.
- Loading branch information
Showing
4 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters