c++findc++20contains

C++20 contains and get value VS find


I understand that some containers in C++, such as map or unordered_map, have a mechanism that allows accessing values directly if you have the key. I'm not sure if this is entirely correct...

The point here is that I want to use the contains method to check that the element exists and then directly retrieve the value with map[key] or map.at[key].

I read somewhere random that doing this is bad practice because it would be performing two searches instead of just one... So I had the question... does actually having the key serve any purpose? Will access not be immediate? Does find iterate over all elements until it finds the key or value?

What is better? example contains

if (storageMap.contains(key)) {
    return storageMap.at(key);
}

Vs example find

auto it = storageMap.find(key);
if (it != storageMap.end()) {
    return it.second;
}

If you could explain to me why one is better than the other, it would be perfect. Alternatively, if both methods are equivalent, explaining why would also be useful. It would also be helpful to know if the compiler will make example contains are efficient as example find


Solution

  • I would say that method 2. with find is more preferable in your situation. In your snippet, you are accessing the element right away.

    In the case of std::map:

    1. With contains + at will perform two BST tree traversals 2 x O(log N) operations. Internally 1x find + 1x lower_bound
    2. With find will perform one BST traversal 1 x O(log N) + O(1) one iterator access operation. Internally just find.

    However, if you do not need to access the found element at all, but rather want to return a boolean existence flag, contains would make more sense. In this case, the performance of find and contains will be identical with contains being more expressive and easier to read.

    Here is the GCC implementation of contains. You can see that it's just a wrapper over find:

    #if __cplusplus > 201703L
          ///@{
          /**
           *  @brief  Finds whether an element with the given key exists.
           *  @param  __x  Key of (key, value) pairs to be located.
           *  @return  True if there is an element with the specified key.
           */
          bool
          contains(const key_type& __x) const
          { return _M_t.find(__x) != _M_t.end(); }
    
          template<typename _Kt>
        auto
        contains(const _Kt& __x) const
        -> decltype(_M_t._M_find_tr(__x), void(), true)
        { return _M_t._M_find_tr(__x) != _M_t.end(); }
          ///@}
    #endif
    

    Implementation of at is using the lower_bound:

          /**
           *  @brief  Access to %map data.
           *  @param  __k  The key for which data should be retrieved.
           *  @return  A reference to the data whose key is equivalent to @a __k, if
           *           such a data is present in the %map.
           *  @throw  std::out_of_range  If no such data is present.
           */
          mapped_type&
          at(const key_type& __k)
          {
        iterator __i = lower_bound(__k);
        if (__i == end() || key_comp()(__k, (*__i).first))
          __throw_out_of_range(__N("map::at"));
        return (*__i).second;
          }