C++ Dilinde spaceship operator(3way comparison operator)
- Yusuf Hançar
- Oct 20, 2024
- 7 min read
C++20 ile birlikte gelen 3way comparison operator, diğer bir deyişle "spaceship operator", karşılaştırma işlemlerini daha basit ve etkili bir şekilde gerçekleştirmemizi sağlar. Bu operatör, <=> token ile temsil edilir ve programlama diline önemli bir kolaylık getirir.
3way comparison operator, C++ dilinin operatör düzeyinde yapılan bir genişlemesidir. Bu, geliştiricilere karmaşık karşılaştırma işlemlerini daha az kodla gerçekleştirme olanağı sunar. Bir nesnenin sıralanabilir olup olmadığını belirlemek için tek bir operatör tanımlamak, karşılaştırmaların yönetimini oldukça kolaylaştırır.
Bu operatörü kullanabilmek için programımıza #include <compare> başlık dosyasını eklememiz gerekmektedir.
C++20 ile birlikte, derleyiciler artık kullanıcı tanımlı türler için default olarak karşılaştırma operatörlerini otomatik olarak oluşturabilir. Örneğin, yalnızca operator<=> tanımlandığında, derleyici diğer karşılaştırma operatörlerini (örneğin operator<, operator>, operator==, vb.) otomatik olarak sağlar.
C++20 öncesi karşılaştırma operatorleri bool döndürür.
En büyük problem custom türlere dönük karşılaştırmalardır.
#include <cmath>
#include <iostream>
int main ()
{
double d1{ NAN }; // std::isnan
double d2{ 4.5 };
std::boolalpha(std::cout);
std::cout << "d1 < d2 : " << (d1 < d2) << "\n";
std::cout << "d1 > d2 : " << (d1 > d2) << "\n";
std::cout << "d1 >= d2 : " << (d1 >= d2) << "\n";
std::cout << "d1 <= d2 : " << (d1 <= d2) << "\n";
std::cout << "d1 == d2 : " << (d1 == d2) << "\n";
std::cout << "d1 != d2 : " << (d1 != d2) << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
d1 < d2 : false
d1 > d2 : false
d1 >= d2 : false
d1 <= d2 : false
d1 == d2 : false
d1 != d2 : true
*****************************
ret = strcmp(s1, s2); ret > 0 ret < 0 0
#include <iostream>
#include <compare>
int main()
{
std::string str1{ "smart" };
std::string str2{ "code" };
//str1 <=> str2 strong ordering türden
if (str1 <=> str2 < 0)
{
std::cout << "yes" << "\n";
}
else
{
std::cout << "no " << "\n";
}
}
*****************************
AÇIKLAMA : c++20
*****************************
CEVAP : no
*****************************
template <typename T>
void print()
{
std::cout << typeid(T{} <=> T{}).name() << "\n";
}
int main()
{
print<int>();
}
*****************************
AÇIKLAMA : c++20
*****************************
CEVAP : std::strong_ordering
*****************************
template <typename T>
void print()
{
std::cout << typeid(T{} <=> T{}).name() << "\n";
}
int main()
{
print<std::string>();
}
*****************************
AÇIKLAMA : c++20
*****************************
CEVAP : std::strong_ordering
*****************************
template <typename T>
void print()
{
std::cout << typeid(T{} <=> T{}).name() << "\n";
}
int main()
{
print<double>();
}
*****************************
AÇIKLAMA : c++20
*****************************
CEVAP : std::partial_ordering
*****************************
C++ 20 ile karşılaştırma operatorleri ikiye ayrıldı.
primary operatorler reverse edilebilirler.
derleyiciler a == b yerine b == a yaparak isim aramayı ona göre yapabilir.
#include <iostream>
class Data {
public:
bool operator==(int value) const
{
return (value == 5);
}
};
int main()
{
Data m;
bool b1 = (m == 5); // Data sınıfından bir nesne ile int karşılaştırması
std::cout << std::boolalpha;
std::cout << "m == 5: " << b1 << "\n";
return 0;
}
class Data {
public :
bool operator==(int value) const
{
return (value == 5);
}
};
int main()
{
Data m;
bool b1 = (m == 5);
bool b2 = (m != 5); // !(m == 5) şeklinde yazar
bool b3 = (5 == m); // (m == 5) şeklinde yazar.
bool b4 = (5 != m);
}
*****************************
AÇIKLAMA : sınıfın != operator fonksiyonu yok zaten hata verecektir.
Ayrıca == üye operator fonksiyon olduğu için sol operand sınıf türünden değilse bunun bir fonksiyon çağrısına dönüştürülme ihtimali yoktur.(5 == m) gibi
*****************************
CEVAP : c++17 de sadece ilki legal
*****************************
secondary operatorler rewritable özelliktedir.
derleyici bu opertorlerle oluşturulan ifadeleri o gurubun primary operatorune yapılan çağrıya dönüştürebilir.
a != b ifadesini !(a == b) şeklinde dönüştürebilir.
Bu durumda isim arama ile != kullanmamıza rağmen derleyici == i bulup değilini alacağı için legal olacaktır.
class Data {
public :
Data(int val) : m_val{ val }{}
auto operator<=>(const Data&) const = default;
private :
int m_val;
};
int main()
{
std::boolalpha(std::cout);
Data m1{ 12}, m2{ 24 };
std::cout << "m1 < m2 : " << (m1 < m2) << "\n";
std::cout << "m1 <= m2 : " << (m1 <= m2) << "\n";
std::cout << "m1 > m2 : " << (m1 > m2) << "\n";
std::cout << "m1 >= m2 : " << (m1 >= m2) << "\n";
std::cout << "m1 == m2 : " << (m1 == m2) << "\n";
std::cout << "m1 != m2 : " << (m1 != m2) << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
m1 < m2 : true
m1 <= m2 : true
m1 > m2 : false
m1 >= m2 : false
m1 == m2 : false
m1 != m2 : true
*****************************
class Date {
public :
Date(int d, int m, int y) : m_y{ y }, m_m{ m }, m_d{ d }{}
auto operator<=>(const Date&) const = default;
private :
int m_y, m_m, m_d;
};
int main ()
{
std::boolalpha(std::cout);
std::cout << (Date{ 12, 11, 2023 } > Date{ 12, 10, 2023 }) << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : true
*****************************
|
1- strong_ordering
|
2- weak_ordering
|
3- partial_ordering
|
#include <iostream>
#include <compare>
template <typename T, typename U>
void print_compare(const T& t, const U& u)
{
using result_type = std::compare_three_way_result_t<T, U>;
std::string stype = typeid(result_type).name();
std::cout << "compare result_type : " << stype << "\n";
auto result = t <=> u;
std::cout << "result of comparison : ";
if (result == 0)
{
if (std::is_same_v<result_type, std::strong_ordering>)
{
std::cout << "equal" << "\n";
}
else
{
std::cout << "equivalent" << "\n";
}
}
else if (result > 0)
{
std::cout << "greater" << "\n";
}
else if (result < 0)
{
std::cout << "less" << "\n";
}
else
{
std::cout << "unordered" << "\n";
}
};
int main ()
{
print_compare(12, 6);
}
*****************************
CEVAP :
compare result_type : std::strong_ordering
result of comparison : greater
*****************************
class Person {
public :
Person(const char* p, int a) : name{ p }, age{ a } {}
//std::strong_ordering operator<=>(const Person&) const; bu şekilde de yazabiliriz.
// auto yazabilmemizin nedeni tüm return lerin dönüş değeri aynı olduğu içindir!!
auto operator<=>(const Person& oth) const
{
if (auto cmp = name <=> oth.name; cmp != 0)
{
return cmp;
}
return age <=> oth.age;
}
private :
std::string name;
int age;
};
int main ()
{
Person p1{ "smart", 17 };
Person p2{ "code", 20 };
std::boolalpha(std::cout);
std::cout << (p1 > p2) << "\n"; // derleyici (p1 <=> p2 > 0) şeklinde yazar.
}
*****************************
AÇIKLAMA : burdaki > operatoru secondary operator kendi kategorisindeki primary operator ile ifade edilebilmektedir.
*****************************
CEVAP : true
*****************************
class Person {
public :
Person(const char* p, int a) : name{ p }, age{ a } {}
std::partial_ordering operator<=>(const Person& oth) const
{
if (auto cmp = name <=> oth.name; cmp != 0)
{
return cmp;
}
return age <=> oth.age;
}
private :
std::string name;
int age;
};
int main ()
{
Person p1{ "smart", 56 };
Person p2{ "code", 40 };
std::boolalpha(std::cout);
std::cout << (p1 > p2) << "\n";
}
*****************************
AÇIKLAMA : strong_ordering partial_ordering e dönüşebildiği için legal
*****************************
CEVAP : true
*****************************
class Person {
public :
Person(const char* p, int a, double s) : name{ p }, age{ a }, salary{ s } {}
auto operator<=>(const Person& oth) const
{
if (auto cmp = name <=> oth.name; cmp != 0)
{
return cmp;
}
if (auto cmp = age <=> oth.age; cmp != 0)
{
return cmp;
}
// inconsistent deduction for auto return type: ‘std::strong_ordering’ and then ‘std::partial_ordering’
return salary <=> oth.salary; // burda syntax error
}
private :
std::string name;
int age;
double salary;
};
int main ()
{
Person p1{ "smart", 56, 60'000. };
Person p2{ "code", 40, 10'000. };
std::boolalpha(std::cout);
std::cout << (p1 > p2) << "\n";
}
*****************************
AÇIKLAMA : deduction ile ilgili hata vardır.
*****************************
class Person {
public :
Person(const char* p, int a, double s) : name{ p }, age{ a }, salary{ s } {}
std::partial_ordering operator<=>(const Person& oth) const
{
if (auto cmp = name <=> oth.name; cmp != 0)
{
return cmp;
}
if (auto cmp = age <=> oth.age; cmp != 0)
{
return cmp;
}
return salary <=> oth.salary;
}
private :
std::string name;
int age;
double salary;
};
int main ()
{
Person p1{ "smart", 56, 60'000. };
Person p2{ "code", 40, 10'000. };
std::boolalpha(std::cout);
std::cout << (p1 > p2) << "\n";
}
*****************************
AÇIKLAMA : double dan partial elde edeceğimiz için partial_ordering return ederek çözdük.
*****************************
CEVAP : true
*****************************
İkinci çözüm type_traits.
n tane türü alır ve türlerin ortak türünün çıkarımını yapar.
class Data {
private:
std::string m_name;
double m_wage;
public:
Data(const std::string& name, double wage)
: m_name(name), m_wage(wage) {}
// Trailing return type ile 3-way karşılaştırma operatörü
auto operator<=>(const Data& oth) const
-> std::common_comparison_category_t<decltype(m_name <=> oth.m_name), decltype(m_wage <=> oth.m_wage)>
{
if (auto cmp = m_name <=> oth.m_name; cmp != 0)
{
return cmp;
}
return m_wage <=> oth.m_wage; // Common comparison type
}
};
int main()
{
Data p1{ "smart", 60000.0 };
Data p2{ "code", 10000.0 };
std::cout << std::boolalpha;
std::cout << (p1 > p2) << "\n";
return 0;
}
*****************************
CEVAP : true
*****************************
[[nodiscard]] attribute ile kullanım ve spaceship operator
class Data {
public :
constexpr Data(int val = 0) : m_val{ val }{}
[[nodiscard]]auto operator<=>(const Data&) const = default;
private :
int m_val;
};
int main()
{
constexpr Data m1{ 345 };
constexpr Data m2{ 355 };
m1 <=> m2;
}
*****************************
AÇIKLAMA :derleyici uyarı mesajı verir.
*****************************
CEVAP : warning: ignoring return value of ‘constexpr auto Data::operator<=>(const Data&) const’, declared with attribute ‘nodiscard’ [-Wunused-result]
*****************************
ÇÖZÜM :
1. auto result = m1 <=> m2;
2. if ((m1 <=> m2) < 0) { // ... }
*****************************
Comments