6
\$\begingroup\$

The following is my attempt to implement a insert_sorted() C++ function that accepts any sorted STL container and item and inserts it at the right position in the container:

template<template<typename, typename, typename> typename C, typename T1, typename Comp, typename A, typename T2>
inline typename C<std::enable_if_t<std::is_convertible_v<T1, std::remove_reference_t<T2>>, T1>, Comp, A>::iterator
insert_sorted (C<T1, Comp, A> & c, T2 && item)
{
 return c.insert (c.end(), std::forward<T2> (item));
}
template<template<typename, typename> typename C, typename T1, typename A, typename T2, typename Comp = std::less<T1>>
inline typename C<std::enable_if_t<std::is_convertible_v<T1, std::remove_reference_t<T2>>, T1>, A>::iterator
insert_sorted (C<T1, A> & c, T2 && item, Comp comp = Comp{})
{
 return c.insert (std::upper_bound (std::begin (c), std::end (c), static_cast<T1>(item), comp), std::forward<T2> (item));
}

These two compile and work fine; the first overload works for set-like containers, while the second for vector-like containers.

I was wondering if you see any drawbacks to this solution and if there is a more generic way to approach the problem, that is, is it possible to write a single insert_sorted() function that does the same as the above two?

EDIT: Thanks to the comments, I was able to replace the two template functions above with the following:

template<typename C, typename T, typename Comp = std::less<T>, 
 typename = std::enable_if<std::is_convertible_v<std::remove_reference_t<T>, typename C::value_type>>>
 inline auto insert_sorted (C & c, T && item, Comp comp = Comp{})
{
 return c.insert (std::upper_bound (std::begin (c), std::end (c), item, comp), std::forward<T> (item));
}

Thank you all for your help!

Toby Speight
87.1k14 gold badges104 silver badges322 bronze badges
asked Mar 15, 2021 at 15:07
\$\endgroup\$
0

2 Answers 2

4
\$\begingroup\$

inline is redundant when writing template functions.


When we use std::begin() and std::end(), we should always consider whether we actually want to use begin() and end() of the type's namespace instead (argument-dependent lookup, aka ADL), with fallback to std:

using std::begin;
using std::end;
return c.insert(std::upper_bound(begin(c), end(c), item, comp),
 std::forward<T>(item));

When we're inserting to a sorted container such as std::set, we probably want to use the container's key_compare as default, rather than std::less<T>. That will require splitting back into two functions again. I prefer to split out a helper that returns an appropriate default comparator:

#include <functional>
template<typename C>
auto comparator_for(const C& c)
 requires requires { c.key_comp(); }
{
 return c.key_comp();
}
template<typename C>
auto comparator_for(const C&)
{
 return std::less<typename C::value_type>{};
}

We can then use it in an overload that defaults the parameter:

#include <algorithm>
#include <concepts>
#include <iterator>
#include <utility>
template<typename C, typename T, typename Comp>
auto insert_sorted(C& c, T&& item, Comp comp)
 requires std::convertible_to<T, typename C::value_type>
{
 using std::begin;
 using std::end;
 return c.insert(std::upper_bound(begin(c), end(c), item, comp),
 std::forward<T>(item));
}
template<typename C, typename T>
auto insert_sorted(C& c, T&& item)
{
 return insert_sorted(c, std::forward<T>(item), comparator_for(c));
}

(I've used modern Concepts syntax rather than std::enable_if, as I find that easier to read).

answered Apr 5, 2021 at 8:08
\$\endgroup\$
2
\$\begingroup\$

Just to present an alternative way of doing things: instead of using insert() and std::upper_bound(), you could use push_back() and std::inplace_merge(). If you are going to provide a ranges-like interface anyway, perhaps you could use std::ranges and copy the interface of std::ranges::inplace_merge() as much as possible:

template<std::ranges::bidirectional_range R, class T, class Comp = std::ranges::less, class Proj = std::identity>
requires std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
std::ranges::borrowed_iterator_t<R>
insert_sorted(R&& r, T&& item, Comp comp = {}, Proj proj = {}) {
 using std::end;
 using std::prev;
 r.push_back(item);
 return std::ranges::inplace_merge(r, prev(end(r)), comp, proj);
}

Note that this doesn't work for std::set, but it does for std::list.

answered Sep 6, 2021 at 10:00
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.