// Copyright 2025 Huawei Cloud Computing Technology Co., Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP #define INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP #include #include #include #include #include #include #include #include // IWYU pragma: keep #include "gsl/gsl" /// \brief Backmap a container of Values to Keys using a given Converter, and /// provide an interface for quick lookup of values by keys. /// \tparam TKey Result of conversion. Stored by value, std::hash /// MUST be available. /// \tparam TValue Source data for conversion. Stored by pointer, the /// source container MUST stay alive. template class BackMap final { public: template using IsKeyWithError = std::bool_constant and std::is_convertible_v>; /// \brief Converter from TValue to TKey. The return type may be different /// from TKey, in such a case Converter is considered a function that DOES /// return TKey, but may fail and return std::nullopt or an unexpected. /// TResult must be either the same type as TKey, or support conversions /// from TKey and to bool. template requires(std::is_same_v or IsKeyWithError::value) using Converter = std::function; explicit BackMap() = default; BackMap(BackMap const&) = delete; BackMap(BackMap&&) = delete; auto operator=(BackMap const&) -> BackMap& = delete; auto operator=(BackMap&&) -> BackMap& = delete; ~BackMap() = default; /// \brief Create a BackMap by iterating over container and applying /// Converter. /// \param container Container to iterate over. begin() and end() methods /// must be available. /// \param converter Converter to apply to elements of the container. /// \return BackMap on success or std::nullopt on failure. In particular, if /// any exception is thrown from internal containers or converter returns an /// error, std::nullopt is returned. template [[nodiscard]] static auto Make(TContainer const* const container, Converter const& converter) noexcept -> std::unique_ptr { if (container == nullptr or converter == nullptr) { return nullptr; } auto const size = std::distance(container->begin(), container->end()); try { auto back_map = std::make_unique(); back_map->keys_.reserve(size); back_map->mapping_.reserve(size); for (auto const& value : *container) { std::optional key = BackMap::Convert(converter, value); if (not key.has_value()) { return nullptr; } if (auto inserted = back_map->keys_.insert(*std::move(key)); inserted.second) { // References and pointers to data stored in the container // are only invalidated by erasing that element, even when // the corresponding iterator is invalidated: // https://en.cppreference.com/w/cpp/container/unordered_set#:~:text=invalidated%20by%20erasing%20that%20element // According to ^, this approach is safe: back_map->mapping_.insert_or_assign(&*inserted.first, &value); } } return back_map; } catch (...) { return nullptr; } } template requires(std::is_invocable_v) [[nodiscard]] static auto Make(TContainer const* const container, TConverter const& converter) noexcept -> std::unique_ptr { using TResult = std::invoke_result_t; return Make(container, converter); } /// \brief Obtain all available keys. [[nodiscard]] auto GetKeys() const noexcept -> std::unordered_set const& { return keys_; } /// \brief Obtain a pointer to the value corresponding to the given key. /// Copy free. /// \return If the key is known, a pointer to the value is returned, /// otherwise std::nullopt. [[nodiscard]] auto GetReference(TKey const& key) const noexcept -> std::optional> { if (auto it = keys_.find(key); it != keys_.end()) { return mapping_.at(&*it); } return std::nullopt; } /// \brief Obtain the set of values corresponding to given keys. If a key /// isn't known to the container, it is ignored. Perform deep copy of /// referenced values. [[nodiscard]] auto GetValues(std::unordered_set const& keys) const noexcept -> std::unordered_set { std::unordered_set result; result.reserve(keys.size()); for (auto const& key : keys) { if (auto value = GetReference(key)) { result.emplace(*value.value()); } } return result; } using Iterable = std::unordered_map, gsl::not_null>; /// \brief Obtain an iterable key-value set where values correspond to the /// given keys. If a key isn't known to the container, it is ignored. Copy /// free. [[nodiscard]] auto IterateReferences( std::unordered_set const* keys) const noexcept -> Iterable { Iterable result; result.reserve(keys->size()); for (auto const& key : *keys) { if (auto value = GetReference(key)) { result.insert_or_assign(&key, value.value()); } } return result; } private: std::unordered_set keys_; std::unordered_map> mapping_; template [[nodiscard]] static auto Convert(Converter const& converter, TValue const& value) noexcept -> std::optional { if constexpr (not std::is_same_v) { TResult converted = std::invoke(converter, value); if (not static_cast(converted)) { return std::nullopt; } return *std::move(converted); } else { return std::invoke(converter, value); } } }; #endif // INCLUDED_SRC_UTILS_CPP_BACK_MAP_HPP