<=>
। बहुत समय पहले नहीं, साइमन ब्रांड ने एक पोस्ट प्रकाशित की थी जिसमें इस बारे में विस्तृत वैचारिक जानकारी थी कि यह ऑपरेटर क्या है और किन उद्देश्यों के लिए इसका उपयोग किया जाता है। इस पोस्ट का मुख्य कार्य "अजीब" नए ऑपरेटर और इसके एनालॉग operator==
के विशिष्ट अनुप्रयोगों का अध्ययन करना है, साथ ही हर रोज़ कोडिंग में इसके उपयोग के लिए कुछ सिफारिशें तैयार करना है।
तुलना
निम्नलिखित की तरह कोड देखना असामान्य नहीं है:
struct IntWrapper { int value; constexpr IntWrapper(int value): value{value} { } bool operator==(const IntWrapper& rhs) const { return value == rhs.value; } bool operator!=(const IntWrapper& rhs) const { return !(*this == rhs); } bool operator<(const IntWrapper& rhs) const { return value < rhs.value; } bool operator<=(const IntWrapper& rhs) const { return !(rhs < *this); } bool operator>(const IntWrapper& rhs) const { return rhs < *this; } bool operator>=(const IntWrapper& rhs) const { return !(*this < rhs); } };
नोट: चौकस पाठकों को ध्यान होगा कि यह वास्तव में C ++ 20 से पहले कोड में होना चाहिए की तुलना में कम क्रिया है। इस पर और बाद में।
आपको यह सुनिश्चित करने के लिए बहुत सारे मानक कोड लिखने की आवश्यकता है कि हमारा प्रकार एक ही प्रकार के कुछ के बराबर है। ठीक है, हम इसे कुछ समय के लिए समझ लेंगे। फिर कोई आता है जो इस तरह से लिखता है:
constexpr bool is_lt(const IntWrapper& a, const IntWrapper& b) { return a < b; } int main() { static_assert(is_lt(0, 1)); }
पहली चीज जो आप देखेंगे वह यह है कि कार्यक्रम संकलित नहीं करेगा।
error C3615: constexpr function 'is_lt' cannot result in a constant expression
समस्या यह है कि तुलनात्मक कार्य में
constexpr
भुला constexpr
गया है। फिर कुछ सभी तुलना ऑपरेटरों के लिए constexpr
जोड़ constexpr
। कुछ दिनों बाद, कोई व्यक्ति is_gt
जोड़ देगा, लेकिन ध्यान दें कि सभी तुलना ऑपरेटरों के पास अपवाद विनिर्देश नहीं है, और आपको 5 ओवरलोड में से प्रत्येक में noexcept
जोड़ने की एक ही थकाऊ प्रक्रिया से noexcept
होगा।यह वह जगह है जहाँ नया C ++ 20 स्पेसशिप ऑपरेटर हमारी सहायता के लिए आता है। आइए देखें कि आप C ++ 20 की दुनिया में मूल
IntWrapper
कैसे लिख सकते हैं: #include <compare> struct IntWrapper { int value; constexpr IntWrapper(int value): value{value} { } auto operator<=>(const IntWrapper&) const = default; };
पहला अंतर जो आप देख सकते हैं, वह है
<compare>
का नया समावेश। स्पेसर ऑपरेटर के लिए आवश्यक सभी प्रकार की तुलना श्रेणियों के साथ कंपाइलर भरने के लिए <compare>
हेडर जिम्मेदार है, ताकि यह हमारे डिफ़ॉल्ट फ़ंक्शन के लिए उपयुक्त एक प्रकार लौटाए। उपरोक्त स्निपेट में, auto
का रिटर्न प्रकार std::strong_ordering
।हमने न केवल 5 अतिरिक्त लाइनें हटा दी हैं, बल्कि हमें कुछ भी निर्धारित करने की आवश्यकता नहीं है, कंपाइलर हमारे लिए यह करेगा।
is_lt
अपरिवर्तित रहता है और बस काम करता है, हालांकि हमने इसे स्पष्ट रूप से अपने डिफ़ॉल्ट operator<=>
में निर्दिष्ट नहीं किया है। यह अच्छा है, लेकिन कुछ लोग इस पर पहेली बना सकते हैं कि is_lt
को संकलन करने की अनुमति क्यों दी जाती है, भले ही वह स्पेसशिप ऑपरेटर का उपयोग न करे। आइए इस सवाल का जवाब ढूंढते हैं।अभिव्यक्त करने वाली अभिव्यक्तियाँ
C ++ 20 में, कंपाइलर को "पुनर्लेखन" के भावों से संबंधित एक नई अवधारणा में पेश किया गया है। स्पेसशिप ऑपरेटर,
operator==
के साथ, पहले दो उम्मीदवारों में से एक है जिसे फिर से लिखा जा सकता है। पुनर्लेखन के और अधिक विशिष्ट उदाहरण के लिए, आइए is_lt
में दिए गए उदाहरण को is_lt
।अधिभार का समाधान करते समय, कंपाइलर सबसे उपयुक्त उम्मीदवारों के एक सेट में से एक का चयन करेगा, जिनमें से प्रत्येक उस ऑपरेटर से मेल खाता है जिसकी हमें आवश्यकता है। तुलनात्मक संचालन और समतुल्यता संचालन के लिए चयन प्रक्रिया में बहुत कम परिवर्तन होता है, जब कंपाइलर को भी विशेष हस्तांतरित और संश्लेषित उम्मीदवारों ( [over.match.oper] / 3.4 ) को इकट्ठा करना होगा।
हमारी अभिव्यक्ति के
a < b
मानक कहता है कि हम operator<=>
लिए टाइप कर सकते हैं operator<=>
या operator<=>
फ़ंक्शन जो इस प्रकार को स्वीकार करते हैं। यह वह है जो संकलक करता है और पता चलता है कि प्रकार a
वास्तव में IntWrapper::operator<=>
। कंपाइलर को तब इस ऑपरेटर का उपयोग करने की अनुमति दी जाती है और अभिव्यक्ति को a < b
(a <=> b) < 0
रूप में फिर से लिखा जाता है। यह फिर से लिखा अभिव्यक्ति सामान्य अधिभार संकल्प के लिए एक उम्मीदवार के रूप में प्रयोग किया जाता है।आप पूछ सकते हैं कि यह लिखित अभिव्यक्ति सही क्यों है। अभिव्यक्ति की शुद्धता वास्तव में शब्दार्थ से मिलती है जो स्पेसशिप ऑपरेटर प्रदान करता है।
<=>
एक तीन-तरफा तुलना है, जिसका अर्थ है कि आपको न केवल एक द्विआधारी परिणाम मिलता है, बल्कि एक आदेश भी होता है (ज्यादातर मामलों में)। यदि आपके पास कोई आदेश है, तो आप किसी भी तुलनात्मक संचालन के संदर्भ में इस आदेश को व्यक्त कर सकते हैं। एक त्वरित उदाहरण, अभिव्यक्ति 4 <=> C ++ 20 में परिणाम std::strong_ordering::less
। std::strong_ordering::less
का परिणाम std::strong_ordering::less
तात्पर्य है कि 4
न केवल 5
से अलग है, बल्कि इस मूल्य से कड़ाई से कम है, जो ऑपरेशन के आवेदन को बनाता है (4 <=> 5) < 0
हमारे परिणाम का वर्णन करने के लिए सही और सटीक है।उपरोक्त जानकारी का उपयोग करके, कंपाइलर किसी भी सामान्यीकृत तुलना ऑपरेटर (यानी,
<
, >
, आदि) को ले सकता है और इसे स्पेसशिप ऑपरेटर के संदर्भ में फिर से लिख सकता है। मानक में, एक लिखित अभिव्यक्ति को अक्सर (a <=> b) @ 0
रूप में संदर्भित किया जाता है जहां @
किसी भी तुलनात्मक संचालन का प्रतिनिधित्व करता है।सिंथेसाइज़िंग एक्सप्रेशंस
पाठकों ने ऊपर "संश्लेषित" अभिव्यक्तियों के लिए एक सूक्ष्म संदर्भ देखा हो सकता है, और वे बयानों की पुनर्लेखन की इस प्रक्रिया में एक भूमिका भी निभाते हैं। निम्नलिखित कार्य पर विचार करें:
constexpr bool is_gt_42(const IntWrapper& a) { return 42 < a; }
यदि हम
IntWrapper
लिए अपनी मूल परिभाषा का उपयोग करते हैं, तो यह कोड संकलित नहीं करेगा।error C2677: binary '<': no global operator found which takes type 'const IntWrapper' (or there is no acceptable conversion)
यह C ++ 20 से पहले समझ में आता है, और इस समस्या को हल करने का तरीका
IntWrapper
कुछ अतिरिक्त friend
कार्यों को IntWrapper
जो IntWrapper
के बाईं ओर स्थित हैं। यदि आप संकलक और IntWrapper
C ++ 20 परिभाषा का उपयोग करके इस उदाहरण को बनाने की कोशिश करते हैं, तो आप नोटिस कर सकते हैं कि, फिर से, यह सिर्फ काम करता है। आइए देखें कि ऊपर का कोड अभी भी C ++ 20 में क्यों संकलित है।ओवरलोड को हल करते समय, कंपाइलर भी इकट्ठा करेगा कि मानक "संश्लेषित" उम्मीदवारों को क्या कहते हैं, या मापदंडों के रिवर्स ऑर्डर के साथ एक फिर से लिखे गए अभिव्यक्ति। उपरोक्त उदाहरण में, कंपाइलर फिर से लिखे गए अभिव्यक्ति
(42 <=> a) < 0
का उपयोग करने की कोशिश करेगा, लेकिन यह पाएगा कि बाईं ओर को संतुष्ट करने के लिए IntWrapper
से कोई रूपांतरण नहीं है, इसलिए पुन: लिखित अभिव्यक्ति को छोड़ दिया गया है। संकलक "संश्लेषित" अभिव्यक्ति 0 < (a <=> 42)
भी कॉल करता है और पता लगाता है कि int
से IntWrapper
रूपांतरण उसके रूपांतरण निर्माता के माध्यम से IntWrapper
, इसलिए इस उम्मीदवार का उपयोग किया जाता है।संश्लेषित अभिव्यक्तियों का उद्देश्य अंतराल में भरने के लिए
friend
फंक्शन टेम्प्लेट लिखने की उलझन से बचना है, जिसमें आपकी वस्तु को अन्य प्रकारों से परिवर्तित किया जा सकता है। संश्लेषित अभिव्यक्तियों को 0 @ (b <=> a)
लिए सामान्यीकृत किया जाता है।अधिक जटिल प्रकार
संकलक द्वारा उत्पन्न स्पेसशिप ऑपरेटर कक्षाओं के अलग-अलग सदस्यों पर नहीं रुकता है, यह आपके प्रकार के सभी सबोबिज के लिए तुलना का सही सेट उत्पन्न करता है:
struct Basics { int i; char c; float f; double d; auto operator<=>(const Basics&) const = default; }; struct Arrays { int ai[1]; char ac[2]; float af[3]; double ad[2][2]; auto operator<=>(const Arrays&) const = default; }; struct Bases : Basics, Arrays { auto operator<=>(const Bases&) const = default; }; int main() { constexpr Bases a = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; constexpr Bases b = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; static_assert(a == b); static_assert(!(a != b)); static_assert(!(a < b)); static_assert(a <= b); static_assert(!(a > b)); static_assert(a >= b); }
कंपाइलर जानता है कि कक्षा के सदस्यों को कैसे विस्तारित किया जाए जो उप-विषयों की अपनी सूची में सरणियाँ हैं और उनकी पुनरावर्ती तुलना करें। बेशक, यदि आप इन कार्यों के निकायों को स्वयं लिखना चाहते हैं, तो आपको कंपाइलर द्वारा अभिव्यक्तियों को फिर से लिखने से लाभ होगा।
एक बतख की तरह दिखता है, एक बतख की तरह तैरता है, और operator==
तरह चुटकी लेता है
मानकीकरण समिति के कुछ बहुत ही चतुर लोगों ने देखा है कि स्पेसशिप ऑपरेटर हमेशा तत्वों की तुलनात्मक रूप से तुलनात्मक प्रदर्शन करेगा, चाहे कुछ भी हो। लेक्सिकोग्राफिक तुलनाओं के बिना शर्त निष्पादन से अक्षम कोड हो सकता है, विशेष रूप से, समानता ऑपरेटर के साथ।
दो लाइनों की तुलना में एक विहित उदाहरण। यदि आपके पास स्ट्रिंग
"foobar"
और आप इसे == का उपयोग करते हुए स्ट्रिंग "foo"
साथ तुलना करते हैं, तो आप इस ऑपरेशन के लगभग स्थिर होने की उम्मीद कर सकते हैं। एक प्रभावी स्ट्रिंग तुलना एल्गोरिथ्म इस प्रकार है:- पहले दो लाइनों के आकार की तुलना करें। यदि आकार अलग हैं, तो
false
वापस आएँ - अन्यथा, दो लाइनों के प्रत्येक तत्व के साथ कदम से कदम मिलाएं और उनकी तुलना करें जब तक कि कोई अंतर न हो या सभी तत्व समाप्त न हों। परिणाम लौटाओ।
स्पेसशिप ऑपरेटर के नियमों के अनुसार, हमें प्रत्येक तत्व की तुलना तब तक शुरू करनी चाहिए जब तक कि हम एक को अलग न कर लें। हमारे उदाहरण में,
"foobar"
और "foo"
केवल 'b'
और '\0'
तुलना करते समय क्या आप अंततः false
।इसका मुकाबला करने के लिए, P1185R2 लेख था, जिसमें बताया गया है कि कंपाइलर कैसे फिर से बनाता है और
operator==
स्वतंत्र रूप से स्पेसशिप ऑपरेटर बनाता है। हमारे IntWrapper
को इस प्रकार लिखा जा सकता है: #include <compare> struct IntWrapper { int value; constexpr IntWrapper(int value): value{value} { } auto operator<=>(const IntWrapper&) const = default; bool operator==(const IntWrapper&) const = default; };
एक और कदम ... हालांकि, अच्छी खबर है; आपको वास्तव में ऊपर दिए गए कोड को लिखने की ज़रूरत नहीं है, क्योंकि बस
auto operator<=>(const IntWrapper&) const = default
लिखने के लिए auto operator<=>(const IntWrapper&) const = default
संकलक के लिए पर्याप्त रूप से आपके लिए एक अलग और अधिक कुशल operator==
उत्पन्न करने के लिए पर्याप्त है!कंपाइलर थोड़ा संशोधित "पुनर्लेखन" नियम लागू करता है, जो
==
और !=
लिए विशिष्ट है, जहां इन ऑपरेटरों में operator==
बजाय उन्हें फिर से लिखा जाता है operator==
बजाय operator<=>
। इसका मतलब है कि !=
इसके अलावा अनुकूलन से लाभ होता है।पुराना कोड नहीं टूटेगा
इस बिंदु पर, आप सोच सकते हैं: ठीक है, अगर संकलक को इस ऑपरेटर के पुनर्लेखन ऑपरेशन को करने की अनुमति है, तो क्या होगा यदि मैं संकलक को बाहर निकालने की कोशिश करता हूं:
struct IntWrapper { int value; constexpr IntWrapper(int value): value{value} { } auto operator<=>(const IntWrapper&) const = default; bool operator<(const IntWrapper& rhs) const { return value < rhs.value; } }; constexpr bool is_lt(const IntWrapper& a, const IntWrapper& b) { return a < b; }
जवाब कोई बड़ी बात नहीं है। C ++ में ओवरलोड रिज़ॉल्यूशन मॉडल वह क्षेत्र है जिसमें सभी उम्मीदवार टकराते हैं। इस विशेष लड़ाई में, हमारे पास उनमें से तीन हैं:
IntWrapper::operator<(const IntWrapper& a, const IntWrapper& b)
IntWrapper::operator<=>(const IntWrapper& a, const IntWrapper& b)
(फिर से लिखा)
IntWrapper::operator<=>(const IntWrapper& b, const IntWrapper& a)
(समन्वित)
यदि हमने C ++ 17 में अधिभार संकल्प नियमों को अपनाया है, तो इस कॉल का परिणाम मिश्रित होगा, लेकिन C ++ 20 अधिभार संकल्प नियमों को बदल दिया गया ताकि संकलक इस स्थिति को सबसे तार्किक अधिभार के लिए हल कर सके।
ओवरलोड रिज़ॉल्यूशन चरण है जब कंपाइलर को अतिरिक्त पास की एक श्रृंखला को पूरा करना होगा। C ++ 20 में, एक नया तंत्र प्रकट हुआ है, जिसमें अधिभार को अधिलेखित या संश्लेषित नहीं किए जाने वाले अधिभार को दिया जाता है, जो हमारे
IntWrapper::operator<
को सर्वश्रेष्ठ उम्मीदवार को अधिभारित करता है और अस्पष्टता का समाधान करता है। समान तंत्र सामान्य पुनर्लेखन अभिव्यक्तियों के बजाय संश्लेषित उम्मीदवारों के उपयोग को रोकता है।अंतिम विचार
स्पेसशिप ऑपरेटर सी ++ के लिए एक स्वागत योग्य अतिरिक्त है, क्योंकि यह आपके कोड को सरल बनाने और कम लिखने में मदद कर सकता है, और कभी-कभी कम बेहतर होता है। तो बकसुआ और अपने सी ++ 20 अंतरिक्ष यान को नियंत्रित!
हम आपसे अनुरोध करते हैं कि आप बाहर जाकर स्पेसशिप ऑपरेटर की कोशिश करें, यह अभी विजुअल स्टूडियो 2019 में
/std:c++latest
तहत उपलब्ध है! नोट के रूप में, P1185R2 में किए गए परिवर्तन विज़ुअल स्टूडियो 2019 संस्करण 16.2 में उपलब्ध होंगे। कृपया ध्यान रखें कि स्पेसशिप ऑपरेटर C ++ 20 का हिस्सा है और C ++ 20 को अंतिम रूप दिए जाने तक कुछ परिवर्तनों के अधीन है।हमेशा की तरह, हमें आपकी प्रतिक्रिया का इंतजार है। ईमेल के माध्यम से visualcpp@microsoft.com पर , Twitter @visualc , या Facebook Microsoft Visual Cpp के माध्यम से कोई भी टिप्पणी भेजने में संकोच न करें।
यदि आप वीएस 2019 में एमएसवीसी के साथ अन्य समस्याओं का सामना करते हैं, तो कृपया हमें इंस्टॉलर से या विज़ुअल स्टूडियो आईडीई से "रिपोर्ट ए प्रॉब्लम" विकल्प के माध्यम से बताएं। सुझाव या बग रिपोर्ट के लिए, DevComm के माध्यम से हमें लिखें।