Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce til::presorted_static_map #7640

Merged
4 commits merged into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1803,6 +1803,7 @@ preinstalled
PRELOAD
PREMULTIPLIED
prepopulated
presorted
PREVENTPINNING
PREVIEWLABEL
PREVIEWWINDOW
Expand Down
47 changes: 42 additions & 5 deletions src/inc/til/static_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)>;
}
25 changes: 25 additions & 0 deletions src/til/ut_til/StaticMapTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
};
Comment on lines +101 to +105
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what happens for the following two scenarios:

        // 1. sorted, but backwards
        static constexpr til::presorted_static_map intIntMap{
            std::pair{ 5, 500 },
            std::pair{ 3, 300 },
            std::pair{ 1, 100 },
        };

        // 2. not sorted, but declared as presorted
        static constexpr til::presorted_static_map intIntMap{
            std::pair{ 3, 300 },
            std::pair{ 1, 100 },
            std::pair{ 5, 500 },
        };

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the comment in the header!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// A failure to sort your keys will result in unusual
// runtime behavior, but no error messages will be
// generated.

ah...and I'm guessing reverse doesn't count :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda wish it were possible to throw a compiler error if they weren't sorted...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alas! That might require constexpr sort!

Unless i can do constexpr “check if the predicate is false” for every element pair ??

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah there might be no good option here. I'm gonna sign it because it is probably worth the risk to hold us over until the real goodness arrives.


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)
}
};