لقد مر الاجتماع في كولونيا ، وتم تخفيض معيار C ++ 20 إلى شكل شبه كامل (على الأقل حتى ظهور ملاحظات خاصة) ، وأود التحدث عن أحد الابتكارات القادمة. هذه هي الآلية التي تسمى عادة
العامل <=> (يعرفها المعيار على أنها "مشغل مقارنة ثلاثي الاتجاهات" ، لكن لها لقب غير رسمي "سفينة الفضاء") ، لكنني أعتقد أن نطاقها أوسع بكثير.
لن يكون لدينا مشغل جديد - فسوف تخضع دلالات المقارنات لتغييرات مهمة على مستوى اللغة نفسها.
حتى لو لم تتمكن من إخراج أي شيء آخر من هذه المقالة ، تذكر هذا الجدول:
الآن سيكون لدينا مشغل جديد ،
<=> ، ولكن الأهم من ذلك هو أن المشغلين منظمون الآن. هناك عوامل تشغيل أساسية وهناك عوامل مشتقة - لكل مجموعة قدراتها الخاصة.
سنتحدث عن هذه الميزات لفترة وجيزة في المقدمة وسننظر في مزيد من التفاصيل في الأقسام التالية.
يمكن
قلب المشغلين الأساسيين (أي إعادة كتابة بترتيب عكسي للمعلمات). يمكن
إعادة كتابة البيانات المشتقة من خلال البيان الأساسي المقابل. لا يقوم المرشحون المحولون أو المعاد كتابتهم بإنشاء وظائف جديدة ؛ فهم ببساطة بدائل على مستوى الكود المصدري ويتم اختيارهم من
مجموعة ممتدة من المرشحين . على سبيل المثال ، يمكن الآن تقييم التعبير
a <9 كـ
a.operator <=> (9) <0 ، والتعبير
10! = B as
! Operator == (b، 10) . هذا يعني أنه سيكون من الممكن الاستغناء عن عامل أو اثنين من المشغلين حيث ، من أجل تحقيق نفس السلوك ، أصبح من الضروري الآن كتابة 2 أو 4 أو 6 أو حتى 12 عاملًا يدويًا.
سيتم عرض نظرة عامة
موجزة على القواعد مع جدول بكل التحولات الممكنة.
يمكن تعريف كل من العوامل الأساسية ومشتقاتها على أنها
الافتراضية . في حالة المشغلين الأساسيين ، هذا يعني أنه سيتم تطبيق المشغل على كل عضو في ترتيب الإعلان ؛ في حالة العوامل المشتقة ، سيتم استخدام المرشحين المعاد كتابتهم.
تجدر الإشارة إلى أنه لا يوجد مثل هذا التحول الذي يمكن من خلاله التعبير عن عامل من نوع واحد (أي المساواة أو الطلب) من حيث المشغل من نوع آخر. بمعنى آخر ، الأعمدة في جدولنا لا تعتمد بأي حال على بعضها البعض. لن يتم مطلقًا تقييم التعبير
a == b كمشغل <=> (a، b) == 0 ضمنيًا (ولكن ، بالطبع ، لا شيء يمنعك من تحديد
المشغل الخاص بك
== عبر
المشغل <=> إذا كنت تريد).
فكر في مثال صغير يوضح كيف يبدو الرمز قبل تطبيق الوظيفة الجديدة وبعده.
سنكتب نوع سلسلة غير حساس لحالة الأحرف ،
CIString ، والتي يمكن مقارنة كائناتها مع بعضها البعض ومع
char const * .
في C ++ 17 ، لمهمتنا ، نحتاج إلى كتابة 18 وظيفة مقارنة:
class CIString { string s; public: friend bool operator==(const CIString& a, const CIString& b) { return assize() == bssize() && ci_compare(asc_str(), bsc_str()) == 0; } friend bool operator< (const CIString& a, const CIString& b) { return ci_compare(asc_str(), bsc_str()) < 0; } friend bool operator!=(const CIString& a, const CIString& b) { return !(a == b); } friend bool operator> (const CIString& a, const CIString& b) { return b < a; } friend bool operator>=(const CIString& a, const CIString& b) { return !(a < b); } friend bool operator<=(const CIString& a, const CIString& b) { return !(b < a); } friend bool operator==(const CIString& a, const char* b) { return ci_compare(asc_str(), b) == 0; } friend bool operator< (const CIString& a, const char* b) { return ci_compare(asc_str(), b) < 0; } friend bool operator!=(const CIString& a, const char* b) { return !(a == b); } friend bool operator> (const CIString& a, const char* b) { return b < a; } friend bool operator>=(const CIString& a, const char* b) { return !(a < b); } friend bool operator<=(const CIString& a, const char* b) { return !(b < a); } friend bool operator==(const char* a, const CIString& b) { return ci_compare(a, bsc_str()) == 0; } friend bool operator< (const char* a, const CIString& b) { return ci_compare(a, bsc_str()) < 0; } friend bool operator!=(const char* a, const CIString& b) { return !(a == b); } friend bool operator> (const char* a, const CIString& b) { return b < a; } friend bool operator>=(const char* a, const CIString& b) { return !(a < b); } friend bool operator<=(const char* a, const CIString& b) { return !(b < a); } };
في C ++ 20 ، يمكنك القيام بـ 4 وظائف فقط:
class CIString { string s; public: bool operator==(const CIString& b) const { return s.size() == bssize() && ci_compare(s.c_str(), bsc_str()) == 0; } std::weak_ordering operator<=>(const CIString& b) const { return ci_compare(s.c_str(), bsc_str()) <=> 0; } bool operator==(char const* b) const { return ci_compare(s.c_str(), b) == 0; } std::weak_ordering operator<=>(const char* b) const { return ci_compare(s.c_str(), b) <=> 0; } };
سوف أخبركم بكل معنى الكلمة ، بمزيد من التفصيل ، ولكن أولاً ، دعنا نعود قليلاً ونتذكر كيف عملت المقارنات مع معيار C ++ 20.
مقارنات في المعايير من C ++ 98 إلى C ++ 17
عمليات المقارنة لم تتغير كثيرا منذ إنشاء اللغة. كان لدينا ستة مشغلين:
== ،! = ،
< ،
> ،
<= و
> = . يحدد المعيار كل واحد منهم لأنواع مدمجة ، ولكن بشكل عام يطيعون نفس القواعد. عند تقييم أي تعبير
@ b (حيث يكون
@ هو واحد من ستة عوامل مقارنة) ، يبحث المترجم عن وظائف الأعضاء والوظائف المجانية والمرشحين المدمجين المسمى
@ عامل ، والذي يمكن استدعاؤه بالنوع
A أو
B بالترتيب المحدد. يتم اختيار المرشح الأنسب منهم. هذا كل شيء. في الواقع ، عمل
جميع المشغلين بنفس الطريقة: العملية
< لم تختلف عن
<< .
هذه المجموعة البسيطة من القواعد سهلة التعلم. جميع المشغلين مستقلون تمامًا ومكافئون. لا يهم ما نعرفه نحن البشر حول العلاقة الأساسية بين
== و
! = العمليات. من حيث اللغة ، هذا واحد. نحن نستخدم التعابير. على سبيل المثال ، نحدد المشغل
! = من خلال
== :
bool operator==(A const&, A const&); bool operator!=(A const& lhs, A const& rhs) { return !(lhs == rhs); }
وبالمثل ، من خلال المشغل
< نحدد جميع عوامل العلاقة الأخرى. نحن نستخدم هذه التعابير لأنه ، على الرغم من قواعد اللغة ، لا نعتبر جميع المشغلين الستة متكافئين. نحن نقبل أن اثنين منهم أساسي (
== و
< ) ، ومن خلالهما يتم التعبير عن كل الآخرين بالفعل.
في الواقع ، فإن مكتبة القوالب القياسية مبنية بالكامل على هاتين المشغلتين ، ويحتوي العدد الهائل من الأنواع في الكود المستغَل على تعريفات لأحدهما فقط أو كليهما.
ومع ذلك ، فإن المشغل
< غير مناسب جدًا للدور الأساسي لسببين.
أولاً ، لا يمكن ضمان قيام مشغلي العلاقات الآخرين بالتعبير عن ذلك. نعم ،
a> b تعني بالضبط نفس
b <a ، لكن ليس صحيحًا أن
<= b تعني بالضبط نفس القيمة
! (B <a) . سيكون التعبيران الأخيران متكافئين في حالة وجود خاصية ثلاثية الرؤوس ، وفي حالة وجود أي من القيمتين ، يكون واحد فقط من العبارات الثلاثة صحيحًا:
a <b ،
a == b أو
a> b . في وجود trichotomy ، يعني التعبير
a <= b أننا نتعامل مع الحالة الأولى أو الثانية ... وهذا يعادل العبارة التي لا نتعامل مع الحالة الثالثة. لذلك
(a <= b) ==! (A> b) ==! (B <a) .
ولكن ماذا لو كان الموقف لا يمتلك خاصية ثلاثية الرؤوس؟ هذه هي سمة من سمات علاقات النظام الجزئي. مثال كلاسيكي هو أرقام
الفاصلة العائمة التي يعطي أي من العمليات
1.f <NaN ،
1.f == NaN و
1.f> NaN يعطي
خطأ . لذلك ،
1.f <= NaN يعطي أيضًا
كذبة ، ولكن في نفس الوقت
! (NaN <1.f) صحيح .
الطريقة الوحيدة لتنفيذ
<= عامل بشكل عام من خلال العوامل الأساسية هي رسم كلتا العمليتين كـ
(a == b) || (a <b) ، وهي خطوة كبيرة إلى الوراء إذا كان
لا يزال يتعين علينا التعامل مع الترتيب الخطي ، حيث أنه لن يتم استدعاء وظيفة واحدة ، ولكن اثنين (على سبيل المثال ، التعبير
"abc..xyz9" <= "abc ..xyz1 " يجب إعادة كتابته كـ
(" abc..xyz9 "==" abc..xyz1 ") || (" abc..xyz9 "<" abc..xyz1 ") ومرتين لمقارنة السطر بالكامل).
ثانياً ، المشغل
< ليس مناسبًا جدًا للدور الأساسي نظرًا لخصائص استخدامه في مقارنات المعجمية. غالبًا ما يرتكب المبرمجون هذا الخطأ:
struct A { T t; U u; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u; } bool operator< (A const& rhs) const { return t < rhs.t && u < rhs.u; } };
لتحديد == عامل التشغيل لمجموعة من العناصر ، يكفي تطبيق
== على كل عضو مرة واحدة ، لكن هذا لن يعمل مع
< عامل التشغيل. من وجهة نظر هذا التطبيق ، سيتم اعتبار المجموعتين
A {1 ، 2} و
A {2 ، 1} متكافئة (حيث أن أيا منهما أقل من الآخر). لإصلاح ذلك ، قم بتطبيق
< عامل التشغيل مرتين على كل عضو باستثناء الأخير:
bool operator< (A const& rhs) const { if (t < rhs.t) return true; if (rhs.t < t) return false; return u < rhs.u; }
أخيرًا ، لضمان التشغيل الصحيح لمقارنات الكائنات غير المتجانسة - أي للتأكد من أن التعبيرات
a == 10 و
10 == تعني نفس الشيء - يوصون عادة بكتابة مقارنات كوظائف مجانية. في الواقع ، هذه هي الطريقة الوحيدة عمومًا لتنفيذ مثل هذه المقارنات. هذا غير مريح لأنه ، أولاً ، يجب عليك مراقبة الامتثال لهذه التوصية ، وثانياً ، يتعين عليك عادةً إعلان أن هذه الوظائف مخفية للأصدقاء من أجل تنفيذ أكثر ملاءمة (أي داخل نص الفصل الدراسي).
لاحظ أنه عند مقارنة الكائنات ذات الأنواع المختلفة ، ليس من الضروري دائمًا كتابة
عامل التشغيل == (X، int) ؛ قد تعني أيضًا الحالات التي يمكن فيها تحويل
int إلى
X ضمنيًا.
لنلخص القواعد وفقًا لمعيار C ++ 20:
- يتم التعامل مع جميع البيانات بنفس الطريقة.
- نحن نستخدم التعابير لتسهيل التنفيذ. العوامل == و < نتخذها للتعابير الأساسية والتعبير عن عوامل العلاقة المتبقية من خلالها.
- هذا مجرد المشغل <غير مناسب للغاية لدور القاعدة.
- من المهم (والموصى به) كتابة مقارنات بين الكائنات غير المتجانسة كوظائف حرة.
مشغل الطلب الأساسي الجديد: <=>
التغيير الأكثر أهمية والملحوظ في عمل المقارنات في C ++ 20 هو إضافة مشغل جديد -
مشغل <=> ، مشغل مقارنة ثلاثي الاتجاهات.
نحن على دراية بالمقارنات الثلاثية بواسطة الدالات
memcmp /
strcmp في C و
basic_string :: compar () في C ++. تقوم جميعها بإرجاع قيمة type
int ، والتي يتم تمثيلها برقم موجب تعسفي إذا كانت الوسيطة الأولى أكبر من الثانية ،
0 إذا كانت متساوية ، ورقم سالبة تعسفية خلاف ذلك.
لا يُرجع مشغل "سفينة الفضاء" قيمة
int ، ولكن كائنًا ينتمي إلى إحدى فئات المقارنة ، وتعكس قيمته نوع العلاقة بين الكائنات المقارنة. هناك ثلاث فئات رئيسية:
- strong_ordering : علاقة ترتيب خطي تنطوي فيها المساواة على قابلية تبادل العناصر (على سبيل المثال (a <=> b) == strong_ordering :: equal تعني أن f (a) == f (b) يحمل جميع الوظائف المناسبة f لم يتم تعريف مصطلح "الوظيفة المناسبة" عمداً تعريفا واضحا ، لكن هذه لا تشمل الوظائف التي تعيد عناوين وسائطهم أو سعة () المتجه ، وما إلى ذلك. نحن مهتمون فقط بالخصائص "الأساسية" ، والتي هي أيضا غامضة للغاية ، ولكن يمكن أن تكون مشروطة افترض أننا نتحدث عن قيمة النوع. قيمة المتجه موجودة فيه عناصر م ، ولكن ليس عنوانه ، وما إلى ذلك). تتضمن هذه الفئة القيم التالية: strong_ordering :: أكبر ، strong_ordering :: equal و strong_ordering :: أقل .
- ضعيف الحدود : علاقة ترتيب خطية تحدد المساواة فيها فئة معادلة معينة فقط. مثال كلاسيكي هو مقارنة سلسلة غير حساسة لحالة الأحرف ، عندما يكون كائنين ضعيفين: مكافئين ، لكن ليسا متساويين تمامًا (وهذا ما يفسر استبدال الكلمة بالتساوي مع ما يعادلها في اسم القيمة).
- جزئي_الترتيب : علاقة ترتيب جزئي. في هذه الفئة ، تتم إضافة قيمة أخرى إلى القيم الأكبر والمكافئة والأقل (كما في حالة الضعف ) - غير مرتبة (" غير مرتبة "). يمكن استخدامه للتعبير عن علاقات الطلب الجزئي في نظام كتابة: 1.f <=> NaN يعطي القيمة الجزئية_ترتيب :: غير مرتبة .
ستعمل بشكل رئيسي مع فئة
strong_ordering ؛ هذا هو أيضا الفئة الأمثل للاستخدام بشكل افتراضي. على سبيل المثال ،
2 <=> 4 بإرجاع
strong_ordering :: أقل ، و
3 <=> -1 بإرجاع strong_ordering :: أكبر .
يمكن تقليل فئات الترتيب الأعلى ضمنيًا إلى فئات ذات ترتيب أضعف (على سبيل المثال ، يمكن
تقليل الترتيب القوي إلى
ترتيب ضعيف ). في هذه الحالة ، يتم الحفاظ على نوع العلاقة الحالي (على سبيل المثال ، يتحول
strong_ordering :: يساوي إلىضعف _ مكافئ :: مكافئ ).
يمكن مقارنة قيم فئات المقارنة بالحرف
0 (ليس بأي
int وليس مع
int يساوي
0 ، ولكن ببساطة مع الحرفي
0 ) باستخدام واحد من ستة عوامل مقارنة:
strong_ordering::less < 0
بفضل المقارنة مع الحرفي
0 يمكننا تنفيذ معاملات العلاقة:
a @ b تعادل
(a <=> b) @ 0 لكل من هؤلاء المشغلين.
على سبيل المثال ، يمكن حساب
2 <4 كـ
(2 <=> 4) <0 ، والذي يتحول إلى
strong_ordering :: less <0 ويعطي القيمة
الحقيقية .
يناسب عامل التشغيل
<=> دور العنصر الأساسي أفضل بكثير من
< العامل ، لأنه يزيل كلتا مشكلتي الأخير.
أولاً ، التعبير
"<= b" مضمون ليكون مكافئًا لـ
(a <=> b) <= 0 حتى مع وجود ترتيب جزئي. بالنسبة
لقيمتين غير مرتبتين ،
سيعطي <=> b القيمة
جزئية_ غير مرتبة :: غير مرتبة ،
أما القيمة
الجزئية_المترتبة :: غير مرتبة <= 0 فستعطي قيمة
خاطئة ، وهو ما نحتاجه. هذا ممكن لأن
<=> يمكنها إرجاع المزيد من أنواع القيم: على سبيل المثال ، تحتوي فئة
التقسيم الجزئي على أربع قيم ممكنة. يمكن أن تكون قيمة type
bool صحيحة أو
خاطئة ، لذا قبل أن نتمكن من التمييز بين مقارنات القيم المرتبة وغير المرتبة.
من أجل الوضوح ، ضع في اعتبارك مثال لعلاقة ترتيب جزئي غير مرتبطة بأرقام الفاصلة العائمة. لنفترض أننا نريد إضافة حالة NaN إلى نوع
int ، حيث NaN هي مجرد قيمة لا تشكل زوجًا مرتبًا مع أي قيمة معنية. يمكنك القيام بذلك باستخدام
std :: اختياري لتخزينه:
struct IntNan { std::optional<int> val = std::nullopt; bool operator==(IntNan const& rhs) const { if (!val || !rhs.val) { return false; } return *val == *rhs.val; } partial_ordering operator<=>(IntNan const& rhs) const { if (!val || !rhs.val) {
<= عامل التشغيل يُرجع القيمة الصحيحة لأنه يمكننا الآن التعبير عن مزيد من المعلومات على مستوى اللغة نفسها.
ثانياً ، للحصول على جميع المعلومات اللازمة ، يكفي تطبيق
<=> مرة واحدة ، مما يسهل تنفيذ المقارنة المعجمية:
struct A { T t; U u; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u; } strong_ordering operator<=>(A const& rhs) const {
انظر
P0515 ، الجملة الأصلية لإضافة
عامل التشغيل <=> ، لمناقشة أكثر تفصيلاً
.ميزات المشغل الجديدة
نحن لا نحصل فقط تحت تصرفنا مشغل جديد. في النهاية ، إذا كان المثال الموضح أعلاه مع الإعلان عن الهيكل
A قال فقط أنه بدلاً من
x <y ، علينا الآن أن نكتب
(x <=> y) <0 في كل مرة ، فلن يرغب أحد في ذلك.
تختلف آلية حل المقارنات في الإصدار C ++ 20 بشكل ملحوظ عن الطريقة القديمة ، ولكن هذا التغيير مرتبط مباشرة بالمفهوم الجديد لمشغلي المقارنة الأساسيين:
== و
<=> . إذا كانت في وقت سابق عبارة (تم تسجيلها عبر
== و
< ) ، والتي استخدمناها ، ولكن لم يعرفها المترجم ، فسوف يفهم هذا الاختلاف الآن.
مرة أخرى ، سأقدم جدولًا رأيته بالفعل في بداية المقال:
حصل كل من المشغلين الأساسيين ومشتقاتهم على قدرة جديدة ، وسأقول بضع كلمات أخرى.
انقلاب المشغلين الأساسيين
على سبيل المثال ، خذ نوعًا يمكن مقارنته فقط بـ
int :
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } };
من وجهة نظر القواعد القديمة ، ليس من المستغرب أن يكون التعبير
a == 10 يعمل ويقيم إلى
a.operator == (10) .
ولكن ماذا عن
10 == a ؟ في C ++ 17 ، يعتبر هذا التعبير خطأ في بناء جملة واضح. لا يوجد مثل هذا المشغل. لكي تعمل هذه التعليمة البرمجية ، يجب عليك كتابة
عامل تشغيل متماثل
== ، والتي ستأخذ أولاً قيمة
int ، ثم
A ... ولتنفيذ ذلك ، يجب أن تكون في شكل دالة مجانية.
في C ++ 20 ، يمكن قلب العوامل الأساسية. بالنسبة لـ
10 == a ، سيجد المترجم
عامل تشغيل مرشح
== (A ، int) (في الواقع ، هذه وظيفة عضو ، لكن من أجل الوضوح ، أكتبها هنا كدالة مجانية) ، ثم بالإضافة إلى ذلك - متغير مع ترتيب عكسي للمعلمات ، أي .
عامل التشغيل == (int، A) . يتزامن هذا المرشح الثاني مع تعبيرنا (والأفضل) ، لذلك سنختاره. يتم تقييم التعبير
10 == a في C ++ 20 كـ
a.operator == (10) . المترجم يفهم أن المساواة متماثلة.
سنقوم الآن بتوسيع نوعنا بحيث يمكن مقارنته بـ
int ليس فقط من خلال مشغل المساواة ، ولكن أيضًا من خلال مشغل الطلب:
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } strong_ordering operator<=>(int j) const { return i <=> j; } };
مرة أخرى ، التعبير
"<=> 42 يعمل بشكل جيد ويتم حسابه وفقًا للقواعد القديمة على أنه
aoperator <=> (42) ، ولكن
42 <=> a سيكون خطأ من وجهة نظر C ++ 17 ، حتى إذا كان العامل
< => موجودة بالفعل في اللغة. لكن في C ++ 20 ،
المشغل <=> ، مثل
عامل التشغيل == ، متماثل: فهو يتعرف على المرشحين المقلوب. بالنسبة لـ
42 <=> a ، سيتم العثور على
عامل وظيفة عضو
<=> (A ، int) (مرة أخرى ، أنا أكتبها هنا كدالة مجانية فقط لمزيد من الوضوح) ، وكذلك
عامل تشغيل مرشح صناعي
<=> (int ، A) . هذه النسخة المعكوسة تتطابق تمامًا مع تعبيرنا - نختارها.
ومع ذلك ، لا
يتم حساب
42 <=> a كـ
a.operator <=> (42) . سيكون ذلك خطأ. يتم تقييم هذا التعبير إلى
0 <=> a.operator <=> (42) . حاول معرفة سبب صحة هذا الإدخال.
من المهم ملاحظة أن المترجم لا ينشئ أي وظائف جديدة. عند حساب
10 == a ، لم يظهر
مشغل المشغل الجديد
== (int، A) ، وعند حساب
42 <=> a ، لم يظهر
المشغل <=> (int، A) . تتم إعادة كتابة تعبيرين فقط من خلال المرشحين المقلوبين. أكرر: لا يتم إنشاء وظائف جديدة.
لاحظ أيضًا أن السجل بترتيب عكسي للمعلمات متاح فقط للمشغلين الأساسيين ، ولكن بالنسبة للمشتقات ، فهو غير متوفر. هذا هو:
struct B { bool operator!=(int) const; }; b != 42;
إعادة كتابة العوامل المشتقة
دعنا نعود إلى مثالنا مع الهيكل
A :
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } strong_ordering operator<=>(int j) const { return i <=> j; } };
خذ التعبير
a! = 17 . في C ++ 17 ، يعد هذا خطأ في بناء الجملة لأن
المشغل! = المشغل غير موجود. ومع ذلك ، في C ++ 20 ، للتعبيرات التي تحتوي على عوامل المقارنة المشتقة ، سيبحث المترجم أيضًا عن العوامل الأساسية المقابلة ويعبر عن المقارنات المشتقة من خلالها.
نحن نعلم أنه في الرياضيات ، العملية
! = تعني في الأساس NOT
== . الآن هذا معروف للمترجم. بالنسبة للتعبير
a! = 17 ، سيبحث ليس فقط عن
المشغل! = المشغلين ، ولكن أيضًا
المشغل == (وكما في الأمثلة السابقة ،
المشغل المقلوب
== ). في هذا المثال ، وجدنا عامل مساوٍ يناسبنا تقريبًا - نحتاج فقط إلى إعادة كتابته وفقًا للدلالات المطلوبة:
a! = 17 سيتم احتسابها على أنها
(A == 17) .
وبالمثل ،
17! = A تُحسب كـ
! A.operator == (17) ، وهو إصدار معاد كتابته ومقلوب.
يتم إجراء تحويلات مماثلة أيضًا لطلب المشغلين. إذا كتبنا
علامة <9 ، فسنحاول (دون جدوى) العثور على
عامل التشغيل < ، وكذلك النظر في المرشحين الأساسيين:
عامل التشغيل <=> . يبدو البديل المقابل لمشغلي العلاقة كما يلي: يتم
حساب a @ b (حيث يكون
@ هو أحد مشغلي العلاقة) كـ
(a <=> b) @ 0 . في حالتنا ،
a.operator <=> (9) <0 . وبالمثل ،
يتم حساب
9 <= a كـ
0 <= a.operator <=> (9) .
لاحظ أنه ، كما في حالة المكالمة ، لا يقوم المترجم بإنشاء أي وظائف جديدة للمرشحين المعاد كتابتهم. يتم حسابها ببساطة بطريقة مختلفة ، ويتم إجراء جميع التحويلات فقط على مستوى شفرة المصدر.
ما سبق يقودني إلى النصائح التالية:
المشغلات الأساسية فقط : تحديد العوامل الأساسية فقط (== و <=>) في النوع الخاص بك.نظرًا لأن المشغلين الأساسيين يقدمون مجموعة كاملة من المقارنات ، يكفي تعريفهم فقط. هذا يعني أنك بحاجة إلى عاملين فقط لمقارنة الكائنات من نفس النوع (بدلاً من 6 ، حتى الآن) وعاملين فقط لمقارنة أنواع مختلفة من الكائنات (بدلاً من 12). إذا كنت تحتاج فقط إلى عملية المساواة ، فقم فقط بكتابة دالة واحدة لمقارنة كائنات من نفس النوع (بدلاً من 2) وواحدة واحدة لمقارنة أنواع مختلفة من الكائنات (بدلاً من 4).
فئة std :: sub_match هي حالة متطرفة: في C ++ 17 يستخدم 42 عامل مقارنة ، بينما يستخدم C ++ 20 8 فقط ، في حين أن الوظيفة لا تعاني بأي شكل من الأشكال.
نظرًا لأن المحول البرمجي يعتبر أيضًا المرشحين المقلوبين ، يمكن تنفيذ كل هذه العوامل كوظائف أعضاء. لم تعد مضطرًا لكتابة وظائف مجانية لمجرد مقارنة الكائنات بأنواعها المختلفة.
قواعد خاصة للعثور على المرشحين
كما ذكرت سابقًا ، تم البحث عن المرشحين لـ
a @ b في C ++ 17 وفقًا للمبدأ التالي: نجد جميع
المشغلين @ واختروا الأكثر ملائمة منهم.
يستخدم C ++ 20 مجموعة موسعة من المرشحين. الآن سنبحث عن كل
عامل @ .
اجعلالمشغل الأساسي لـ
@ (يمكن أن يكون نفس المشغل). نجد أيضًا جميع
العواملولكل واحد منهم نضيف نسخته المقلوبة. من بين كل هؤلاء المرشحين الموجودين ، نختار الأكثر ملائمة.
لاحظ أن التحميل الزائد للمشغل مسموح به في
مسار واحد . نحن لا نحاول استبدال مرشحين مختلفين. أولاً نجمعها جميعًا ، ثم نختار الأفضل منها. في حالة عدم وجود ذلك ، يفشل البحث ، كما كان من قبل.
الآن لدينا الكثير من المرشحين المحتملين ، وبالتالي المزيد من عدم اليقين. النظر في المثال التالي:
struct C { bool operator==(C const&) const; bool operator!=(C const&) const; }; bool check(C x, C y) { return x != y; }
في C ++ 17 ، كان لدينا مرشح واحد فقط لـ
x! = Y ، والآن يوجد ثلاثة:
x.operator! = (Y) ،! X.operator == (y) و
! Y.operator == (x) . ماذا تختار؟ انهم جميعا نفس الشيء! (ملاحظة: المرشح
y.operator! = (X) غير موجود ، حيث يمكن
قلب المشغلين الأساسيين فقط.)
تم تقديم قاعدتين إضافيتين لإزالة عدم اليقين هذا. المرشحون غير المحولون يفضلون المتحولين ؛ . ,
x.operator!=(y) «»
!x.operator==(y) , «»
!y.operator==(x) . , «» .
:
operator@@ . . , .
-. — (,
x < y , —
(x <=> y) < 0 ), (,
x <=> y void - , DSL), . . ,
bool ( :
operator== bool , ?)
على سبيل المثال:
struct Base { friend bool operator<(const Base&, const Base&);
d1 < d2 :
#1 #2 . —
#2 , , , . ,
d1 < d2 (d1 <=> d2) < 0 . ,
void 0 — , . , - ,
#1 .
, , C++17, . , - . :
, . .
. , , , , , ( ). , :
« » , , ..
a < b 0 < (b <=> a) , , , .
C++17 . . :
struct A { T t; U u; V v; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u && v == rhs.v; } bool operator!=(A const& rhs) const { return !(*this == rhs); } bool operator< (A const& rhs) const {
-
std::tie() , .
, : :
struct A { T t; U u; V v; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u && v == rhs.v; } strong_ordering operator<=>(A const& rhs) const {
.
<=> < . , .
c != 0 , , (
), .
. C++20 , :
struct A { T t; U u; V v; bool operator==(A const& rhs) const = default; strong_ordering operator<=>(A const& rhs) const = default; };
, . , :
struct A { T t; U u; V v; bool operator==(A const& rhs) const = default; auto operator<=>(A const& rhs) const = default; };
. , , :
struct A { T t; U u; V v; auto operator<=>(A const& rhs) const = default; };
, , . :
operator== ,
operator<=> .
C++20: . . , , , .
PVS-Studio , <=> . , -. , , (. "
"). ++ .
PVS-Studio <, :
bool operator< (A const& rhs) const { return t < rhs.t && u < rhs.u; }
. , - . .
:
Comparisons in C++20 .