---
title: "Better Lookups for map , unordered_map , and flat_map"
document: P3091R6
date: 2026-06-11
audience: LWG
reply-to:
  - "Pablo Halpern <phalpern@halpernwightsoftware.com>"
---

# Abstract

The most convenient way to look up an element of a `map` -like container is to use the index operator, i.e., `theMap[key]` . This operator cannot be used, however, when 1) the container is `const` , 2) the mapped type is not default constructible, 3) the default-constructed value is inappropriate for the context, or 4) the container should not be modified. These limitations often force the user to resort to the `find` member function, which returns an iterator that points to a `pair` and typically leads to more complex code having at least one `if` statement and/or duplicate lookup operations. Taking inspiration from other languages, especially Python, this paper proposes the addition (in C++29) of a `lookup` member function that returns `optional<T&>` and leverages the observers and monadic operations of `optional` , such as `value_or` , to simplify lookups for `map` , `unordered_map` , and `flat_map` . This proposal’s usefulness would also be enhanced by the *maybe* facilities proposed in [[P1255]](http://wg21.link/P1255).

# Change Log

**R6** (2026-06 during the Brno meeting)

- Rename `get` to `lookup` per 2026-06-10 discussion of P4139 in Brno.
- Remove `noexcept` , as the comparison functor might throw. Conditional `noexcept` was rejected as being unnecessary and overly complicated.
- Use “ `contains(x)` ” instead of “ `find(x) == end` is `false` ”.
- Minor punctuation changes.

**R5** (2025-10 pre-Kona mailing)

- Rebased to [[N5014]](https://wg21.link/n5014)
- Updated abstract to reflect the fact that P2988 was merged into the WP.
- Added editorial comments encouraging moving descriptions into associative container requirements sections.
- Added complexity clause for `unordered_map::get` .

**R4** (2025-06-20 in Sofia)

- Rebased to [[N5008]](https://wg21.link/n5008)
- Added `constexpr` .
- Added `flat_map` support.
- Fixed typo in feature-test macro.
- Recorded the vote in LEWG to keep the name `.get` and not change it to `.lookup` .
- This revision (R4) was forwarded from LEWG to LWG.

**R3** (2024-10-15 pre-Wroclaw mailing)

- Removed wording that duplicates parts of [[P2988R7]](https://wg21.link/p2988r7).
- Removed enhancements to `optional<T>::value_or` that will be handled in a separate paper, [[P1255]](http://wg21.link/P1255), by Steve Downey.

**R2** (2024-05-23 pre-St. Louis mailing)

- LEWG Reached consensus on some aspects of [[P2988R4]](https://wg21.link/p2988r4) - specifically that `value_or` should return an rvalue for both `optional<T>` and `optional<T&>` . This change is reflected in the wording for this paper. Rather than a separate `optional<T>::or_construct` member, the functionality of `or_construct` is folded into an enhanced `value_or` both `optional<T>` and `optional<T&>` .

**R1** (post 2024-02-27 LEWGI teleconference)

- As a result of discussions in LEWGI on 27 Feb 2024, the return value of `get` was changed from `mapped_type` to `optional<mapped_type&>` , and the functionality of `get_ref` and `get_as` were delegated to `optional` . Note that `optional<T&>` is not valid in C++23 but is proposed in [[P2988R4]](https://wg21.link/p2988r4) for C++26.
- Added a proposed `or_construct` member to `optional`
- Proposed an enhancement of `optional<T&>::value_or` over that in [[P2988R4]](https://wg21.link/p2988r4)
- Added feature-test macros

**R0** (2024-02-15 pre-Tokyo mailing)

- Initial version

# Motivation

Just about every modern computer language has, as part of the language or its standard library, one or more associative containers that uniquely map a key to a value, variously called `map` , `hash_map` , `dictionary` , or something similar. In C++26, we have `std::map` , `std::unordered_map` , and `std::flat_map` . The easy-to-write and easy-to-read expression for retrieving a value for an associated key is simply `theMap[key]` ; in other words, a mapped value is retrieved (and often set) using the index operator, which returns a reference to the value associated with the key. Unfortunately, the index operator in the C++ associative containers has a number of shortcomings compared to many other languages.

- It works only for non- `const` containers.
- It works only for default-constructible mapped types.
- The default-constructed object (when available) is not always the correct identity for a given use case.
- It modifies the container when the key is absent.

Consider a `std::unordered_map` named `theMap` that maps an integer key to a floating-point value, modeling a sparse array of `double` . If we want to find the largest of the `double` values mapped to the integer keys in the range 1 to 100, we might be tempted to write the following loop:

```cpp
double largest = -std::numeric_limits<double>::infinity();
for (int i = 1; i <= 100; ++i)
  largest = std::max(largest, theMap[i]);
```

If `theMap` is `const` , the loop will not compile. If any of the keys in the range `[1, 100]` are absent from the map, then `theMap[i]` will return a default-constructed `double` having value 0.0, which might or might not be desirable, depending on whether we want to treat missing values as truly having value 0.0 or to ignore them (or, equivalently, to treat them as having value \(-\infty\)). Finally if, before executing this loop, `theMap` contains only, say, five entries, then at the end of the loop, it will contain at least 100 entries, most of whose values will be zero.

There are alternatives that avoid all these shortcomings but are significantly less elegant and therefore more error prone. For example, the `at` member function looks a lot like `operator[]` and has none of the above shortcomings, but missing keys are handled by throwing exceptions, making this option impractical for situations other than when the key is almost certain to exist. Moreover, a `try` - `catch` block within a loop is rarely a clean way to structure code:

```cpp
double largest = -std::numeric_limits<double>::infinity();
for (int i = 1; i <= 100; ++i)
{
  try {
    largest = std::max(largest, theMap.at(i));
  } catch (const std::out_of_range&) { }
}
```

The above code would work with a `const` map, would ignore missing elements (rather than treating them as zeros), and would not populate the map with useless entries, but many programmers would argue that the loop is inelegant at best. In most C++ implementations, this code would be extremely inefficient unless key misses are rare.

The other obvious alternative uses the `find` member function:

```cpp
double largest = -std::numeric_limits<double>::infinity();
for (int i = 1; i <= 100; ++i)
{
  auto iter = theMap.find(i);
  if (iter != theMap.end())
    largest = std::max(largest, iter->second);
}
```

This version of the loop is only slightly more verbose than our starting point and avoids all the issues, but using iterators, needing to call *two* member functions ( `find` and `end` ), and having to extract the `second` member of the element `pair` for what should be a *simple* operation increases the cognitive load on both the programmer and the reader. Moreover, a generic use of `find` can yield a subtle bug. Consider the following function template, `f` :

```cpp
template <class Key, class Value>
void f(const Key& k, const std::map<Key, Value>& aMap)
{
  Value obj = some-default-obj-value-expression;
  auto iter = aMap.find(k);
  if (iter != aMap.end())
    obj = iter->second;
  // code that uses `obj` ...
}
```

An instantiation of `f` will not compile unless `Value` is copy assignable. Worse, unless tested with a nonassignable type, the bug could go undetected for a long time. One fix would be initialize `obj` in a single expression:

```cpp
Value obj = aMap.count(k) ? aMap.at(k) : some-default-obj-value-expression;
```

While correct, this solution involves two lookup operations: one for the `count` and one for the `at` . A better fix requires a bit more code:

```cpp
auto iter = aMap.find(k);
Value obj = iter != aMap.end() ? iter->second : some-default-obj-value-expression;
```

Note that the last solution again involves `iterator` , `pair` , and a conditional expression, which is a far cry from the simplicity of `operator[]` .

Let’s contrast these less-than-ideal map lookups to dictionary lookups in another language and consider one way to write the largest-value computation in Python:

```python
inf = float("inf")
largest = -inf
for i in range(1, 101):
    largest = max(largest, theMap.get(i, -inf))
```

The `get` member of Python’s dictionary type looks up the supplied key (first argument). If the key exists in the dictionary, `get` returns the corresponding value; otherwise, `get` returns the specified alternative value (second argument).

In this paper, I propose `lookup` member functions for `std::map` , `std::unordered_map` , and `std::flat_map` similar to the `get` member in Python dictionaries. Because C++ does not have a `None` value like Python’s, `get` returns an `optional` , instead, and delegates the construction of an alternative return value to the `optional` observer members.

# Proposed Feature

## Overview

What’s desired is a simple expression that, given a key, returns the mapped value if the key exists in a specific map and a user-supplied *alternative value* if the key does not exist. The proposed feature is a `lookup` member function for `map` -like containers that returns `optional<T&>` (or, for a `const` map, `optional<const T&>` ):

```cpp
template<class Key, class T, class Compare = less<Key>,
         class Allocator = allocator<pair<const Key, T>>>
class map {
  // ...
  constexpr optional<      mapped_type&> lookup(const key_type& k);
  constexpr optional<const mapped_type&> lookup(const key_type& k) const;
  //...
};
```

These functions depend on having an `optional` template that can be instantiated with reference types, as proposed in [[P2988R12]](https://wg21.link/p2988r12), which was voted into the working draft at the June 2025 meeting in Sofia.

Using this feature, the earlier example could be written almost as simply as the Python version:

```cpp
constexpr double inf = std::numeric_limits<double>::infinity();
double largest = -inf;
for (int i = 1; i <= 100; ++i)
  largest = std::max(largest, theMap.lookup(i).value_or(-inf));
```

## Not Proposed: Enhancements to optional::value_or

To maximize the usefulness and convenience of the proposed `lookup` function, earlier revisions of this paper also proposed enhancements to `optional<T>::value_or` and `optional<T&>::value_or` , allowing (but not requiring) the caller to specify a return type other than `T` , and giving it a variadic argument list comprising constructor arguments for the return value. Although such an extension would be useful, it can also be provided through a non-member function that is expected to be in the next revision of [[P1255]](http://wg21.link/P1255) by Steve Downey. Since that change does not directly relate to the subject of this proposal, there is no reason to combine it with this paper. You can see the proposed extensions in a previous revision of this paper, [[P3091R2]](https://wg21.link/p3091r2).

# Before and After Comparisons

The following table shows how operations are simplified using the proposed new member functions. In these examples, `K` , `T` , and `U` are object types; `m` is an object of (possibly `const` ) `std::map<K, T>` , `unordered_map<K, T>` , or `flat_map<K, T>` ; `k` is a value compatible with `K` ; `v` is an lvalue of type (possibly `const` ) `T` ; and `a1..aN` are arguments that can used to construct an object of type `T` .

<!-- tomd:lossy-table -->

```cpp
auto iter = m.find(k);
T x = iter == m.end() ? T{} : iter->second;
```

```cpp
T x = m.lookup(k).value_or(T{});
```

```cpp
auto iter = m.find(k);
T x = iter == m.end() ?
    T(a1...aN) : iter->second;
```

```cpp
T x = m.lookup(k).value_or(T(a1...aN));
```

```cpp
auto iter = m.find(k);
T& x = iter == m.end() ? v : iter->second;
```

```cpp
T& x = std::reference_or(m.lookup(k), v);  // assuming P1255
```

```cpp
map<K, vector<U>> m{ ... };
auto iter = m.find(k);
span<U> x = iter == m.end() ?
    span<U>{} : iter->second;
```

```cpp
map<K, vector<U>> m{ ... };
span<U> x = value_or<span<U>>(m.lookup(k)); // assuming P1255
```

```cpp
map<K, vector<U>> m{ ... };
const array<U, N> preset{ ... };
auto iter = m.find(k);
span<const U> x = iter == m.end() ?
    span<const U>(preset) : iter->second;
```

```cpp
map<K, vector<U>> m{ ... };
const array<U, N> preset{ ... };
span<const U> x =
  value_or<span<const U>>(m.lookup(k), preset); // assuming P1255
```

```cpp
unordered_map<K, U*> m{ ... };
if (auto iter = m.find(k); iter != m.end()) {
  U* p = iter->second;
  // use p ...
}
```

```cpp
unordered_map<K, U*> m{ ... };
if (U* p = m.lookup(k).value_or((U*) nullptr); p) {
  // use p ...
}

// OR

unordered_map<K, U*> m{ ... };
m.lookup(k).and_then([=](U* p) {
    // use p ...
  });

// OR

unordered_map<K, U*> m{ ... };
for (U* p : m.lookup(k)) {
  // use p ...
}
```

```cpp
if (auto iter = m.find(k); iter != m.end()) {
  T& r = iter->second;
  // use r ...
}
```

```cpp
if (auto q = m.lookup(k); q) {
  T& r = *q;
  // use r ...
}

// OR

for (auto& r : m.lookup(k)) {
  // use r ...
}
```

# Alternatives Considered

## Other Names for lookup

The previous version of this paper used the name `map::<K,V>::get` rather than `map<K,V>::lookup` .

The name `get` was borrowed from the Python dictionary member of the same name. Other names considered were `try_at` , `lookup_at` , `get_optional` , and `lookup_optional` . The `get` name was originally selected for brevity and consistency with other languages, but is different from all other uses of `get` in the Standard Library in that no other use of `get` returns without retrieving the requested value (among other inconsistencies described in [[P4139R3]](http://wg21.link/P4139R3)). LEWG changed the name to `lookup` following discussion of [[P4139R2]](https://wg21.link/p4139r2) in Brno on 2026-06-10:

> POLL: Forward “P4139R2: Better Name for Better Lookups in P3091” with the name `lookup` and without the addition of the random access sequence container (section 4.3) to LWG for C++29.

| SF | F | N | A | SA |
| --- | --- | --- | --- | --- |
| 5 | 12 | 5 | 4 | 2 |

## Add a similar member function to random-access sequential containers

[[P4139R2]](https://wg21.link/p4139r2) additionally proposed that accessing a random-access sequential container using an index that might be out of bounds is similar to accessing an associative container with a key that might not exist, and thus proposed that a function having the same name be added to such random-access containers. This proposal failed to reach consensus in LEWG in Brno, however, and was removed from [[P4139R3]](http://wg21.link/P4139R3).

## Build Retrieve-Value-or-Return-Alternative Functionality Directly into map

Version R0 of this paper proposed `get` , and `get_ref` member functions that would look up a key and return the corresponding value (or a reference to the corresponding value) or else construct an alternative from the nonkey arguments without, involving `optional<T&>` :

```cpp
// return a value
template <class U = remove_cvref_t<T>, class... Args>
  U get(const key_type& key, Args&&... args);
template <class U = remove_cvref_t<T>, class... Args>
  U get(const key_type& key, Args&&... args) const;

// return a reference
template <class Arg>
  auto get_ref(const key_type& key, Arg&& ref)       -> common_reference_t<      mapped_type&, Arg>;
template <class Arg>
  auto get_ref(const key_type& key, Arg&& ref) const -> common_reference_t<const mapped_type&, Arg>;
```

The following table shows the usage of the R0 design compared to the currently proposed design.

<!-- tomd:lossy-table -->

```cpp
auto tval = theMap.get(k);
```

```cpp
auto tval = theMap.get(k).value_or(T{});
```

```cpp
auto tval = theMap.get(k, cT1, cT2);
```

```cpp
auto tval = theMap.lookup(k).value_or(T(cT1, cT2));
```

```cpp
auto& tref = theMap.get_ref(k, lvalue);
```

```cpp
// per P1255
auto& tref = reference_or(theMap.lookup(k), lvalue);
```

```cpp
auto uval = theMap.get<U>(k, cU1, cU2);
```

```cpp
// per P1255
auto uval = value_or<U>(theMap.lookup(k), cU1, cU2);
```

```cpp
if (auto opt = theMap.get<std::optional<T&>>(k);
    opt) {
  auto& ref = *opt;
  // ...
}
```

```cpp
if (auto opt = theMap.lookup(k); opt) {
  auto& ref = *opt;
  // ...
}
```

**Advantages of the R0 Design Over the Current Design**

- R0 provides more concise and readable calls to `get` (and `get_ref` ) in many cases.
- The desired return-value-if-present-else-return-alternative functionality is all specified in one place in the R0 design, exactly where a beginner would look for it.

**Advantages of the Current Design Over the R0 Design**

- The current `lookup` provides a direct indication, via a disengaged `optional` return value, that the key was not found.
- By returning `optional` , the current design can leverage all of the functionality of `optional` , including `value_or` and monadic operations. Any future improvements to `optional` could be accessed by users of `map::lookup` without modifying `map` . In particular, all of the features in [[P1255]](http://wg21.link/P1255) could be applied to the return value of `map::lookup` , including a `std::reference_or` function.
- The current version has a single observer ( `lookup` ) compared to two observers for the R0 design ( `get` and `get_ref` ). Since each observer requires four overloads ( `const` and non- `const` , each having a `key_type` or templated `K` parameter), the interface simplification is notable.
- The current specification of `lookup` is simple to specify and implement, making `lookup` easy to add to nonstandard map-like containers and new standard containers such as `flat_map` and even for random-access sequence containers, including `vector` and `deque` , should the committee choose to do that.

During the [2024-02-27 LEWGI (SG18) telecon](https://wiki.edg.com/bin/view/Wg21telecons2024/P3091#Library-Evolution-2024-02-27), unanimous consent was achieved for `lookup` returning `optional<T&>` (known as the Alternative Design in [[P3091R0]](https://wg21.link/p3091r0)):

> POLL: The alternative design with a smaller container API with extensions to `std::optional` should be pursued.

| SF | WF | N | WA | SA |
| --- | --- | --- | --- | --- |
| 6 | 4 | 0 | 0 | 0 |

## Free Functions Instead of Members

Providing the functionality described in this paper is possible using namespace-scope functions, without modifying `std::map` and `std::unordered_map` :

```cpp
template <class Map, class K, class... Args>
  typename optional<typename Map::mapped_type&> lookup(Map& m, const K& k);
template <class Map, class K, class... Args>
  typename optional<const typename Map::mapped_type&> lookup(const Map& m, const K& k);

auto x = lookup(m, k).value_or(v);
```

One benefit to this approach is that the namespace-scoped `lookup` template can handle any *map-like* dictionary type (i.e., a type having a `mapped_type` and a `find` method that returns an `iterator` pointing to a key-value `pair` ). Such an approach, however, has disadvantages.

- A global function is less intuitive because it puts `lookup` outside of the map interface.
- The short name can cause ADL issues.
- The *map-like* concept is novel and not used elsewhere in the Standard. Whether many classes model this concept and would therefore benefit from the generality is unclear.

# Implementation Experience

An implementation, with tests and usage examples, can be found at [https://github.com/phalpern/WG21-halpern/tree/main/P3091-map_lookup/code](https://github.com/phalpern/WG21-halpern/tree/main/P3091-map_lookup/code).

Some of the functionality can be found in Meta’s [[Folly]](https://github.com/facebook/folly/blob/323e467e2375e535e10bda62faf2569e8f5c9b19/folly/MapUtil.h#L35-L71) library.

# Wording

This wording is relative to the July 2025 working draft, [[N5014]](https://wg21.link/n5014).

## Feature-Test Macros

To the list in 17.3.2 [[version.syn]](https://wg21.link/N5014#version.syn)<sup>1</sup>, add:

> `#define __cpp_lib_map_lookup yyyymmL // also in <map>, <unordered_map>, <flat_map>`

## std::map Changes

In 23.4.3.1 [[map.overview]](https://wg21.link/N5014#map.overview)/2, insert the `lookup` element-access members:

> //
> 
> 23.4.3.3
> [[map.access]](https://wg21.link/N5014#map.access),
> element access
> 
> constexpr mapped_type& operator[](const key_type& x);
> 
> constexpr mapped_type& operator[](key_type&& x);
> 
> template<class K> constexpr mapped_type& operator[](K&& x);
> 
> constexpr mapped_type&       at(const key_type& x);
> 
> const constexpr mapped_type& at(const key_type& x) const;
> 
> template<class K> constexpr mapped_type&       at(const K& x);
> 
> template<class K> const constexpr mapped_type& at(const K& x) const;
> 
> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

At the end of 23.4.3.3 [[map.access]](https://wg21.link/N5014#map.access), add these descriptions:

> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

> > *Constraints*: For the third and fourth overloads, the *qualified-id* `Compare::is_transparent` is valid and denotes a type.

> > *Preconditions*: The expression `find(x)` is well-formed and has well-defined behavior.

> > *Returns*: `find(x)->second` if `contains(x)` is `true` , otherwise `nullopt` .

> > *Complexity*: Logarithmic.

## std::unordered_map Changes

In 23.5.3.1 [[unord.map.overview]](https://wg21.link/N5014#unord.map.overview)/3, insert the `lookup` element-access members:

> //
> 
> 23.5.3.3
> [[unord.map.elem]](https://wg21.link/N5014#unord.map.elem),
> element access
> 
> constexpr mapped_type& operator[](const key_type& x);
> 
> constexpr mapped_type& operator[](key_type&& x);
> 
> template<class K> constexpr mapped_type& operator[](K&& x);
> 
> constexpr mapped_type&       at(const key_type& x);
> 
> const constexpr mapped_type& at(const key_type& x) const;
> 
> template<class K> constexpr mapped_type&       at(const K& x);
> 
> template<class K> const constexpr mapped_type& at(const K& x) const;
> 
> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

At the end of 23.5.3.3 [[unord.map.elem]](https://wg21.link/N5014#unord.map.elem), add these descriptions:

> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

> > *Constraints*: For the third and fourth overloads, the *qualified-id*s `Hash::is_transparent` and `Pred::is_transparent` are valid and denote types.

> > *Preconditions*: The expression `find(x)` is well-formed and has well-defined behavior.

> > *Returns*: `find(x)->second` if `contains(x)` is `true` , otherwise `nullopt` .

> > *Complexity*: Average case constant, worst case linear in `size()` .

> > > **Editorial Note to LWG**: The *Complexity* clause is technically redundant because the return value is specified in terms of `find` . I added it here to match the format of `map::lookup` , which in turn matches the format of `map::at` . However, *Complexity* is currently missing for two of the overloads of `unordered_map::at` that are *not* specified in terms if `find` . Should that be an LWG issue?

## std::flat_map Changes

In 23.6.8.2 [[flat.map.defn]](https://wg21.link/N5014#flat.map.defn), insert the `lookup` element-access members:

> //
> 
> 23.6.8.6
> [[flat.map.access]](https://wg21.link/N5014#flat.map.access)
> 
> ,
> element access
> 
> constexpr mapped_type& operator[](const key_type& x);
> 
> constexpr mapped_type& operator[](key_type&& x);
> 
> template<class K> constexpr mapped_type& operator[](K&& x);
> 
> constexpr mapped_type& at(const key_type& x);
> 
> constexpr const mapped_type& at(const key_type& x) const;
> 
> template<class K> constexpr mapped_type& at(const K& x);
> 
> template<class K> constexpr const mapped_type& at(const K& x) const;
> 
> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

At the end of 23.6.8.6 [[flat.map.access]](https://wg21.link/N5014#flat.map.access), add these descriptions:

**Editorial Note to LWG**: The following description is character-for-character identical to that of `map::lookup` . The same is true for the existing descriptions of `operator[]` and `at` . Consider moving all of these descriptions into 23.2.7.1 [[associative.reqmts.general]](https://wg21.link/N5014#associative.reqmts.general) and, for consistency, move the `unordered_map` versions into 23.2.8.1 [[unord.req.general]](https://wg21.link/N5014#unord.req.general). **Resolution**: LWG agrees, but there should be a separate paper, as there is a sizable set of similar functions for which this change should be made.

> constexpr optional<mapped_type&>       lookup(const key_type& x);
> 
> constexpr optional<const mapped_type&> lookup(const key_type& x) const;
> 
> template<class K> constexpr optional<mapped_type&>       lookup(const K& x);
> 
> template<class K> constexpr optional<const mapped_type&> lookup(const K& x) const;

> > *Constraints*: For the third and fourth overloads, the *qualified-id* `Compare::is_transparent` is valid and denotes a type.

> > *Preconditions*: The expression `find(x)` is well-formed and has well-defined behavior.

> > *Returns*: `find(x)->second` if `contains(x)` is `true` , otherwise `nullopt` .

> > *Complexity*: Logarithmic.

# Acknowledgments

Thanks to Tomasz Kamiński for pushing me on the `optional<T&>` approach.

Thanks to Steve Downey for working with me to harmonize P2988 and P1255 with this paper.

Thanks to Lori Hughes for editing support.

# References

[Folly] Meta. folly/folly/MapUtil.h.

https://github.com/facebook/folly/blob/323e467e2375e535e10bda62faf2569e8f5c9b19/folly/MapUtil.h#L35-L71

[N5008] Thomas Köppe. 2025-03-15. Working Draft, Programming Languages —
C++.

https://wg21.link/n5008

[N5014] Thomas Köppe. 2025-08-05. Working Draft, Standard for
Programming Language C++.

https://wg21.link/n5014

[P1255] Steve Downey. A view of 0 or 1 elements:

views::nullable

And a concept to constrain

maybe

s.

http://wg21.link/P1255

[P2988R12] Steve Downey, Peter Sommerlad. 2025-04-04.
std::optional<T&>.

https://wg21.link/p2988r12

[P2988R4] Steve Downey, Peter Sommerlad. 2024-04-16.
std::optional<T&>.

https://wg21.link/p2988r4

[P2988R7] Steve Downey, Peter Sommerlad. 2024-09-10.
std::optional<T&>.

https://wg21.link/p2988r7

[P3091R0] Pablo Halpern. 2024-02-06. Better lookups for `map` and
`unordered_map`.

https://wg21.link/p3091r0

[P3091R2] Pablo Halpern. 2024-05-22. Better lookups for `map` and
`unordered_map`.

https://wg21.link/p3091r2

[P4139R2] Nathan Myers, Pablo Halpern. 2026-05-09. Better Name for
Better Lookups in P3091.

https://wg21.link/p4139r2

[P4139R3] Nathan Myers, Pablo Halpern. Better Name for Better Lookups in
P3091.

http://wg21.link/P4139R3

---

1. All citations to the Standard are to working draft N5014 unless otherwise specified.↩︎
