Is there a nice way to assign std::minmax(a, b) to std::tie(a, b)?

Solution 1:

You can use an initializer list for minmax:

std::tie(a, b) = std::minmax({a, b});

This causes temporary objects to be created, just like when using unary plus, but has the benefit that it works with types lacking the unary plus operator too.

using namespace std::string_view_literals;

auto [a, b] = std::make_pair("foo"sv, "bar"sv);
std::tie(a, b) = std::minmax({a, b});
std::cout << "a: " << a << ", b: " << b << '\n';

Output:

a: bar, b: foo

Could it be that this is the wrong direction and just saying if (a >= b) { std::swap(a, b); } would be the best approach here?

I'd make it if(b < a) std::swap(a, b); because of the Compare1 requirement, but yes, I suspect that'll be faster and it's still very clear what you want to accomplish.


[1] Compare [...] The return value of the function call operation applied to an object of a type satisfying Compare, when contextually converted to bool, yields true if the first argument of the call appears before the second in the strict weak ordering relation induced by this type, and false otherwise.

Solution 2:

You can enforce this with a certain level of brevity as follows.

std::tie(a, b) = std::minmax(+a, +b);

std::cout << "a: " << a << ", b: " << b << '\n';

Explanation: the builtin unary plus operator, for the sake of symmetry with its unary minus sibling, returns its operand by value (it also performs the usual arithmetic conversions, but that doesn't apply to ints). This means it has to create a temporary, even though this temporary is nothing but a copy of the operand. But for the usage of minmax in this example, it's sufficient: swapping references here doesn't assign through anymore, because the references on the right hand side (the const int& arguments passed to minmax) don't refer to the same objects as those on the left hand side (inside the tuple of references created by std::tie).

The output is as desired:

a: 5, b: 7

Solution 3:

Sometimes, taking a step back and finding a different way pays off:

if (b < a)
    std::iter_swap(&a, &b);

That's concise and generally more efficient, certainly at least on par. Maybe pack it into its own function:

template <class T>
void reorder(T& a, T& b)
noexcept(noexcept(b < a, void(), std::iter_swap(&a, &b))) {
    if (b < a)
        std::iter_swap(&a, &b);
}

I'm using std::iter_swap() so I don't have to use the using std::swap; swap(a, b) two-step for generality in pre-C++2a, which introduces customization point objects making that obsolete.