4

Background:

I am trying to implement a function, let's call it mysort, that takes variable number of arguments of same type and sort them. For example the code below should print 1 2 3

template <typename ... Args>
void mysort(Args& ... args);
int main(int, char**)
{
    int x = 3;
    int y = 1;
    int z = 2;
    mysort(x, y, z);
    cout << x << ' ' << y << ' ' << z << endl;
    return 0;
}

What I have tried:

Here is the solution I came up with

#include <array>
#include <algorithm>
#include <functional>

template <typename T, typename ... Args>
void mysort(T& t, Args& ... args)
{
    constexpr size_t n = sizeof...(Args) + 1;
    std::array<std::reference_wrapper<T>, n> temp = { std::ref(t), std::ref(args)... };
    std::sort(temp.begin(), temp.end());
}

But this solution doesn't work because what std::swap does with std::reference_wrapper is not what I expect (I expect any changes to std::reference_wrapper should change the object for which std::reference_wrapper was created).

Everything starts working after adding

namespace std
{
    void swap(std::reference_wrapper<int>& a, std::reference_wrapper<int>& b) noexcept
    {
        std::swap(a.get(), b.get());
    }
}

I'm not sure if the standard requires std::sort to use std::swap, so I think this is not a complete solution and is implementation-dependent.

Summarizing:

My idea of a std::reference_wrapper is that it should behave just like a reference, but that doesn't seem to be the case. Here are my questions:

  1. What is the best way to implement mysort?
  2. Why doesn't the standard provide a std::swap specification for std::reference_wrapper that would swap underlying objects?

Thank you in advance for your answers!

ArthurBesse
  • 325
  • 1
  • 12
  • 5
    Before std::swap, operator= already doesn't do what you think: it rebinds (on purpose). – Marc Glisse Apr 23 '23 at 17:06
  • 1
    `temp` will end up being a sorted list of references to your original variables https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/operator%3D. `std::reference` wrapper doesn't behave like a reference, it allows storing a reference where it wouldn't normally be possible to store one, to do that it needs to be copyable/assignable – Alan Birtles Apr 23 '23 at 17:08
  • *My idea of a std::reference_wrapper is that it should behave just like a reference, but that doesn't seem to be the case.* That is, indeed, not the case. – Eljay Apr 23 '23 at 17:25
  • The actual use case wants something else: Usualy you use a container of reference wrappers because you cant have a contianer of references and then you want `std::sort` to shuffle the elements in the container not what they refer to – 463035818_is_not_an_ai Apr 23 '23 at 17:49

2 Answers2

5

The point of std::reference_wrapper is that it is copyable and assignable, "assignable" in the sense that a reference wrapper can be rebound to a new value the way that a normal reference cannot.

You're doing the opposite of what it is intended for. In your case when the implementation of std::sort swaps items in the temporary array it is rebinding the references, but this is not what you want. You want it to swap the actual items not the references.

One way to do this would be to let std::sort sort copies of the arguments and then assign them back to your reference arguments in sorted order:

template <typename T, typename ... Args>
void mysort(T& t, Args& ... args)
{
    constexpr size_t n = sizeof...(Args) + 1;
    std::array<T, n> temp = { t, args... };
    std::sort(temp.begin(), temp.end());
    std::tie(t, args...) = std::tuple_cat(temp);
}

Here I wrap them in a tuple so I can assign via std::tie. There may be some way to do this with fewer copies, but this is what came to mind.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
-1

Basically, std::reference_wrapper is an old poorly designed class that currently serves certain simple purposes, like in std::bind/bind_front or in construction of std::thread, where it is used to imply that the variables aren't copied but taken by reference. And one creates them via std::ref/cref.

I just don't recommend ever using std::reference_wrapper aside from template cases where it is used to tell the difference whether user wants to forward a copy or a reference to the object.

If you want a reference imitating class that performs an assign/move-through rather than rebind that's up to you to implement. Just you should be aware that it can be problematic in certain situations.

Also don't ever write any kind of

namespace std
{
    void swap(std::reference_wrapper<int>& a, std::reference_wrapper<int>& b) noexcept
    {
        std::swap(a.get(), b.get());
    }
}

It might break a lot of code that you import.

ALX23z
  • 4,456
  • 1
  • 11
  • 18
  • 2
    In what way is `std::reference_wrapper` poorly designed? It does exactly what it needs to do and no more? Extra functionality would be wasteful in the use cases it was designed for – Alan Birtles Apr 23 '23 at 18:55
  • @AlanBirtles it was designed way before `std::thread` and `std::bind` existed. And it just failed to find any proper use. It was simply reused for these objects as it fitted their construction scheme. – ALX23z Apr 23 '23 at 19:08
  • Actually `boost::bind`, `boost::thread` and `boost::ref` were all released at the same time in version 1.25.0 https://www.boost.org/doc/libs/1_31_0/. Even if it did predate them it doesn't mean it's poorly designed. You still haven't stated what's poor about it's design? – Alan Birtles Apr 23 '23 at 20:49
  • @AlanBirtles the class doesn't do anything useful, except for this specific purpose which makes it more of an internal service class rather than an API that should be exposed to users. – ALX23z Apr 24 '23 at 03:04
  • It fulfills that purpose exactly as needed though so seems perfectly designed? Maybe you actually mean the language is poorly designed making this class necessary? – Alan Birtles Apr 24 '23 at 06:25
  • @AlanBirtles you again miss the point. The problem is that the class shouldn't be a part of the stl's API. Also, the cases, where it helps were obsolute too as they were added together with introduction of lambdas, making them largely pointless addition. – ALX23z Apr 24 '23 at 07:34
  • Maybe I am just being stupid, who knows but I myself have used `std:: reference_wrapper` in the implementation of some classes and it made my code simpler by not having to implement it myself so it seems to be that's it's neither poorly designed or obsolete. You've still not justified what about it is poorly designed – Alan Birtles Apr 24 '23 at 07:40
  • @AlanBirtles just replace it with raw pointer. Or an `observer_ptr` if you want to be fancy or the codebase has bad practice of calling new/delete manually. – ALX23z Apr 24 '23 at 07:52
  • But a pointer doesn't have the same semantics, it can be null for example https://stackoverflow.com/questions/26766939/what-is-the-difference-between-stdreference-wrapper-and-a-simple-pointer – Alan Birtles Apr 24 '23 at 08:33
  • @AlanBirtles you cannot just replace it with raw pointer. But its not like something requires complex change. It's purely cosmetics. That the reference wrapper cannot be null but is rebindable, makes it weird. Normally, one ought to have neither or both. – ALX23z Apr 24 '23 at 09:32
  • Beyond being nullable, raw pointers need to be deferenced to get a value out of them so they won't work as stand-ins for their values in standard algorithms and containers. Now you could wrap them in a struct and define the operations you need on the struct, but then you are just implementing your own version of reference_wrapper. – jwezorek Apr 24 '23 at 18:11
  • @jwezorek I've never seen `reference_wrapper` being used in standart algos. And there is no point in using them in containers as they function as pointers with mildly different syntax. – ALX23z Apr 24 '23 at 20:14