Note: this is based on the version of Ox in following revision in the Nostalgia repo : 1b629da8fc658a85f07b5209f2791a5ebdf79fa1

Problem

In C++, hash maps are often used with strings as keys. This would typically look something like this.

std::unordered_map<std::string, int> ages;
ages["Jerry Smith"] = 54;

And here is an example of a lookup:

int age = ages["Jerry Smith"];

There is a hidden inefficiency here. The lookup operator does not take a std::string_view or a C string. The lookup operator takes a std::string. That means, even though we are passing in a C string that has all the necessary data, we are implicitly calling the std::string constructor, which will allocate space on the heap for the string data, then copy the existing C string into the buffer it allocated. Then, as soon as the lookup call is finished, the temporary std::string is destroyed.

We usually use std::string_view to avoid this, but the std::unordered_map lookup operator (and other functions that take the key) naively uses the key type parameter for lookups.

Solution: MaybeView

Unfortunately, we really cannot fix std::unordered_map without amending the C++ standard. However, we can fix ox::HashMap.

This is where ox::MaybeView comes in.

// these are actually spread out across a few different files in Ox

template<typename T>
struct MaybeView {
	using type = T;
};

template<typename T>
using MaybeView_t = typename MaybeView<T>::type;

template<size_t sz>
struct MaybeView<ox::IString<sz>> {
	using type = ox::StringView;
};

template<size_t sz>
struct MaybeView<ox::BasicString<sz>> {
	using type = ox::StringView;
};

ox::MaybeView_t allows us to easily get the view form of certain types, while simply using the actual type passed in for types that do not have corresponding view types. This would mean that ox::MaybeView_t<int> would evaluate to int, and ox::MaybeView_t<ox::String> would evaluate to ox::StringView.

ox::HashMap uses ox::MaybeView_t as so:

template<typename K, typename T>
class HashMap {
    // note: many HashMap members have been removed from this excerpt for the
    // sake of brevity

	public:
		constexpr T &operator[](MaybeView_t<K> const&key);

		constexpr Result<T*> at(MaybeView_t<K> const&key) noexcept;

		constexpr Result<const T*> at(MaybeView_t<K> const&key) const noexcept;

		constexpr void erase(MaybeView_t<K> const&key);

		[[nodiscard]]
		constexpr bool contains(MaybeView_t<K> const&key) const noexcept;

};

ox::Vector similarly takes advantage of ox::MaybeView_t for its contains function:

template<
    typename T, std::size_t SmallVectorSize = 0,
    typename Allocator = std::allocator<T>>
class Vector: detail::VectorAllocator<T, Allocator, SmallVectorSize> {

	public:
		[[nodiscard]]
		constexpr bool contains(MaybeView_t<T> const&) const noexcept;

};