CodeNebula
C++17stladvanced

Node Handle API

C++17 · Practical Modern · Compiler flag: -std=c++17

Explanation

Provides the ability to extract, modify, and reinsert nodes from associative and unordered containers without copying or moving the element's value. The `extract()` method returns an owning node handle, and `insert()` accepts node handles from compatible containers.

Real-World Scenario

Transfer user records between an active-users map and an archived-users map without copying large value objects.

Quick Snippet

snippet.cpp
#include <map>
#include <string>

std::map<int, std::string> active = {{1, "Alice"}, {2, "Bob"}};
std::map<int, std::string> archived;

// Move node from active to archived without copying the string
auto node = active.extract(1);
archived.insert(std::move(node));
// active: {2, "Bob"}, archived: {1, "Alice"}

Full Example

node_handle.cpp
// CodeNebula - C++17 Example: Node Handles (extract / insert)
// Compile: g++ -std=c++17 -Wall -Wextra -o example node_handle.cpp

// Demonstrates C++17 node handles for associative containers. extract()
// removes a node without deallocation; insert() with a node handle transfers
// ownership. This enables moving elements between containers without copying,
// and also allows modifying keys of map elements in-place.

#include <iostream>
#include <map>
#include <set>
#include <string>

template <typename Map>
void print_map(const std::string& label, const Map& m) {
    std::cout << "  " << label << ": ";
    for (const auto& [k, v] : m) std::cout << "{" << k << ":" << v << "} ";
    std::cout << (m.empty() ? "(empty)" : "") << std::endl;
}

template <typename Set>
void print_set(const std::string& label, const Set& s) {
    std::cout << "  " << label << ": ";
    for (const auto& v : s) std::cout << v << " ";
    std::cout << (s.empty() ? "(empty)" : "") << std::endl;
}

int main()
{
    // ------------------------------------------------------------------
    // 1. Extract and insert: move nodes between maps
    // ------------------------------------------------------------------
    std::cout << "=== Move Nodes Between Maps ===" << std::endl;
    std::map<int, std::string> src = {{1, "one"}, {2, "two"}, {3, "three"}};
    std::map<int, std::string> dst = {{10, "ten"}};

    print_map("src before", src);
    print_map("dst before", dst);

    // Extract node with key 2 from src
    auto node = src.extract(2);
    std::cout << "  Extracted key " << node.key() << " -> \"" << node.mapped() << "\"" << std::endl;

    // Insert the node into dst (no copy, no allocation)
    dst.insert(std::move(node));

    print_map("src after", src);
    print_map("dst after", dst);

    // ------------------------------------------------------------------
    // 2. Modify a key using node handles (impossible without extract)
    // ------------------------------------------------------------------
    std::cout << "\n=== Modify Key In-Place ===" << std::endl;
    std::map<int, std::string> m = {{1, "alpha"}, {2, "beta"}, {3, "gamma"}};
    print_map("before", m);

    // Change key 2 to key 20
    auto nh = m.extract(2);
    nh.key() = 20;  // Modify the key!
    m.insert(std::move(nh));
    print_map("after (2->20)", m);

    // ------------------------------------------------------------------
    // 3. Extract by iterator
    // ------------------------------------------------------------------
    std::cout << "\n=== Extract by Iterator ===" << std::endl;
    auto it = m.find(3);
    auto nh2 = m.extract(it);
    std::cout << "  Extracted: {" << nh2.key() << ":" << nh2.mapped() << "}" << std::endl;
    print_map("remaining", m);

    // Re-insert with modified value
    nh2.mapped() = "GAMMA";
    m.insert(std::move(nh2));
    print_map("re-inserted", m);

    // ------------------------------------------------------------------
    // 4. Node handles with std::set
    // ------------------------------------------------------------------
    std::cout << "\n=== Set Node Handles ===" << std::endl;
    std::set<std::string> s1 = {"apple", "banana", "cherry"};
    std::set<std::string> s2 = {"date", "elderberry"};

    print_set("s1 before", s1);
    print_set("s2 before", s2);

    auto sn = s1.extract("banana");
    s2.insert(std::move(sn));

    print_set("s1 after", s1);
    print_set("s2 after", s2);

    // ------------------------------------------------------------------
    // 5. merge() transfers all compatible nodes
    // ------------------------------------------------------------------
    std::cout << "\n=== merge() ===" << std::endl;
    std::map<int, std::string> a = {{1, "A"}, {2, "B"}};
    std::map<int, std::string> b = {{2, "X"}, {3, "C"}, {4, "D"}};

    print_map("a before", a);
    print_map("b before", b);

    a.merge(b);  // Moves non-conflicting nodes from b to a

    print_map("a after merge", a);
    print_map("b after merge", b);  // Key 2 remains (conflict)

    return 0;
}

/*
Expected output:

=== Move Nodes Between Maps ===
  src before: {1:one} {2:two} {3:three}
  dst before: {10:ten}
  Extracted key 2 -> "two"
  src after: {1:one} {3:three}
  dst after: {2:two} {10:ten}

=== Modify Key In-Place ===
  before: {1:alpha} {2:beta} {3:gamma}
  after (2->20): {1:alpha} {3:gamma} {20:beta}

=== Extract by Iterator ===
  Extracted: {3:gamma}
  remaining: {1:alpha} {20:beta}
  re-inserted: {1:alpha} {3:GAMMA} {20:beta}

=== Set Node Handles ===
  s1 before: apple banana cherry
  s2 before: date elderberry
  s1 after: apple cherry
  s2 after: banana date elderberry

=== merge() ===
  a before: {1:A} {2:B}
  b before: {2:X} {3:C} {4:D}
  a after merge: {1:A} {2:B} {3:C} {4:D}
  b after merge: {2:X}
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example node_handle.cpp