---
title: "Constructing owning nodehandle s without containers"
document: P4309R0
date: 2026-07-15
audience: LEWG
reply-to:
  - "Michael Florian Hava <mfh.cpp@gmail.com>"
---

Whilst the existing `node-handle` API works fairly well, there is one annoyance we’ve encountered multiple times: being unable to directly create an owning `node-handle` without constructing a container. Creating a container just to emplace one element that is immediately extracted, after which the container is destroyed is silly … yet is necessary with the current API, if a separation from the target-container (one of the motivations of this API) is desired.

We think this use-case should be supported directly and propose an extension to the `nodehandle` API.

```cpp
                 Before
                                                         Proposed
using immovable = …; 
using container = node-based-container<immovable>; 
using node = container::node_type; 
auto factory(auto & input_stream) -> node { 
 auto in{input_stream.next()}; 
 if(can_ignore(in)) return {}; //empty handle 
 container c; //allocate expensive data structure 😬 
```

```cpp
  //then allocate the actual object 
  c.emplace(move(in)); 
  auto nh{c.extract(c.begin())}; 
  if(filter(nh.value())) return {}; //empty handle 
  return nh; 
}
                                               using immovable = …; 
                                               using container = node-based-container<immovable>; 
                                               using node = container::node_type; 
                                               auto factory(auto & input_stream) -> node { 
                                                 auto in{input_stream.next()}; 
                                                 if(can_ignore(in)) return {}; //empty handle 
```

:::wording-add

<ins>//allocate only the actual object 😊</ins> node nh{in_place, move(in)}; if(filter(nh.value())) return {}; //empty handle return nh; }

:::

[RISC Software GmbH, Softwarepark 32a, 4232 Hagenberg, Austria, michael.hava@risc-software.at](mailto:michael.hava@risc-software.at)

This feature could be provided in different ways: factory functions , or additional constructors for 2 `node-handle`. We prefer the latter option as it is the most logical to us - after all, what we want is a way to construct an owning `node-handle`. Additionally this design avoids having to come up with a new name for this operation.

We propose to extended the `node-handle` „meta-template“ that all actual handle types have to conform to in the following way : 3

Both proposed constructors are marked explicit to prevent implicit dynamic allocations and take a leading tag parameter - �1⃣ in order to disambiguate the construction without additional arguments from the existing default constructor and 2⃣� to mark the ability to pass an allocator that should be used for allocating the actual node.

The basic idea is that these constructors work in the following way : 4

Note that `node` will often be associated with multiple, compatible containers who differ - for this use case - only in inconsequential details . As we don’t want a container to be created, we don’t 5 care which compatible container would be used.

```cpp
template<unspecified> 
struct node-handle { 
  using value_type     = see below;     //not present for map containers  
  using key_type       = see below;     //only present for map containers 
  using mapped_type    = see below;     //only present for map containers 
  using allocator_type = see below; 
private: 
  using container-node-type = unspecified;                                         //exposition-only 
  using ator-traits = allocator_traits<allocator_type>;                            //exposition-only 
  typename ator-traits::template rebind_traits<container-node-type>::pointer ptr_; //exposition-only 
  optional<allocator_type> alloc_;                                                 //exposition-only 
public: 
  node-handle() noexcept : ptr_(), alloc_() {} 
  template<typename... Args> 
  explicit 
  node-handle(in_place_t, Args &&... args); 
                                                                                               //this proposal 1⃣ 
  template<typename... Args> 
  explicit 
  node-handle(allocator_arg_t, const allocator_type & alloc, Args &&... args); 
                                                                                               //this proposal 2⃣ 
  node-handle(node-handle &&) noexcept; 
  auto operator=(node-handle &&) -> node-handle &; 
  ~node-handle(); 
```

:::wording-add

… }; node nh{in_place, arg...}; <ins>//is equivalent to:</ins> node nh{[] { some-compatible-container<node> c; c.emplace(arg...); return c.extract(c.begin()); }()}; node nh{allocator_arg, alloc, arg...}; <ins>//is equivalent to:</ins> node nh{[] { some-compatible-container<node> c{alloc}; c.emplace(arg...); return c.extract(c.begin()); }()};

:::

Either as free functions, or as static member functions of the respective containers. 2

Note: assumes P3049 being accepted. 3

The code shown is illustrative, actual code differs between three groups of node-based containers 4 (maps&sets, doubly-linked list, singly-linked list) due to differences in their `emplace` and `extract` APIs.

Hasher, comparisons, support for duplicated keys.

## Proposed Wording

### [version.syn]

### [container.node]

## Acknowledgements

:::wording-add

<ins>#define __cpp_lib_node_construct YYYYMML //also in <map>, <set>, <unordered_map>, <unordered_set>, <list>,</ins> <ins><forward_list></ins>

:::

**[DRAFTING NOTE: Adjust the placeholder value as needed to denote the proposal’s date of adoption.]**

**??.?.?.? Overview** **[container.node.overview]**

…

2 If a node handle is not empty, then it contains an allocator that is equal to the allocator of the container when the element was extracted. If a node handle is empty, it contains no allocator.

:::wording-add

… template<unspecified> class node-handle { … using container-node-type = unspecified; // exposition only using ator-traits = allocator_traits<allocator_type>; // exposition only typename ator-traits::template rebind_traits<container-node-type>::pointer ptr_; // exposition only optional<allocator_type> alloc_; // exposition only public: // [container.node.cons], constructors, copy, and assignment constexpr node-handle() noexcept : ptr_(), alloc_() {} <ins>template<class... Args></ins> <ins>constexpr explicit node-handle(in_place_t, Args&&... args);</ins> <ins>template<class... Args></ins> <ins>constexpr explicit node-handle(allocator_arg_t, const allocator_type& a, Args&&... args);</ins> constexpr node-handle(node-handle&&) noexcept; … };

:::

:::wording-add

??.?.?.? Constructors, copy, and assignment [container.node.cons] <ins>template<class... Args></ins> <ins>constexpr explicit node-handle(in_place_t, Args&&... args);</ins> <ins>template<class... Args></ins> <ins>constexpr explicit node-handle(allocator_arg_t, const allocator_type& a, Args&&... args);</ins>

:::

:::wording-add

<ins>1</ins> <ins>Let a be allocator_type() for the overload with no parameter a.</ins>

:::

:::wording-add

<ins>2</ins> <ins>Let C be a container type so that C::node_type is the same as node-handle and let c be an instance of C initialized with a.</ins>

:::

**[DRAFTING NOTE: node handles are shared between compatible containers, but as we don’t want instances of** `C` **to be** **constructed, it doesn’t matter which compatible container type is used as** `C`**.]**

:::wording-add

<ins>3</ins> <ins>Effects: Constructs a node-handle object as if by extracting an object t from c, where t has been emplaced into c with</ins> <ins>std::forward<Args>(args)....</ins>

:::

:::wording-add

<ins>4</ins> <ins>Remarks: Implementations should perform no more than one memory allocation.</ins>

:::

```cpp
constexpr node-handle(node-handle&& nh) noexcept;
```

:::wording

<del>1</del><ins>5</ins> Effects: Constructs a node-handle object initializing ptr_ with nh.ptr_. Move constructs alloc_ with nh.alloc_. Assigns nullptr to nh.ptr_ and assigns nullopt to nh.alloc_.

:::

<!-- tomd:glyph-placeholders: placeholders=2 skipped_coincident=2 skipped_code_section=2 -->
