diff --git a/src/RemoteTagCache.hxx b/src/RemoteTagCache.hxx
index 5295f46d9..96e59c0f2 100644
--- a/src/RemoteTagCache.hxx
+++ b/src/RemoteTagCache.hxx
@@ -52,26 +52,10 @@ class RemoteTagCache final {
 		void OnRemoteTag(Tag &&tag) noexcept override;
 		void OnRemoteTagError(std::exception_ptr e) noexcept override;
 
-		struct Hash : std::hash<std::string> {
-			using std::hash<std::string>::operator();
-
+		struct GetUri {
 			[[gnu::pure]]
-			std::size_t operator()(const Item &item) const noexcept {
-				return std::hash<std::string>::operator()(item.uri);
-			}
-		};
-
-		struct Equal {
-			[[gnu::pure]]
-			bool operator()(const Item &a,
-					const Item &b) const noexcept {
-				return a.uri == b.uri;
-			}
-
-			[[gnu::pure]]
-			bool operator()(const std::string &a,
-					const Item &b) const noexcept {
-				return a == b.uri;
+			std::string_view operator()(const Item &item) const noexcept {
+				return item.uri;
 			}
 		};
 	};
@@ -101,7 +85,9 @@ class RemoteTagCache final {
 	ItemList invoke_list;
 
 	IntrusiveHashSet<Item, 127,
-			 IntrusiveHashSetOperators<Item::Hash, Item::Equal>,
+			 IntrusiveHashSetOperators<std::hash<std::string_view>,
+						   std::equal_to<std::string_view>,
+						   Item::GetUri>,
 			 IntrusiveHashSetBaseHookTraits<Item>,
 			 true> map;
 
diff --git a/src/input/cache/Manager.cxx b/src/input/cache/Manager.cxx
index 9ac0ae5d5..9b0356159 100644
--- a/src/input/cache/Manager.cxx
+++ b/src/input/cache/Manager.cxx
@@ -11,37 +11,10 @@
 
 #include <string.h>
 
-inline std::size_t
-InputCacheManager::ItemHash::operator()(std::string_view uri) const noexcept
+inline std::string_view
+InputCacheManager::ItemGetUri::operator()(const InputCacheItem &item) const noexcept
 {
-	return std::hash<std::string_view>{}(uri);
-}
-
-inline std::size_t
-InputCacheManager::ItemHash::operator()(const InputCacheItem &item) const noexcept
-{
-	return std::hash<std::string_view>{}(item.GetUri());
-}
-
-inline bool
-InputCacheManager::ItemEqual::operator()(const InputCacheItem &a,
-					 std::string_view b) const noexcept
-{
-	return a.GetUri() == b;
-}
-
-inline bool
-InputCacheManager::ItemEqual::operator()(std::string_view a,
-					 const InputCacheItem &b) const noexcept
-{
-	return a == b.GetUri();
-}
-
-inline bool
-InputCacheManager::ItemEqual::operator()(const InputCacheItem &a,
-					 const InputCacheItem &b) const noexcept
-{
-	return a.GetUri() == b.GetUri();
+	return item.GetUri();
 }
 
 InputCacheManager::InputCacheManager(const InputCacheConfig &config) noexcept
diff --git a/src/input/cache/Manager.hxx b/src/input/cache/Manager.hxx
index 26cba09af..bf160bb12 100644
--- a/src/input/cache/Manager.hxx
+++ b/src/input/cache/Manager.hxx
@@ -24,32 +24,17 @@ class InputCacheManager {
 
 	size_t total_size = 0;
 
-	struct ItemHash {
+	struct ItemGetUri {
 		[[gnu::pure]]
-		std::size_t operator()(std::string_view uri) const noexcept;
-
-		[[gnu::pure]]
-		std::size_t operator()(const InputCacheItem &item) const noexcept;
-	};
-
-	struct ItemEqual {
-		[[gnu::pure]]
-		bool operator()(const InputCacheItem &a,
-				std::string_view b) const noexcept;
-
-		[[gnu::pure]]
-		bool operator()(std::string_view a,
-				const InputCacheItem &b) const noexcept;
-
-		[[gnu::pure]]
-		bool operator()(const InputCacheItem &a,
-				const InputCacheItem &b) const noexcept;
+		std::string_view operator()(const InputCacheItem &item) const noexcept;
 	};
 
 	IntrusiveList<InputCacheItem> items_by_time;
 
 	IntrusiveHashSet<InputCacheItem, 127,
-			 IntrusiveHashSetOperators<ItemHash, ItemEqual>> items_by_uri;
+			 IntrusiveHashSetOperators<std::hash<std::string_view>,
+						   std::equal_to<std::string_view>,
+						   ItemGetUri>> items_by_uri;
 
 public:
 	explicit InputCacheManager(const InputCacheConfig &config) noexcept;
diff --git a/src/util/IntrusiveHashSet.hxx b/src/util/IntrusiveHashSet.hxx
index 5b2520a86..a1a252352 100644
--- a/src/util/IntrusiveHashSet.hxx
+++ b/src/util/IntrusiveHashSet.hxx
@@ -75,7 +75,12 @@ struct IntrusiveHashSetMemberHookTraits {
 	}
 };
 
-template<typename Hash, typename Equal>
+/**
+ * @param GetKey a function object which extracts the "key" part of an
+ * item
+ */
+template<typename Hash, typename Equal,
+	 typename GetKey=std::identity>
 struct IntrusiveHashSetOperators {
 	using hasher = Hash;
 	using key_equal = Equal;
@@ -85,6 +90,9 @@ struct IntrusiveHashSetOperators {
 
 	[[no_unique_address]]
 	Equal equal;
+
+	[[no_unique_address]]
+	GetKey get_key;
 };
 
 /**
@@ -214,7 +222,7 @@ public:
 						     Disposer<value_type> auto disposer) noexcept {
 		auto &bucket = GetBucket(key);
 		std::size_t n = bucket.remove_and_dispose_if([this, &key](const auto &item){
-			return ops.equal(key, item);
+			return ops.equal(key, ops.get_key(item));
 		}, disposer);
 		counter -= n;
 		return n;
@@ -225,7 +233,7 @@ public:
 							Disposer<value_type> auto disposer) noexcept {
 		auto &bucket = GetBucket(key);
 		std::size_t n = bucket.remove_and_dispose_if([this, &key, &pred](const auto &item){
-			return ops.equal(key, item) && pred(item);
+			return ops.equal(key, ops.get_key(item)) && pred(item);
 		}, disposer);
 		counter -= n;
 		return n;
@@ -247,7 +255,7 @@ public:
 	constexpr std::pair<bucket_iterator, bool> insert_check(const auto &key) noexcept {
 		auto &bucket = GetBucket(key);
 		for (auto &i : bucket)
-			if (ops.equal(key, i))
+			if (ops.equal(key, ops.get_key(i)))
 				return {bucket.iterator_to(i), false};
 
 		/* bucket.end() is a pointer to the bucket's list
@@ -267,7 +275,7 @@ public:
 
 		/* using insert_after() so the new item gets inserted
 		   at the front of the bucket list */
-		GetBucket(item).insert_after(bucket, item);
+		GetBucket(ops.get_key(item)).insert_after(bucket, item);
 	}
 
 	/**
@@ -276,12 +284,12 @@ public:
 	 */
 	constexpr void insert(reference item) noexcept {
 		++counter;
-		GetBucket(item).push_front(item);
+		GetBucket(ops.get_key(item)).push_front(item);
 	}
 
 	constexpr bucket_iterator erase(bucket_iterator i) noexcept {
 		--counter;
-		return GetBucket(*i).erase(i);
+		return GetBucket(ops.get_key(*i)).erase(i);
 	}
 
 	constexpr bucket_iterator erase_and_dispose(bucket_iterator i,
@@ -295,7 +303,7 @@ public:
 	constexpr bucket_iterator find(const auto &key) noexcept {
 		auto &bucket = GetBucket(key);
 		for (auto &i : bucket)
-			if (ops.equal(key, i))
+			if (ops.equal(key, ops.get_key(i)))
 				return bucket.iterator_to(i);
 
 		return end();
@@ -305,7 +313,7 @@ public:
 	constexpr const_bucket_iterator find(const auto &key) const noexcept {
 		auto &bucket = GetBucket(key);
 		for (auto &i : bucket)
-			if (ops.equal(key, i))
+			if (ops.equal(key, ops.get_key(i)))
 				return bucket.iterator_to(i);
 
 		return end();
@@ -322,7 +330,7 @@ public:
 					  std::predicate<const_reference> auto pred) noexcept {
 		auto &bucket = GetBucket(key);
 		for (auto &i : bucket)
-			if (ops.equal(key, i) && pred(i))
+			if (ops.equal(key, ops.get_key(i)) && pred(i))
 				return bucket.iterator_to(i);
 
 		return end();
@@ -349,7 +357,7 @@ public:
 		auto &bucket = GetBucket(key);
 
 		for (auto i = bucket.begin(), e = bucket.end(); i != e;) {
-			if (!ops.equal(key, *i))
+			if (!ops.equal(key, ops.get_key(*i)))
 				++i;
 			else if (expired_pred(*i))
 				i = erase_and_dispose(i, disposer);