diff --git a/framework/global/tests/CMakeLists.txt b/framework/global/tests/CMakeLists.txt index f69ce1a324..00e1747c0c 100644 --- a/framework/global/tests/CMakeLists.txt +++ b/framework/global/tests/CMakeLists.txt @@ -57,6 +57,8 @@ set(MODULE_TEST_SRC ${CMAKE_CURRENT_LIST_DIR}/ringqueue_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/rpcqueue_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/geometry_tests.cpp + ${CMAKE_CURRENT_LIST_DIR}/sharedmap_tests.cpp + ${CMAKE_CURRENT_LIST_DIR}/sharedhashmap_tests.cpp ) include(SetupGTest) diff --git a/framework/global/tests/sharedhashmap_tests.cpp b/framework/global/tests/sharedhashmap_tests.cpp new file mode 100644 index 0000000000..eea2a0efe9 --- /dev/null +++ b/framework/global/tests/sharedhashmap_tests.cpp @@ -0,0 +1,209 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +#include +#include + +#include "types/sharedhashmap.h" + +using namespace muse; + +class Global_Types_SharedHashMapTests : public ::testing::Test +{ +public: +}; + +TEST_F(Global_Types_SharedHashMapTests, ErasePos_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedHashMap copy = original; + + // [WHEN] + auto it = std::as_const(copy).find(2); + copy.erase(it); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_FALSE(copy.contains(2)); + EXPECT_EQ(original.size(), 3); + EXPECT_TRUE(original.contains(2)); +} + +TEST_F(Global_Types_SharedHashMapTests, EraseRange_Full_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" }, { 2, "b" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.erase(copy.cbegin(), copy.cend()); + + // [THEN] + EXPECT_TRUE(copy.empty()); + EXPECT_EQ(original.size(), 2); +} + +TEST_F(Global_Types_SharedHashMapTests, EraseRange_Partial_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" }, { 2, "b" }, { 3, "c" }, { 4, "d" } }; + SharedHashMap copy = original; + + // [WHEN] + auto first = std::as_const(copy).find(2); + auto last = std::next(first); + copy.erase(first, last); + + // [THEN] + EXPECT_EQ(copy.size(), 3); + EXPECT_FALSE(copy.contains(2)); + EXPECT_EQ(original.size(), 4); + EXPECT_TRUE(original.contains(2)); +} + +TEST_F(Global_Types_SharedHashMapTests, EraseRange_Empty_WhileShared_IsNoOp) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" }, { 2, "b" } }; + SharedHashMap copy = original; + + // [WHEN] + auto it = std::as_const(copy).find(1); + copy.erase(it, it); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_EQ(original.size(), 2); +} + +TEST_F(Global_Types_SharedHashMapTests, InsertHint_AtEnd_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.insert(copy.cend(), { 2, "b" }); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_TRUE(copy.contains(2)); + EXPECT_EQ(original.size(), 1); + EXPECT_FALSE(original.contains(2)); +} + +TEST_F(Global_Types_SharedHashMapTests, InsertHint_AtExistingElement_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.insert(std::as_const(copy).find(1), { 2, "b" }); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_TRUE(copy.contains(2)); + EXPECT_EQ(original.size(), 1); + EXPECT_FALSE(original.contains(2)); +} + +TEST_F(Global_Types_SharedHashMapTests, InsertHint_IntoEmptyMap_WhileShared) +{ + // [GIVEN] + SharedHashMap original; + SharedHashMap copy = original; + + // [WHEN] cbegin() == cend() on an empty map + ASSERT_EQ(copy.cbegin(), copy.cend()); + copy.insert(copy.cend(), { 1, "a" }); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_TRUE(original.empty()); +} + +TEST_F(Global_Types_SharedHashMapTests, EmplaceHint_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.emplace_hint(copy.cend(), 2, "b"); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_TRUE(copy.contains(2)); + EXPECT_EQ(original.size(), 1); +} + +TEST_F(Global_Types_SharedHashMapTests, TryEmplaceHint_NewKey_WhileShared) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.try_emplace(copy.cend(), 2, "b"); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_EQ(copy.at(2), "b"); + EXPECT_EQ(original.size(), 1); +} + +TEST_F(Global_Types_SharedHashMapTests, TryEmplaceHint_ExistingKey_WhileShared_DoesNotOverwrite) +{ + // [GIVEN] + SharedHashMap original { { 1, "a" } }; + SharedHashMap copy = original; + + // [WHEN] + copy.try_emplace(std::as_const(copy).find(1), 1, "z"); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_EQ(copy.at(1), "a"); +} + +TEST_F(Global_Types_SharedHashMapTests, Merge_WithMapSharingStorage) +{ + // [GIVEN] + SharedHashMap a { { 1, "a" }, { 2, "b" } }; + SharedHashMap b = a; // shares storage with a + + // [WHEN] + a.insert({ 3, "c" }); // detaches a from b + a.merge(b); + + // [THEN] + EXPECT_EQ(a.size(), 3); + EXPECT_TRUE(a.contains(1)); + EXPECT_TRUE(a.contains(2)); + EXPECT_TRUE(a.contains(3)); + EXPECT_EQ(b.size(), 2); + EXPECT_TRUE(b.contains(1)); + EXPECT_TRUE(b.contains(2)); +} diff --git a/framework/global/tests/sharedmap_tests.cpp b/framework/global/tests/sharedmap_tests.cpp new file mode 100644 index 0000000000..11fc42191f --- /dev/null +++ b/framework/global/tests/sharedmap_tests.cpp @@ -0,0 +1,275 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +#include +#include + +#include "types/sharedmap.h" + +using namespace muse; + +class Global_Types_SharedMapTests : public ::testing::Test +{ +public: +}; + +TEST_F(Global_Types_SharedMapTests, ErasePos_Middle_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] + auto it = std::as_const(copy).find(2); + copy.erase(it); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_FALSE(copy.contains(2)); + EXPECT_EQ(original.size(), 3); + EXPECT_TRUE(original.contains(2)); +} + +TEST_F(Global_Types_SharedMapTests, ErasePos_Begin_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] + copy.erase(copy.cbegin()); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_FALSE(copy.contains(1)); + EXPECT_EQ(original.size(), 3); + EXPECT_TRUE(original.contains(1)); +} + +TEST_F(Global_Types_SharedMapTests, EraseRange_Partial_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" }, { 4, "d" } }; + SharedMap copy = original; + + // [WHEN] + copy.erase(std::as_const(copy).find(2), std::as_const(copy).find(4)); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_TRUE(copy.contains(1)); + EXPECT_FALSE(copy.contains(2)); + EXPECT_FALSE(copy.contains(3)); + EXPECT_TRUE(copy.contains(4)); + EXPECT_EQ(original.size(), 4); +} + +TEST_F(Global_Types_SharedMapTests, EraseRange_Full_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" } }; + SharedMap copy = original; + + // [WHEN] + copy.erase(copy.cbegin(), copy.cend()); + + // [THEN] + EXPECT_TRUE(copy.empty()); + EXPECT_EQ(original.size(), 2); +} + +TEST_F(Global_Types_SharedMapTests, EraseRange_BeginToKey_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] begin() paired with a key + copy.erase(copy.cbegin(), std::as_const(copy).find(3)); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_FALSE(copy.contains(1)); + EXPECT_FALSE(copy.contains(2)); + EXPECT_TRUE(copy.contains(3)); + EXPECT_EQ(original.size(), 3); +} + +TEST_F(Global_Types_SharedMapTests, EraseRange_KeyToEnd_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] a key paired with end() + copy.erase(std::as_const(copy).find(2), copy.cend()); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_TRUE(copy.contains(1)); + EXPECT_FALSE(copy.contains(2)); + EXPECT_FALSE(copy.contains(3)); + EXPECT_EQ(original.size(), 3); +} + +TEST_F(Global_Types_SharedMapTests, EraseRange_Empty_WhileShared_IsNoOp) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] + auto it = std::as_const(copy).find(2); + copy.erase(it, it); + + // [THEN] + EXPECT_EQ(copy.size(), 3); + EXPECT_EQ(original.size(), 3); +} + +TEST_F(Global_Types_SharedMapTests, InsertHint_AtEnd_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 2, "b" } }; + SharedMap copy = original; + + // [WHEN] + copy.insert(copy.cend(), { 3, "c" }); + + // [THEN] + EXPECT_EQ(copy.size(), 3); + EXPECT_TRUE(copy.contains(3)); + EXPECT_EQ(original.size(), 2); + EXPECT_FALSE(original.contains(3)); +} + +TEST_F(Global_Types_SharedMapTests, InsertHint_AtBegin_WhileShared) +{ + // [GIVEN] + SharedMap original { { 2, "b" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] + copy.insert(copy.cbegin(), { 1, "a" }); + + // [THEN] + EXPECT_EQ(copy.size(), 3); + EXPECT_TRUE(copy.contains(1)); + EXPECT_EQ(original.size(), 2); + EXPECT_FALSE(original.contains(1)); +} + +TEST_F(Global_Types_SharedMapTests, InsertHint_Middle_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" }, { 3, "c" } }; + SharedMap copy = original; + + // [WHEN] + copy.insert(std::as_const(copy).find(3), { 2, "b" }); + + // [THEN] + EXPECT_EQ(copy.size(), 3); + EXPECT_TRUE(copy.contains(2)); + EXPECT_EQ(original.size(), 2); + EXPECT_FALSE(original.contains(2)); +} + +TEST_F(Global_Types_SharedMapTests, InsertHint_IntoEmptyMap_WhileShared) +{ + // [GIVEN] + SharedMap original; + SharedMap copy = original; + + // [WHEN] cbegin() == cend() on an empty map + ASSERT_EQ(copy.cbegin(), copy.cend()); + copy.insert(copy.cend(), { 1, "a" }); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_TRUE(original.empty()); +} + +TEST_F(Global_Types_SharedMapTests, EmplaceHint_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" } }; + SharedMap copy = original; + + // [WHEN] + copy.emplace_hint(copy.cend(), 2, "b"); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_TRUE(copy.contains(2)); + EXPECT_EQ(original.size(), 1); +} + +TEST_F(Global_Types_SharedMapTests, TryEmplaceHint_NewKey_WhileShared) +{ + // [GIVEN] + SharedMap original { { 1, "a" } }; + SharedMap copy = original; + + // [WHEN] + copy.try_emplace(copy.cend(), 2, "b"); + + // [THEN] + EXPECT_EQ(copy.size(), 2); + EXPECT_EQ(copy.at(2), "b"); + EXPECT_EQ(original.size(), 1); +} + +TEST_F(Global_Types_SharedMapTests, TryEmplaceHint_ExistingKey_WhileShared_DoesNotOverwrite) +{ + // [GIVEN] + SharedMap original { { 1, "a" } }; + SharedMap copy = original; + + // [WHEN] + copy.try_emplace(copy.cbegin(), 1, "z"); + + // [THEN] + EXPECT_EQ(copy.size(), 1); + EXPECT_EQ(copy.at(1), "a"); +} + +TEST_F(Global_Types_SharedMapTests, Merge_WithMapSharingStorage) +{ + // [GIVEN] + SharedMap a { { 1, "a" }, { 2, "b" } }; + SharedMap b = a; // shares storage with a + + // [WHEN] + a.insert({ 3, "c" }); // detaches a from b + a.merge(b); + + // [THEN] + EXPECT_EQ(a.size(), 3); + EXPECT_TRUE(a.contains(1)); + EXPECT_TRUE(a.contains(2)); + EXPECT_TRUE(a.contains(3)); + EXPECT_EQ(b.size(), 2); + EXPECT_TRUE(b.contains(1)); + EXPECT_TRUE(b.contains(2)); +} diff --git a/framework/global/types/sharedhashmap.h b/framework/global/types/sharedhashmap.h index 6858617a78..d72f1b787e 100644 --- a/framework/global/types/sharedhashmap.h +++ b/framework/global/types/sharedhashmap.h @@ -24,6 +24,7 @@ #include #include +#include namespace muse { template @@ -138,18 +139,6 @@ class SharedHashMap return find(key) != end(); } - template - bool containsAnyOf(BeginIt beginIt, EndIt endIt) const noexcept - { - for (auto it = beginIt; it != endIt; ++it) { - if (this->contains(*it)) { - return true; - } - } - - return false; - } - bool empty() const noexcept { return m_dataPtr->empty(); @@ -172,12 +161,31 @@ class SharedHashMap m_dataPtr->insert(std::forward(pair)); } - void insert(iterator first, iterator last) + void insert(const_iterator first, const_iterator last) { ensureDetach(); m_dataPtr->insert(first, last); } + iterator insert(const_iterator hint, const PairType& pair) + { + return withDetachedHint(hint, [this, &pair](const_iterator h) { return m_dataPtr->insert(h, pair); }); + } + + iterator insert(const_iterator hint, PairType&& pair) + { + return withDetachedHint(hint, [this, &pair](const_iterator h) { + return m_dataPtr->insert(h, std::forward(pair)); + }); + } + + void merge(SharedHashMap& source) + { + ensureDetach(); + source.ensureDetach(); + m_dataPtr->merge(*source.m_dataPtr); + } + void insert_or_assign(const KeyType& key, ValType&& val) { ensureDetach(); @@ -197,6 +205,37 @@ class SharedHashMap m_dataPtr->emplace(std::forward(args)...); } + template + iterator emplace_hint(const_iterator hint, Args&& ... args) + { + return withDetachedHint(hint, [this, &args ...](const_iterator h) { + return m_dataPtr->emplace_hint(h, std::forward(args)...); + }); + } + + template + std::pair try_emplace(const KeyType& key, Args&& ... args) + { + ensureDetach(); + return m_dataPtr->try_emplace(key, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const KeyType& key, Args&& ... args) + { + return withDetachedHint(hint, [this, &key, &args ...](const_iterator h) { + return m_dataPtr->try_emplace(h, key, std::forward(args)...); + }); + } + + template + iterator try_emplace(const_iterator hint, KeyType&& key, Args&& ... args) + { + return withDetachedHint(hint, [this, &key, &args ...](const_iterator h) { + return m_dataPtr->try_emplace(h, std::forward(key), std::forward(args)...); + }); + } + void clear() noexcept { ensureDetach(); @@ -209,14 +248,27 @@ class SharedHashMap m_dataPtr->erase(key); } - void erase(iterator first, iterator last) + iterator erase(const_iterator pos) { - ensureDetach(); - m_dataPtr->erase(first, last); + return withDetachedHint(pos, [this](const_iterator p) { return m_dataPtr->erase(p); }); + } + + void erase(const_iterator first, const_iterator last) + { + if (m_dataPtr.use_count() == 1) { + m_dataPtr->erase(first, last); + } else { + auto [dfirst, dlast] = detachAndReanchorRange(first, last); + m_dataPtr->erase(dfirst, dlast); + } } bool operator ==(const SharedHashMap& another) const noexcept { + if (m_dataPtr == another.m_dataPtr) { + return true; + } + return *m_dataPtr == *another.m_dataPtr; } @@ -239,6 +291,54 @@ class SharedHashMap m_dataPtr = std::make_shared(*m_dataPtr); } + // Detach-safe descriptor of a position + struct AtEnd {}; + using Anchor = std::variant; + + Anchor describeAnchor(const_iterator it) const + { + if (it == m_dataPtr->cend()) { + return AtEnd {}; + } + return it->first; + } + + const_iterator resolveAnchor(const Anchor& a) const + { + if (std::holds_alternative(a)) { + return m_dataPtr->cend(); + } + return m_dataPtr->find(std::get(a)); + } + + const_iterator detachAndReanchor(const_iterator hint) + { + const Anchor a = describeAnchor(hint); + ensureDetach(); + return resolveAnchor(a); + } + + template + auto withDetachedHint(const_iterator hint, Op&& op) + { + if (m_dataPtr.use_count() == 1) { + return op(hint); + } + + const_iterator dhint = detachAndReanchor(hint); + return op(dhint); + } + + std::pair detachAndReanchorRange(const_iterator first, const_iterator last) + { + const Anchor firstAnchor = describeAnchor(first); + const Anchor lastAnchor = describeAnchor(last); + + ensureDetach(); + + return { resolveAnchor(firstAnchor), resolveAnchor(lastAnchor) }; + } + DataPtr m_dataPtr = nullptr; }; } diff --git a/framework/global/types/sharedmap.h b/framework/global/types/sharedmap.h index 71206cb32b..bc1f0469de 100644 --- a/framework/global/types/sharedmap.h +++ b/framework/global/types/sharedmap.h @@ -24,6 +24,7 @@ #include #include +#include namespace muse { template @@ -135,6 +136,12 @@ class SharedMap return m_dataPtr->find(key); } + iterator find(const KeyType& key) noexcept + { + ensureDetach(); + return m_dataPtr->find(key); + } + iterator lower_bound(const KeyType& key) { ensureDetach(); @@ -184,6 +191,31 @@ class SharedMap m_dataPtr->insert(std::forward(pair)); } + void insert(const_iterator first, const_iterator last) + { + ensureDetach(); + m_dataPtr->insert(first, last); + } + + iterator insert(const_iterator hint, const PairType& pair) + { + return withDetachedHint(hint, [this, &pair](const_iterator h) { return m_dataPtr->insert(h, pair); }); + } + + iterator insert(const_iterator hint, PairType&& pair) + { + return withDetachedHint(hint, [this, &pair](const_iterator h) { + return m_dataPtr->insert(h, std::forward(pair)); + }); + } + + void merge(SharedMap& source) + { + ensureDetach(); + source.ensureDetach(); + m_dataPtr->merge(*source.m_dataPtr); + } + void insert_or_assign(const KeyType& key, ValType&& val) { ensureDetach(); @@ -203,6 +235,37 @@ class SharedMap m_dataPtr->emplace(std::forward(args)...); } + template + iterator emplace_hint(const_iterator hint, Args&& ... args) + { + return withDetachedHint(hint, [this, &args ...](const_iterator h) { + return m_dataPtr->emplace_hint(h, std::forward(args)...); + }); + } + + template + std::pair try_emplace(const KeyType& key, Args&& ... args) + { + ensureDetach(); + return m_dataPtr->try_emplace(key, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const KeyType& key, Args&& ... args) + { + return withDetachedHint(hint, [this, &key, &args ...](const_iterator h) { + return m_dataPtr->try_emplace(h, key, std::forward(args)...); + }); + } + + template + iterator try_emplace(const_iterator hint, KeyType&& key, Args&& ... args) + { + return withDetachedHint(hint, [this, &key, &args ...](const_iterator h) { + return m_dataPtr->try_emplace(h, std::forward(key), std::forward(args)...); + }); + } + void clear() noexcept { ensureDetach(); @@ -215,14 +278,27 @@ class SharedMap m_dataPtr->erase(key); } - void erase(iterator first, iterator last) + iterator erase(const_iterator pos) { - ensureDetach(); - m_dataPtr->erase(first, last); + return withDetachedHint(pos, [this](const_iterator p) { return m_dataPtr->erase(p); }); + } + + void erase(const_iterator first, const_iterator last) + { + if (m_dataPtr.use_count() == 1) { + m_dataPtr->erase(first, last); + } else { + auto [dfirst, dlast] = detachAndReanchorRange(first, last); + m_dataPtr->erase(dfirst, dlast); + } } bool operator ==(const SharedMap& another) const noexcept { + if (m_dataPtr == another.m_dataPtr) { + return true; + } + return *m_dataPtr == *another.m_dataPtr; } @@ -233,12 +309,20 @@ class SharedMap bool operator <(const SharedMap& another) const noexcept { - return m_dataPtr->operator <(another.m_dataPtr); + if (m_dataPtr == another.m_dataPtr) { + return false; + } + + return *m_dataPtr < *another.m_dataPtr; } bool operator >(const SharedMap& another) const noexcept { - return m_dataPtr->operator >(another.m_dataPtr); + if (m_dataPtr == another.m_dataPtr) { + return false; + } + + return *m_dataPtr > *another.m_dataPtr; } private: @@ -255,6 +339,61 @@ class SharedMap m_dataPtr = std::make_shared(*m_dataPtr); } + // Detach-safe descriptor of a position + struct AtBegin {}; + struct AtEnd {}; + using Anchor = std::variant; + + Anchor describeAnchor(const_iterator it) const + { + if (it == m_dataPtr->cbegin()) { + return AtBegin {}; + } + if (it == m_dataPtr->cend()) { + return AtEnd {}; + } + return it->first; + } + + const_iterator resolveAnchor(const Anchor& a) const + { + if (std::holds_alternative(a)) { + return m_dataPtr->cbegin(); + } + if (std::holds_alternative(a)) { + return m_dataPtr->cend(); + } + return m_dataPtr->find(std::get(a)); + } + + const_iterator detachAndReanchor(const_iterator hint) + { + const Anchor a = describeAnchor(hint); + ensureDetach(); + return resolveAnchor(a); + } + + template + auto withDetachedHint(const_iterator hint, Op&& op) + { + if (m_dataPtr.use_count() == 1) { + return op(hint); + } + + const_iterator dhint = detachAndReanchor(hint); + return op(dhint); + } + + std::pair detachAndReanchorRange(const_iterator first, const_iterator last) + { + const Anchor firstAnchor = describeAnchor(first); + const Anchor lastAnchor = describeAnchor(last); + + ensureDetach(); + + return { resolveAnchor(firstAnchor), resolveAnchor(lastAnchor) }; + } + DataPtr m_dataPtr = nullptr; }; }