Search code examples
c++sortingvariadicin-place

C++ Variadic Function To Sort Arguments In-place


While implementing a variation of a binary search problem, I needed to reorder slicing points (i.e start, mid, end) so that they are stored within the corresponding variable (e.g. (1,5,2) -> (1,2,5)). This is fairly simple to do with a few if statements and swaps. However as a thought experiment, I'm now interested in generalizing this to work with n many T type variables. I started experimenting with some intuitive solutions and as a starting place, I came up with this template function:

template<typename T>
void 
sortInPlace(
    std::function<bool (const T&, const T&)> compareFunc,
    T& start, 
    T& mid, 
    T& end)
    {
        std::vector<T> packed {start, mid, end};
        std::sort(packed.begin(), packed.end(), compareFunc);
        auto packedAsTuple = make_tuple(packed[0], packed[1], packed[2]);
        std::tie(start, mid, end) = packedAsTuple;
    }

And when I ran the following, using typedef std::pair<int,int> Pivot:

//Comparison function to sort by pair.first, ascending:
std::function<bool(const Pivot&, const Pivot&)>
  comp =[](const Pivot & a, const Pivot & b) {
     return std::get < 0 > (a) < std::get < 0 > (b);
  };

int main(){
    Pivot a(8,1);
    Pivot b(2,3);
    Pivot c(4,6);

    sortInPlace(comp,a,b,c);
}

This turns out to work as intended:

a after sort: 2, 3
b after sort: 4, 6
c after sort: 8, 1

Ideally, the following step is to convert this template into a variadic template but I'm having trouble achieving this. I also have a few things that bother me regarding the current version:

  1. The usage of std::vector was an arbitrary decision. I've done this because it's unclear to me what structure/container is the best to use in packing of these values. Seems to me that the choice of structure needs to be constructed, sorted and unpacked/converted to tuples easily and I'm not sure if there is such a magical structure out there.
  2. Just to get something going, I had to settle with manually packing/unpacking the arguments. It is also unclear to me how to use std::tie or any other unpacking/moving operation with variable number (i.e. unknown in compile-time) of elements.
  3. While there is no particular reason to exclusively use stl functions/structures, I'd be surprised to learn that there is no intuitive way to achieve this using the abstractions provided within stl. As a result, I'm more interested in achieving my goal using minimal help outside of stl.

I embarked on this thought experiment expecting to end up with a syntactically-correct version of std::move(std::sort({x,y,z}, comp), {x,y,z}) and considering where my research has led me so far, I'm starting to think I'm over-complicating this problem. Any help, insight or suggestion would be much appreciated!


Solution

  • One possible C++17 solution with std::sort that generalizes your example:

    template<class Comp, class... Ts>
    void my_sort(Comp comp, Ts&... values) {
        using T = std::common_type_t<Ts...>;
        T vals[]{std::move(values)...};
    
        std::sort(std::begin(vals), std::end(vals), comp);
    
        auto it = std::begin(vals);
        ((values = std::move(*it++)), ...);
    }
    
    using Pivot = std::pair<int, int>;
    
    const auto comp = [](Pivot a, Pivot b) {
        return std::get<0>(a) < std::get<0>(b);
    };
    
    Pivot a(8, 1);
    Pivot b(2, 3);
    Pivot c(4, 6);
    
    my_sort(comp, a, b, c);
    

    If the number N of parameters in the pack is small, you don't need std::sort at all. Just a series of (hardcoded) comparisons (and for small N the minimum number of comparisons is known exactly) will do the job - see sec. 5.3 Optimum sorting of Knuth's TAOCP vol. 3.