5

I want to insert new (unique) element into known place (generally somewhere in the middle) of an ordered associative container std::set/std::multiset/std::map/std::multimap using insert (w/ hint) or emplace_hint.

During insertion operation I absolutely sure, that the place to insert is right before the "hint" iterator. Generally I can compare any two non-neighbouring elements in the container, but this operation is strongly heavyweight. To avoid overhead imposed, I provide custom comparator for the container, which contains a references to pointers to both neigbouring elements (they always became known right before the insertion/emplacement operation).

#include <map>
#include <set>

static std::size_t counter = 0;

template< typename T >
struct less
{ 

    T const * const & pl;
    T const * const & pr;

    bool operator () (T const & l, T const & r) const
    {
        if (&l == &r) {
            return false;
        }
        if (pl) {
            if (&l == pl) {
                return true;
            } 
            if (&r == pl) {
                return false;
            }
        }
        if (pr) {
            if (&r == pr) {
                return true;
            }
            if (&l == pr) {
                return false;
            }   
        }
        ++counter;
        return l < r; // very expensive, it is desirable this line to be unrecheable
    }

};

#include <iostream>
#include <algorithm>
#include <iterator>

#include <cassert>

int main()
{
    using T = int;
    T const * pl = nullptr;
    T const * pr = nullptr;
    less< T > less_{pl, pr};
    std::set< T, less< T > > s{less_};
    s.insert({1, 2,/* 3, */4, 5});
    std::copy(std::cbegin(s), std::cend(s), std::ostream_iterator< T >(std::cout, " "));
    std::cout << '\n';
    auto const hint = s.find(4);
    // now I want to insert 3 right before 4 (and, of course, after 2)
    pl = &*std::prev(hint); // prepare comparator to make a cheap insertion
    pr = &*hint;
    // if hint == std::end(s), then pr = nullptr
    // else if hint == std::begin(s), then pl = nullptr
    // if I tried to insert w/o hint, then pl = pr = nullptr;
    {
        std::size_t const c = counter;
        s.insert(hint, 3);
        assert(counter == c);
    }
    std::copy(std::cbegin(s), std::cend(s), std::ostream_iterator< T >(std::cout, " "));
    std::cout << '\n';
}

Current libc++/libstdc++ implementations allows me to use described comparator, but is there undefined behaviour if I rely on their current behaviour? Can I rely, that insert (w/ hint parameter) or emplace_hint (and modern insert_or_assign/try_emplace w/ hint parameter for map/multimap) don't touch any other elements other then pointed by pl and pr? Is it implementation-defined thing?

Why I want this strange thing? IRL I tried to implement Fortune's algorithm to find Voronoi diagram on the plane using native STL's self-balanced binary search tries. std::set is used to store current state of a part of a so-called beach line: a chain of sorted endpoints. When I add a new endpoint I always know the place where to insert it right before the insertion. It would be best if I can add assert(false); before or throw std::logic_error{};/__builtin_unreachable(); instead of last return in comparator functor. I only can do it if there is corresponding logical guarantee. Can I do this?

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169

0 Answers0