[libcxx-commits] [libcxx] [libc++] Avoid constructing additional opjects when using map::at (PR #157866)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 10 07:30:18 PDT 2025
https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/157866
This patch adds additional overloads to `map::at` in case its known that the argument is transparently comparable to the key type. This avoids actually constructing the key type in some cases, potentially removing allocations.
>From 8ef0d31f3da499b8cb47b525c774d5726e6f1494 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Wed, 10 Sep 2025 16:27:03 +0200
Subject: [PATCH] [libc++] Avoid constructing additional opjects when using
map::at
---
libcxx/include/__functional/operations.h | 96 +++++++++++++++++++
libcxx/include/__tree | 21 ++--
.../is_transparently_comparable.h | 25 +++++
.../include/__type_traits/make_transparent.h | 42 ++++++++
libcxx/include/__utility/try_key_extraction.h | 47 +++++----
libcxx/include/map | 22 +++++
libcxx/include/string | 11 +++
7 files changed, 233 insertions(+), 31 deletions(-)
create mode 100644 libcxx/include/__type_traits/is_transparently_comparable.h
create mode 100644 libcxx/include/__type_traits/make_transparent.h
diff --git a/libcxx/include/__functional/operations.h b/libcxx/include/__functional/operations.h
index 7b0ea11db5844..6cd450d465ff4 100644
--- a/libcxx/include/__functional/operations.h
+++ b/libcxx/include/__functional/operations.h
@@ -16,6 +16,7 @@
#include <__fwd/functional.h>
#include <__type_traits/desugars_to.h>
#include <__type_traits/is_integral.h>
+#include <__type_traits/make_transparent.h>
#include <__utility/forward.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -60,6 +61,11 @@ struct plus<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<plus<_Tp> > {
+ using type _LIBCPP_NODEBUG = plus<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -86,6 +92,11 @@ struct minus<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<minus<_Tp> > {
+ using type _LIBCPP_NODEBUG = minus<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -112,6 +123,11 @@ struct multiplies<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<multiplies<_Tp> > {
+ using type _LIBCPP_NODEBUG = multiplies<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -138,6 +154,11 @@ struct divides<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<divides<_Tp> > {
+ using type _LIBCPP_NODEBUG = divides<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -164,6 +185,11 @@ struct modulus<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<modulus<_Tp> > {
+ using type _LIBCPP_NODEBUG = modulus<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -188,6 +214,11 @@ struct negate<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<negate<_Tp> > {
+ using type _LIBCPP_NODEBUG = negate<>;
+};
+
// Bitwise operations
#if _LIBCPP_STD_VER >= 14
@@ -216,6 +247,11 @@ struct bit_and<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<bit_and<_Tp> > {
+ using type _LIBCPP_NODEBUG = bit_and<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
struct bit_not : __unary_function<_Tp, _Tp> {
@@ -235,6 +271,11 @@ struct bit_not<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<bit_not<_Tp> > {
+ using type _LIBCPP_NODEBUG = bit_not<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -261,6 +302,11 @@ struct bit_or<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<bit_or<_Tp> > {
+ using type _LIBCPP_NODEBUG = bit_or<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -287,6 +333,11 @@ struct bit_xor<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<bit_xor<_Tp> > {
+ using type _LIBCPP_NODEBUG = bit_xor<>;
+};
+
// Comparison operations
#if _LIBCPP_STD_VER >= 14
@@ -315,6 +366,11 @@ struct equal_to<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<equal_to<_Tp> > {
+ using type _LIBCPP_NODEBUG = equal_to<>;
+};
+
// The non-transparent std::equal_to specialization is only equivalent to a raw equality
// comparison when we don't perform an implicit conversion when calling it.
template <class _Tp>
@@ -350,6 +406,11 @@ struct not_equal_to<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<not_equal_to<_Tp> > {
+ using type _LIBCPP_NODEBUG = not_equal_to<>;
+};
+
template <class _Tp>
struct less : __binary_function<_Tp, _Tp, bool> {
typedef bool __result_type; // used by valarray
@@ -377,6 +438,11 @@ struct less<void> {
typedef void is_transparent;
};
+template <class _Tp>
+struct __make_transparent<less<_Tp> > {
+ using type _LIBCPP_NODEBUG = less<>;
+};
+
template <class _Tp, class _Up>
inline const bool __desugars_to_v<__less_tag, less<>, _Tp, _Up> = true;
@@ -410,6 +476,11 @@ struct less_equal<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<less_equal<_Tp> > {
+ using type _LIBCPP_NODEBUG = less_equal<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -436,6 +507,11 @@ struct greater_equal<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<greater_equal<_Tp> > {
+ using type _LIBCPP_NODEBUG = greater_equal<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -468,6 +544,11 @@ template <class _Tp, class _Up>
inline const bool __desugars_to_v<__greater_tag, greater<>, _Tp, _Up> = true;
#endif
+template <class _Tp>
+struct __make_transparent<greater<_Tp> > {
+ using type _LIBCPP_NODEBUG = greater<>;
+};
+
// Logical operations
#if _LIBCPP_STD_VER >= 14
@@ -496,6 +577,11 @@ struct logical_and<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<logical_and<_Tp> > {
+ using type _LIBCPP_NODEBUG = logical_and<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -520,6 +606,11 @@ struct logical_not<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<logical_not<_Tp> > {
+ using type _LIBCPP_NODEBUG = logical_not<>;
+};
+
#if _LIBCPP_STD_VER >= 14
template <class _Tp = void>
#else
@@ -546,6 +637,11 @@ struct logical_or<void> {
};
#endif
+template <class _Tp>
+struct __make_transparent<logical_or<_Tp> > {
+ using type _LIBCPP_NODEBUG = logical_or<>;
+};
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___FUNCTIONAL_OPERATIONS_H
diff --git a/libcxx/include/__tree b/libcxx/include/__tree
index 81a73342cc61b..f88b6e58564c0 100644
--- a/libcxx/include/__tree
+++ b/libcxx/include/__tree
@@ -929,24 +929,24 @@ public:
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __emplace_unique(_Args&&... __args) {
return std::__try_key_extraction<key_type>(
- [this](const key_type& __key, _Args&&... __args2) {
+ [this]<class _KeyT>(const _KeyT& __key, _Args&&... __args2) {
auto [__parent, __child] = __find_equal(__key);
- __node_pointer __r = static_cast<__node_pointer>(__child);
- bool __inserted = false;
if (__child == nullptr) {
- __node_holder __h = __construct_node(std::forward<_Args>(__args2)...);
- __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
- __r = __h.release();
- __inserted = true;
+ return [](__tree& __self, __end_node_pointer __parent2, __node_base_pointer& __child2, _Args&&... __args3) {
+ __node_holder __h = __self.__construct_node(std::forward<_Args>(__args3)...);
+ __self.__insert_node_at(__parent2, __child2, static_cast<__node_base_pointer>(__h.get()));
+ return pair<iterator, bool>(iterator(static_cast<__node_pointer>(__h.release())), true);
+ }(*this, __parent, __child, std::forward<_Args>(__args2)...);
+ } else {
+ return pair<iterator, bool>(iterator(static_cast<__node_pointer>(__child)), false);
}
- return pair<iterator, bool>(iterator(__r), __inserted);
},
[this](_Args&&... __args2) {
__node_holder __h = __construct_node(std::forward<_Args>(__args2)...);
auto [__parent, __child] = __find_equal(__h->__get_value());
__node_pointer __r = static_cast<__node_pointer>(__child);
bool __inserted = false;
- if (__child == nullptr) {
+ if (__child == nullptr) [[unlikely]] {
__insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
__r = __h.release();
__inserted = true;
@@ -1729,7 +1729,8 @@ __tree<_Tp, _Compare, _Allocator>::__find_equal(const _Key& __v) {
}
__node_base_pointer* __node_ptr = __root_ptr();
- auto __comp = __lazy_synth_three_way_comparator<_Compare, _Key, value_type>(value_comp());
+ auto&& __transparent = std::__as_transparent(value_comp());
+ auto __comp = __lazy_synth_three_way_comparator<__make_transparent_t<_Compare>, _Key, value_type>(__transparent);
while (true) {
auto __comp_res = __comp(__v, __nd->__get_value());
diff --git a/libcxx/include/__type_traits/is_transparently_comparable.h b/libcxx/include/__type_traits/is_transparently_comparable.h
new file mode 100644
index 0000000000000..9e27c485360fd
--- /dev/null
+++ b/libcxx/include/__type_traits/is_transparently_comparable.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___TYPE_TRAITS_IS_TRANSPARENTLY_COMPARABLE_H
+#define _LIBCPP___TYPE_TRAITS_IS_TRANSPARENTLY_COMPARABLE_H
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Key, class _Arg>
+inline const bool __is_transparently_comparable_v = __is_same(_Key, _Arg);
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_IS_TRANSPARENTLY_COMPARABLE_H
diff --git a/libcxx/include/__type_traits/make_transparent.h b/libcxx/include/__type_traits/make_transparent.h
new file mode 100644
index 0000000000000..06b1b0e444812
--- /dev/null
+++ b/libcxx/include/__type_traits/make_transparent.h
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___TYPE_TRAITS_MAKE_TRANSPARENT_H
+#define _LIBCPP___TYPE_TRAITS_MAKE_TRANSPARENT_H
+
+#include <__config>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/is_same.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Comparator>
+struct __make_transparent {
+ using type _LIBCPP_NODEBUG = _Comparator;
+};
+
+template <class _Comparator>
+using __make_transparent_t _LIBCPP_NODEBUG = typename __make_transparent<_Comparator>::type;
+
+template <class _Comparator, __enable_if_t<is_same<_Comparator, __make_transparent_t<_Comparator> >::value, int> = 0>
+_Comparator& __as_transparent(_Comparator& __comp) {
+ return __comp;
+}
+
+template <class _Comparator, __enable_if_t<!is_same<_Comparator, __make_transparent_t<_Comparator> >::value, int> = 0>
+__make_transparent_t<_Comparator> __as_transparent(_Comparator& __comp) {
+ return __make_transparent_t<_Comparator>();
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_MAKE_TRANSPARENT_H
diff --git a/libcxx/include/__utility/try_key_extraction.h b/libcxx/include/__utility/try_key_extraction.h
index 755c08214019f..a5ca4e98a1eec 100644
--- a/libcxx/include/__utility/try_key_extraction.h
+++ b/libcxx/include/__utility/try_key_extraction.h
@@ -14,6 +14,7 @@
#include <__fwd/tuple.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_same.h>
+#include <__type_traits/is_transparently_comparable.h>
#include <__type_traits/remove_const.h>
#include <__type_traits/remove_const_ref.h>
#include <__utility/declval.h>
@@ -38,20 +39,22 @@ template <class _KeyT,
class _WithKey,
class _WithoutKey,
class _Arg,
- __enable_if_t<is_same<_KeyT, __remove_const_ref_t<_Arg> >::value, int> = 0>
+ __enable_if_t<__is_transparently_comparable_v<_KeyT, __remove_const_ref_t<_Arg> >, int> = 0>
_LIBCPP_HIDE_FROM_ABI _Ret
__try_key_extraction_impl(__priority_tag<1>, _WithKey __with_key, _WithoutKey, _Arg&& __arg) {
return __with_key(__arg, std::forward<_Arg>(__arg));
}
-template <class _KeyT,
- class _Ret,
- class _WithKey,
- class _WithoutKey,
- class _Arg,
- __enable_if_t<__is_pair_v<__remove_const_ref_t<_Arg> > &&
- is_same<__remove_const_t<typename __remove_const_ref_t<_Arg>::first_type>, _KeyT>::value,
- int> = 0>
+template <
+ class _KeyT,
+ class _Ret,
+ class _WithKey,
+ class _WithoutKey,
+ class _Arg,
+ __enable_if_t<
+ __is_pair_v<__remove_const_ref_t<_Arg> > &&
+ __is_transparently_comparable_v<_KeyT, __remove_const_t<typename __remove_const_ref_t<_Arg>::first_type> >,
+ int> = 0>
_LIBCPP_HIDE_FROM_ABI _Ret
__try_key_extraction_impl(__priority_tag<1>, _WithKey __with_key, _WithoutKey, _Arg&& __arg) {
return __with_key(__arg.first, std::forward<_Arg>(__arg));
@@ -63,24 +66,26 @@ template <class _KeyT,
class _WithoutKey,
class _Arg1,
class _Arg2,
- __enable_if_t<is_same<_KeyT, __remove_const_ref_t<_Arg1> >::value, int> = 0>
+ __enable_if_t<__is_transparently_comparable_v<_KeyT, __remove_const_ref_t<_Arg1> >, int> = 0>
_LIBCPP_HIDE_FROM_ABI _Ret
__try_key_extraction_impl(__priority_tag<1>, _WithKey __with_key, _WithoutKey, _Arg1&& __arg1, _Arg2&& __arg2) {
return __with_key(__arg1, std::forward<_Arg1>(__arg1), std::forward<_Arg2>(__arg2));
}
#ifndef _LIBCPP_CXX03_LANG
-template <class _KeyT,
- class _Ret,
- class _WithKey,
- class _WithoutKey,
- class _PiecewiseConstruct,
- class _Tuple1,
- class _Tuple2,
- __enable_if_t<is_same<__remove_const_ref_t<_PiecewiseConstruct>, piecewise_construct_t>::value &&
- __is_tuple_v<_Tuple1> && tuple_size<_Tuple1>::value == 1 &&
- is_same<__remove_const_ref_t<typename tuple_element<0, _Tuple1>::type>, _KeyT>::value,
- int> = 0>
+template <
+ class _KeyT,
+ class _Ret,
+ class _WithKey,
+ class _WithoutKey,
+ class _PiecewiseConstruct,
+ class _Tuple1,
+ class _Tuple2,
+ __enable_if_t<
+ is_same<__remove_const_ref_t<_PiecewiseConstruct>, piecewise_construct_t>::value && __is_tuple_v<_Tuple1> &&
+ tuple_size<_Tuple1>::value == 1 &&
+ __is_transparently_comparable_v<_KeyT, __remove_const_ref_t<typename tuple_element<0, _Tuple1>::type>>,
+ int> = 0>
_LIBCPP_HIDE_FROM_ABI _Ret __try_key_extraction_impl(
__priority_tag<1>,
_WithKey __with_key,
diff --git a/libcxx/include/map b/libcxx/include/map
index f428c781e5036..9bdc36d95f2e3 100644
--- a/libcxx/include/map
+++ b/libcxx/include/map
@@ -600,6 +600,7 @@ erase_if(multimap<Key, T, Compare, Allocator>& c, Predicate pred); // C++20
# include <__tree>
# include <__type_traits/container_traits.h>
# include <__type_traits/is_allocator.h>
+# include <__type_traits/is_transparently_comparable.h>
# include <__type_traits/remove_const.h>
# include <__type_traits/type_identity.h>
# include <__utility/forward.h>
@@ -703,6 +704,11 @@ public:
# endif
};
+template <class _Key, class _MapValueT, class _Compare, bool __b>
+struct __make_transparent<__map_value_compare<_Key, _MapValueT, _Compare, __b> > {
+ using type _LIBCPP_NODEBUG = __map_value_compare<_Key, _MapValueT, __make_transparent_t<_Compare> >;
+};
+
# if _LIBCPP_STD_VER >= 14
template <class _MapValueT, class _Key, class _Compare>
struct __lazy_synth_three_way_comparator<__map_value_compare<_Key, _MapValueT, _Compare>, _MapValueT, _MapValueT> {
@@ -1085,6 +1091,22 @@ public:
_LIBCPP_HIDE_FROM_ABI mapped_type& operator[](key_type&& __k);
# endif
+ template <class _Arg, __enable_if_t<__is_transparently_comparable_v<key_type, __remove_cvref_t<_Arg> >, int> = 0>
+ _LIBCPP_HIDE_FROM_ABI inline mapped_type& at(_Arg&& __arg) {
+ auto [_, __child] = __tree_.__find_equal(__arg);
+ if (__child == nullptr)
+ std::__throw_out_of_range("map::at: key not found");
+ return static_cast<__node_pointer>(__child)->__get_value().second;
+ }
+
+ template <class _Arg, __enable_if_t<__is_transparently_comparable_v<key_type, __remove_cvref_t<_Arg> >, int> = 0>
+ _LIBCPP_HIDE_FROM_ABI inline const mapped_type& at(_Arg&& __arg) const {
+ auto [_, __child] = __tree_.__find_equal(__arg);
+ if (__child == nullptr)
+ std::__throw_out_of_range("map::at: key not found");
+ return static_cast<__node_pointer>(__child)->__get_value().second;
+ }
+
_LIBCPP_HIDE_FROM_ABI mapped_type& at(const key_type& __k);
_LIBCPP_HIDE_FROM_ABI const mapped_type& at(const key_type& __k) const;
diff --git a/libcxx/include/string b/libcxx/include/string
index 0abdfebcb863f..de3b33fb196db 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -633,6 +633,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_standard_layout.h>
+# include <__type_traits/is_transparently_comparable.h>
# include <__type_traits/is_trivially_constructible.h>
# include <__type_traits/is_trivially_copyable.h>
# include <__type_traits/is_trivially_relocatable.h>
@@ -2534,6 +2535,16 @@ struct __default_three_way_comparator<basic_string<_CharT, _Traits, _Alloc>, bas
}
};
+template <class _CharT, class _Traits, class _Alloc>
+inline const bool
+ __is_transparently_comparable_v<basic_string<_CharT, _Traits, _Alloc>, basic_string_view<_CharT, _Traits>> = true;
+
+template <class _CharT, class _Traits, class _Alloc>
+inline const bool __is_transparently_comparable_v<basic_string<_CharT, _Traits, _Alloc>, const _CharT*> = true;
+
+template <class _CharT, class _Traits, class _Alloc, size_t _Np>
+inline const bool __is_transparently_comparable_v<basic_string<_CharT, _Traits, _Alloc>, _CharT[_Np]> = true;
+
# if _LIBCPP_STD_VER >= 17
template <class _InputIterator,
class _CharT = __iter_value_type<_InputIterator>,
More information about the libcxx-commits
mailing list