C++ Spaceship operator <=> and std::sort with user defined type

258 views Asked by At

I'm trying to use std::sort() on a vector of shared_ptr<Card>. I created a function that implements the spaceship operator. I couldn't simply overload it on Card because the container refers to shared_ptr<Card>, not the Card itself. The std::sort() works with a function that implements a < comparison, but not a <=> comparison.

std::strong_ordering compare(std::shared_ptr<Card> left, std::shared_ptr<Card> right)
{
    int leftManaCost = left->getManaCost();
    int rightManaCost = right->getManaCost();
    return std::tie(leftManaCost) <=> std::tie(rightManaCost);
}

bool compareJustMinor(std::shared_ptr<Card> left, std::shared_ptr<Card> right) 
{ 
    return (left->getManaCost() < right->getManaCost()); 
}

void Deck::Print()
{
    std::sort(cards.begin(), cards.end(), compare); //works with compareJustMinor

I´ll summarize the information that may be relevant

  1. Deck is a class that has a member std::vector<std::shared_ptr<Card>> cards;
  2. Card is a base class with a pure virtual function

I'm kind of rusty on C++ and new to the <=> operator, so I may be missing/misunderstanding something obvious.

Here is the error log:

>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7363,21): error C2678: binary '!': no operator found which takes a left-hand operand of type 'std::strong_ordering' (or there is no acceptable conversion)
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7355,1): message : could be 'built-in C++ operator!(bool)'
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7355,1): message : while trying to match the argument list '(std::strong_ordering)'
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7425): message : see reference to function template instantiation 'std::pair<_RanIt,_RanIt> std::_Partition_by_median_guess_unchecked<_RanIt,std::strong_ordering(__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)>(_RanIt,_RanIt,_Pr)' being compiled
1>        with
1>        [
1>            _RanIt=std::shared_ptr<Card> *,
1>            _Pr=std::strong_ordering (__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)
1>        ]
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7455): message : see reference to function template instantiation 'void std::_Sort_unchecked<std::shared_ptr<Card>*,std::strong_ordering(__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)>(_RanIt,_RanIt,__int64,_Pr)' being compiled
1>        with
1>        [
1>            _RanIt=std::shared_ptr<Card> *,
1>            _Pr=std::strong_ordering (__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)
1>        ]
1>D:\C++ Projects\MTG\Deck.cpp(99): message : see reference to function template instantiation 'void std::sort<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<_Ty>>>,std::strong_ordering(__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)>(const _RanIt,const _RanIt,_Pr)' being compiled
1>        with
1>        [
1>            _Ty=std::shared_ptr<Card>,
1>            _RanIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<std::shared_ptr<Card>>>>,
1>            _Pr=std::strong_ordering (__cdecl *)(std::shared_ptr<Card>,std::shared_ptr<Card>)
1>        ]
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7363,15): error C2088: '!': illegal for struct
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7367,81): error C2678: binary '!': no operator found which takes a left-hand operand of type 'std::strong_ordering' (or there is no acceptable conversion)
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7355,1): message : could be 'built-in C++ operator!(bool)'
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7355,1): message : while trying to match the argument list '(std::strong_ordering)'
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7367,75): error C2088: '!': illegal for struct
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7378,29): error C2451: a conditional expression of type 'std::strong_ordering' is not valid
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7378,29): message : No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7391,29): error C2451: a conditional expression of type 'std::strong_ordering' is not valid
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\algorithm(7391,29): message : No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1

There are 1 answers

0
Jan Schultke On

The comparator which std::sort expects should satisfy the requirement of a Compare, i.e. it should behave like a < operator.

Your code should work if you pass compare_just_minor to std::sort instead of compare. Here is a working example:

#include <memory>
#include <algorithm>
#include <vector>

struct Card {
    int mana_cost;
};

std::strong_ordering compare(std::shared_ptr<Card> a, std::shared_ptr<Card> b) {
    return a->mana_cost <=> b->mana_cost;
}

bool compare_just_minor(std::shared_ptr<Card> a, std::shared_ptr<Card> b) {
    return std::is_lt(compare(a, b));
}

int main() {
    std::vector<std::shared_ptr<Card>> cards;
    std::sort(cards.begin(), cards.end(), compare_just_minor);
}

See live example at Compiler Explorer.

That being said, the <=> operator is normally defined directly for a class, not for a smart pointer of that class:

struct Card {
    int mana_cost;
    // return std::strong_ordering, but we can let the compiler
    // figure out all the details
    friend auto operator<=>(const Card&, const Card&) = default;
};

// ...

// note: std::ranges::sort is more concise
std::ranges::sort(cards, [](const auto& p0, const auto& p1) {
    // < is automatically delegated to <=> for Card
    return *p0 < *p1;
});

The use of std::shared_ptr is also questionable. Chances are you could just use std::vector<Card> directly.


See also: What are the basic rules and idioms for operator overloading?