Introduce til::presorted_static_map (#7640)

til::static_map can't be constexpr until we move to C++20.
It can't be constexpr because std::sort isn't constexpr until then.
This poses a problem: if we start using it and treating it like a map,
we'll incur a potentially high cost in static initialization in both
code size in .text and runtime.

This commit introduces presorted_static_map, which is static_map except
that it doesn't automatically sort its keys. That's the only difference.

At this point, it's just a maplike interface to a constant array of
pairs that does a binary search. It should be used for small tables that
are used infrequently enough as to not warrant their cost in code size
or initialization time. It should also be used for tables that aren't
going to be edited much by developers (like the color table in #7578.)
This commit is contained in:
Dustin L. Howett 2020-09-29 12:01:50 -07:00 committed by GitHub
parent 3cf31fbde4
commit 6f051140da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 5 deletions

View file

@ -1804,6 +1804,7 @@ preinstalled
PRELOAD
PREMULTIPLIED
prepopulated
presorted
PREVENTPINNING
PREVIEWLABEL
PREVIEWWINDOW

View file

@ -4,13 +4,35 @@
#pragma once
// static_map implements a very simple std::map-like type
// that is entirely (compile-time-)constant.
// that is entirely (compile-time-)constant after C++20.
// There is no requirement that keys be sorted, as it will
// use constexpr std::sort during construction.
//
// Until we can use C++20, this is no cheaper than using
// a static std::unordered_map that is initialized at
// startup or on first use.
// To build something that can be constexpr as of C++17,
// use til::presorted_static_map and make certain that
// your pairs are sorted as they would have been sorted
// by your comparator.
// A failure to sort your keys will result in unusual
// runtime behavior, but no error messages will be
// generated.
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
template<typename K, typename V, typename Compare = std::less<K>, size_t N = 0>
namespace details
{
struct unsorted_input_t : public std::false_type
{
};
struct presorted_input_t : public std::true_type
{
};
}
template<typename K, typename V, typename Compare = std::less<K>, size_t N = 0, typename SortedInput = details::unsorted_input_t>
class static_map
{
public:
@ -22,8 +44,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_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!
if constexpr (!SortedInput::value)
{
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
@ -66,9 +91,21 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
std::array<std::pair<K, V>, N> _array;
};
template<typename K, typename V, typename Compare = std::less<K>, size_t N = 0>
class presorted_static_map : public static_map<K, V, Compare, N, details::presorted_input_t>
{
public:
template<typename... Args>
constexpr explicit presorted_static_map(const Args&... args) noexcept :
static_map{ args... } {};
};
// 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)>;
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), details::unsorted_input_t>;
template<typename First, typename... Rest>
presorted_static_map(First, Rest...) -> presorted_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)>;
}

View file

@ -95,4 +95,29 @@ class StaticMapTests
VERIFY_THROWS(unused = intIntMap[7], std::runtime_error);
}
#pragma warning(pop)
TEST_METHOD(Presort)
{
static constexpr til::presorted_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(4), std::runtime_error);
VERIFY_THROWS(unused = intIntMap.at(7), std::runtime_error);
#pragma warning(push)
#pragma warning(disable : 26446) // Suppress bounds.4 check for subscript operator.
VERIFY_ARE_EQUAL(500, intIntMap[5]);
VERIFY_THROWS(unused = intIntMap[4], std::runtime_error);
VERIFY_THROWS(unused = intIntMap[7], std::runtime_error);
#pragma warning(pop)
}
};