4
\$\begingroup\$

dict objects in Python have an update method that can be used to extend one dictionary with all items from another, possibly overwriting entries that already exist. I find this function very useful from time to time.

The associative containers from the C++ standard library (std::map and std::unordered_map) do not provide a direct analogue for this functionality so I wrote the following utility functions.

Comments regarding any aspect of the code are welcome.

#include <cassert>
#include <utility>

namespace codereview
{

    template <typename DictT>
    void update(DictT& dst, const DictT& src)
    {
        for (const auto& [key, value] : src) {
            dst.insert_or_assign(key, value);
        }
    }

    template <typename DictT>
    void update(DictT& dst, DictT&& src) noexcept
    {
        // Equal allocators are a precondition for splicing nodes
        assert(src.get_allocator() == dst.get_allocator());
        dst.merge(src);
        while (!src.empty()) {
            auto node = src.extract(src.cbegin());
            dst.extract(node.key());
            dst.insert(std::move(node));
        }
    }

}  // namespace codereview
\$\endgroup\$
  • 1
    \$\begingroup\$ I asked about the weird interface on SO. Feel free to join in guys, this looks really interesting. Might as well be a bug. \$\endgroup\$ – Incomputable Nov 23 '17 at 20:56
3
\$\begingroup\$

You're not thinking laterally here. If you std::swap() your two inputs, then you can merge() the original into the new, and the contents of the new map will prevail.

Here's what I mean:

namespace codereview
{
    template <typename DictT>
    void update(DictT& dst, DictT src)
    {
        std::swap(dst, src);
        dst.merge(src);
    }

}  // namespace codereview
// Test
#include <iostream>
#include <map>

template<typename Map>
void print(const Map& x)
{
    for (auto const& i: x)
        std::cout << i.first << "=" << i.second << " ";
    std::cout << std::endl;
}

int main()
{
    using Map = std::map<int, const char*>;
    Map a { {0, "zero"}, {1, "one"} };
    const Map b { {0, "nothing"}, {2, "two"}};

    print(a);

    // test lvalue argument
    codereview::update(a, b);
    print(a);

    // test rvalue argument
    codereview::update(a, Map{{3, "three"}, {0, "nada"}});
    print(a);
}

Output

0=zero 1=one 
0=nothing 1=one 2=two 
0=nada 1=one 2=two 3=three 

Note that I changed the method signature to accept src by copy - the compiler will move-construct into src if it can, but if not, we'll need a copy so that we don't perturb the caller's version when we swap.

\$\endgroup\$
  • \$\begingroup\$ I see now. Really good solution. I kind of ignored effects of swap \$\endgroup\$ – Incomputable Nov 23 '17 at 18:00
  • \$\begingroup\$ Nice! This solution is indeed more elegant and probably also more efficient than mine. \$\endgroup\$ – 5gon12eder Nov 24 '17 at 14:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.