क्या वास्तव में एक कारण है कि अधिक भार और & || शॉर्ट सर्किट नहीं है?


137

परिचालकों का लघु परिचलन व्यवहार &&और ||प्रोग्रामर के लिए एक अद्भुत उपकरण है।

लेकिन ओवरलोड होने पर वे इस व्यवहार को क्यों खो देते हैं? मैं समझता हूं कि ऑपरेटर केवल कार्यों के लिए सिंटैक्टिक चीनी हैं, लेकिन ऑपरेटरों के boolपास यह व्यवहार है, तो इसे इस एकल प्रकार तक ही सीमित क्यों रखा जाना चाहिए? क्या इसके पीछे कोई तकनीकी तर्क है?


1
@PiotrS। वह सवाल शायद इसका जवाब है। मुझे लगता है कि मानक इस उद्देश्य के लिए एक नया वाक्यविन्यास परिभाषित कर सकता है। शायद पसंद है operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)
iFreilicht

1
@PiotrS {true, false, nil}. : त्रि-राज्य तर्क पर विचार करें :। चूंकि nil&& x == nilयह शॉर्ट-सर्किट हो सकता है।
MSALERS

1
@MSalters: गौर कीजिए std::valarray<bool> a, b, c;, आप कैसे कल्पना करते हैं कि a || b || cकम-से-कम हो?
पायोत्र स्कोटनिक

4
@PiotrS।: मैं तर्क दे रहा हूं कि जो कम सर्कुलेटिंग के लिए कम से कम एक गैर-बूल प्रकार मौजूद है , वह समझ में आता है। मैं यह तर्क नहीं दे रहा हूं कि शॉर्टक्रिचुइटिंग हर गैर-बूल प्रकार के लिए समझ में आता है ।
MSALERS

3
किसी ने अभी तक इसका उल्लेख नहीं किया है, लेकिन पिछड़ी संगतता का मुद्दा भी है। जब तक विशेष देखभाल उन परिस्थितियों को सीमित करने के लिए समर्पित नहीं है जहां यह लघु-परिशोधन लागू होगा, इस तरह के लघु-संचलन मौजूदा कोड को तोड़ सकते हैं जो ओवरलोड हो जाते हैं operator&&या operator||दोनों ऑपरेंड का मूल्यांकन करने पर निर्भर करता है। मौजूदा भाषा में सुविधाओं को जोड़ने पर पिछड़ी संगतता बनाए रखना (या होना चाहिए) महत्वपूर्ण है।
डेविड हैम्मन

जवाबों:


151

सभी डिज़ाइन प्रक्रियाओं के परिणामस्वरूप पारस्परिक असंगत लक्ष्यों के बीच समझौता होता है। दुर्भाग्य से, &&C ++ में ओवरलोड ऑपरेटर के लिए डिज़ाइन प्रक्रिया ने एक भ्रामक अंतिम परिणाम उत्पन्न किया: जो कि आप जिस सुविधा से चाहते हैं &&- उसका लघु-परिचायक व्यवहार - छोड़ा गया है।

इस दुर्भाग्यपूर्ण जगह पर उस डिजाइन प्रक्रिया का विवरण कैसे समाप्त हुआ, यह मैं नहीं जानता। हालांकि यह देखना प्रासंगिक है कि बाद की डिजाइन प्रक्रिया ने इस अप्रिय परिणाम को कैसे ध्यान में रखा। सी # में, अतिभारित &&ऑपरेटर है लघु सर्किटिंग। C # के डिजाइनरों ने कैसे हासिल किया?

अन्य उत्तरों में से एक "लंबोदर उठाने" का सुझाव देता है। अर्थात्:

A && B

नैतिक रूप से कुछ के रूप में महसूस किया जा सकता है:

operator_&& ( A, ()=> B )

जहाँ दूसरा तर्क आलसी मूल्यांकन के लिए कुछ तंत्र का उपयोग करता है ताकि मूल्यांकन करते समय, अभिव्यक्ति के दुष्प्रभाव और मूल्य उत्पन्न हों। अतिभारित ऑपरेटर के कार्यान्वयन केवल आवश्यक होने पर आलसी मूल्यांकन करेंगे।

यह वह नहीं है जो C # डिज़ाइन टीम ने किया था। (एक तरफ: हालांकि लैम्ब्डा लिफ्टिंग वह है जो मैंने तब किया था जब ऑपरेटर की अभिव्यक्ति ट्री प्रतिनिधित्व करने का समय आ गया था ??, जिसे कुछ रूपांतरण कार्यों को आलसी तरीके से करने की आवश्यकता होती है। यह बताते हुए कि विस्तार में हालांकि एक बड़ा विषयांतर होगा। कहने के लिए पर्याप्त है कि लैम्ब्डा लिफ्टिंग। काम करता है लेकिन पर्याप्त रूप से भारी है कि हम इसे टालना चाहते हैं।)

बल्कि, सी # समाधान समस्या को दो अलग-अलग समस्याओं में तोड़ देता है:

  • क्या हमें दाहिने हाथ के ऑपरेंड का मूल्यांकन करना चाहिए?
  • यदि उपरोक्त का उत्तर "हां" था, तो हम दोनों ऑपरेंड को कैसे जोड़ेंगे?

इसलिए समस्या को &&सीधे ओवरलोड करने के लिए अवैध बनाकर हल किया जाता है। बल्कि, C # में आपको दो ऑपरेटरों को ओवरलोड करना होगा , जिनमें से प्रत्येक उन दो प्रश्नों में से एक का उत्तर देता है।

class C
{
    // Is this thing "false-ish"? If yes, we can skip computing the right
    // hand size of an &&
    public static bool operator false (C c) { whatever }

    // If we didn't skip the RHS, how do we combine them?
    public static C operator & (C left, C right) { whatever }
    ...

(एक तरफ: वास्तव में, तीन। सी # की आवश्यकता है कि यदि ऑपरेटर falseप्रदान किया जाता है तो ऑपरेटर trueको भी प्रदान किया जाना चाहिए, जो प्रश्न का उत्तर देता है: क्या यह बात "सच-ईश है?"। आमतौर पर केवल एक ही ऐसे ऑपरेटर को उपलब्ध कराने का कोई कारण नहीं होगा # सी। दोनों की आवश्यकता है।)

फार्म के एक विवरण पर विचार करें:

C cresult = cleft && cright;

कंपाइलर इसके लिए कोड बनाता है जैसा आपने सोचा था कि आपने यह छद्म सी # लिखा था:

C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);

जैसा कि आप देख सकते हैं, बाएं हाथ की ओर हमेशा मूल्यांकन किया जाता है। यदि यह "असत्य-ईश" होना निर्धारित है तो यह परिणाम है। अन्यथा, दाहिने हाथ की ओर का मूल्यांकन किया जाता है, और उत्सुक उपयोगकर्ता-परिभाषित ऑपरेटर &को आमंत्रित किया जाता है।

||ऑपरेटर ऑपरेटर की एक मंगलाचरण सच्चे और उत्सुक के रूप में, अनुरूप तरह से परिभाषित किया गया है |ऑपरेटर:

cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);

सभी चार ऑपरेटरों को परिभाषित करके - true, false, &और |- सी # आप न केवल कहने के लिए अनुमति देता है cleft && cright, लेकिन यह भी गैर शॉर्ट सर्किट cleft & cright, और यह भी if (cleft) if (cright) ..., और c ? consequence : alternativeऔर while(c), और इतने पर।

अब, मैंने कहा कि सभी डिजाइन प्रक्रियाएं समझौता का परिणाम हैं। यहां C # भाषा डिजाइनर शॉर्ट-सर्कुलेटिंग &&और ||राइट प्राप्त करने में कामयाब रहे , लेकिन ऐसा करने के लिए दो के बजाय चार ऑपरेटरों को ओवरलोड करने की आवश्यकता होती है , जो कुछ लोग भ्रमित करते हैं। ऑपरेटर सही / गलत सुविधा C # में कम से कम अच्छी तरह से समझी जाने वाली सुविधाओं में से एक है। एक समझदार और सीधी भाषा होने का लक्ष्य जो C ++ उपयोगकर्ताओं के लिए परिचित है, इच्छाओं का विरोध कम सर्कुलेटिंग और लैम्ब्डा उठाने या आलसी मूल्यांकन के अन्य रूपों को लागू नहीं करने की इच्छा से किया गया था। मुझे लगता है कि एक उचित समझौता स्थिति थी, लेकिन यह महसूस करने के लिए यह महत्वपूर्ण है कि है एक समझौता स्थिति। बस एक अलग है C ++ के डिजाइनरों से समझौता स्थिति।

यदि ऐसे ऑपरेटरों के लिए भाषा डिज़ाइन का विषय आपको पसंद है, तो मेरी श्रृंखला को पढ़ने पर विचार करें क्यों C # इन ऑपरेटरों को अशक्त मल पर परिभाषित नहीं करता है:

http://ericlippert.com/2012/03/26/null-is-not-false-part-one/


1
@Deduplicator: आप इस प्रश्न और उत्तरों को पढ़ने के लिए भी इच्छुक हो सकते हैं: stackoverflow.com/questions/5965968/…
एरिक लिपिपर्ट

5
इस मामले में, मुझे लगता है कि समझौता उचित से अधिक है। जटिल सामान कुछ ऐसा है जो केवल एक कक्षा के पुस्तकालय के वास्तुकार से संबंधित होना चाहिए, और इस जटिलता के बदले में, यह पुस्तकालय की खपत को आसान और अधिक सहज बनाता है।
कोड़ी ग्रे

1
@EricLippert मेरा मानना ​​है कि एन्विज़न यह बता रहा था कि उसने इस पोस्ट को देखा और सोचा कि यह आप थे ... फिर देखा कि वह सही था। वह कह रहा था your postअप्रासंगिक है। His noticing your distinct writing styleअप्रासंगिक है।
वर्नरसीडी

5
Microsoft टीम को सी # और (2) में सही काम करने के लिए एक अच्छा अच्छा प्रयास करने के लिए (1) के लिए पर्याप्त क्रेडिट नहीं मिलता है और इसे सही से अधिक बार मिल रहा है।
कोडेनहेम

2
@Voo: यदि आप एक अंतर्निहित रूपांतरण को लागू करना चुनते हैं boolतो आप उपयोग कर सकते हैं &&और ||बिना कार्यान्वित operator true/falseया बिना operator &/|# समस्या के। समस्या ठीक उसी स्थिति में उत्पन्न होती है , जहां संभव होने के लिए कोई रूपांतरण नहीं boolहै , या जहां कोई वांछित नहीं है।
एरिक लिपर्ट

43

मुद्दा यह है कि (सी ++ 98 की सीमा के भीतर) दाहिने हाथ के ऑपरेंड को तर्क के रूप में ओवरलोडेड ऑपरेटर फ़ंक्शन को पारित किया जाएगा। ऐसा करने पर, इसका मूल्यांकन पहले से ही किया जाएगा । ऐसा कुछ भी नहीं है operator||()या operator&&()कोड ऐसा नहीं कर सकता है या ऐसा नहीं कर सकता है।

मूल ऑपरेटर अलग है, क्योंकि यह एक फ़ंक्शन नहीं है, लेकिन भाषा के निचले स्तर पर लागू किया गया है।

अतिरिक्त भाषा सुविधाओं सकता दाएँ हाथ के गैर मूल्यांकन संकार्य वाक्य रचना बना दिया है संभव । हालाँकि, वे परेशान नहीं हुए क्योंकि केवल कुछ चुनिंदा मामले हैं जहाँ यह शब्द उपयोगी होगा। (ठीक वैसे ही ? :, जो ओवरलोडिंग के लिए बिल्कुल भी उपलब्ध नहीं है।

(मेमनों को मानक में लाने के लिए उन्हें 16 साल लग गए ...)

शब्दार्थ उपयोग के लिए, विचार करें:

objectA && objectB

यह करने के लिए नीचे फोड़े:

template< typename T >
ClassA.operator&&( T const & objectB )

इस बारे में सोचें कि आप वास्तव में ऑब्जेक्टबी (अज्ञात प्रकार के) के साथ क्या करना चाहते हैं, रूपांतरण ऑपरेटर को कॉल करने के अलावा bool, और आप इसे भाषा की परिभाषा के लिए शब्दों में कैसे शामिल करेंगे।

और अगर आप बूल में रूपांतरण को बुला रहे हैं , तो ठीक है ...

objectA && obectB

एक ही बात करता है, अब करता है? तो पहली जगह में अधिभार क्यों?


7
अच्छी तरह से आपकी तर्क त्रुटि वर्तमान में परिभाषित भाषा के भीतर एक अलग परिभाषित भाषा के प्रभावों के कारण है। पुराने दिनों में बहुत सारे newbies उस wrt को करते थे। "वर्चुअल कंस्ट्रक्टर"। इसने उन्हें इस तरह की बॉक्स-सोच से बाहर निकालने के लिए स्पष्टीकरण का एक बड़ा हिस्सा लिया। वैसे भी, अंतर्निहित ऑपरेटरों के शॉर्ट-सर्कुलेटिंग के साथ तर्क गैर-मूल्यांकन के बारे में गारंटी होती है। यदि उपयोगकर्ता शॉर्ट-सर्कुलेटिंग उनके लिए परिभाषित करते हैं, तो ऐसी गारंटी उपयोगकर्ता-परिभाषित अधिभार के लिए भी होगी।
चीयर्स एंड हीथ। - अल्फ

1
@iFreilicht: मैंने मूल रूप से एक ही बात को Deduplicator या Piotr कहा है, बस अलग-अलग शब्दों के साथ। मैंने संपादित उत्तर में बिंदु पर थोड़ा विस्तार किया। यह इस तरह से बहुत अधिक सुविधाजनक था, आवश्यक भाषा एक्सटेंशन (जैसे लंबोदा) हाल तक तक मौजूद नहीं थे, और लाभ वैसे भी नगण्य रहा होगा। कुछ समय के लिए जिम्मेदार लोगों को "पसंद" कुछ ऐसा करना होगा जो पहले से ही कंपाइलर बिल्डरों द्वारा नहीं किया गया था, 1998 में वापस आ गया। (देखें export।)
DevSolar

9
@iFreilicht: boolकिसी भी वर्ग के लिए रूपांतरण ऑपरेटर के पास सभी सदस्य चरों तक पहुंच होती है, और बिल्टिन ऑपरेटर के साथ ठीक काम करता है। कुछ भी हो लेकिन रूपांतरण-से-बूल वैसे भी शॉर्ट-सर्किट मूल्यांकन के लिए शब्दार्थ नहीं है! इसे सिमेंटिक दृष्टिकोण से देखने की कोशिश करें, वाक्य-रचना की नहीं: जिसे आप हासिल करने की कोशिश कर रहे हैं, न कि आप इसके बारे में कैसे जाएँगे।
DevSolar

1
मुझे मानना ​​पड़ेगा कि मैं एक के बारे में नहीं सोच सकता। एकमात्र कारण शॉर्ट सर्किटिंग मौजूद है क्योंकि यह बूलियन पर संचालन के लिए समय बचाता है और आप सभी तर्कों का मूल्यांकन करने से पहले एक अभिव्यक्ति का परिणाम जान सकते हैं। अन्य और संचालन के साथ, ऐसा नहीं है, और यही वजह है &और &&एक ही ऑपरेटर नहीं हैं। मुझे यह महसूस करने में मदद करने के लिए धन्यवाद।
iFreilicht

8
@iFreilicht: बल्कि, शॉर्ट सर्कुलेटिंग का उद्देश्य है क्योंकि बाएं हाथ की ओर की गणना दाएं हाथ की तरफ की पूर्व शर्त की सच्चाई को स्थापित कर सकती हैif (x != NULL && x->foo)स्पीड के लिए नहीं, बल्कि सुरक्षा के लिए शॉर्ट सर्किटिंग की आवश्यकता होती है।
एरिक लिपर्ट

26

एक सुविधा के बारे में सोचा जाना चाहिए, डिजाइन, कार्यान्वित, प्रलेखित और भेज दिया गया।

अब हमने इसके बारे में सोचा, आइए देखें कि अब यह आसान क्यों हो सकता है (और तब करना मुश्किल है)। यह भी ध्यान रखें कि केवल सीमित मात्रा में संसाधन हैं, इसलिए इसे जोड़ने से कुछ और हो सकता है (आप इसके लिए क्या करना चाहेंगे?)।


सिद्धांत रूप में, सभी ऑपरेटर केवल एक "मामूली" अतिरिक्त भाषा-सुविधा के साथ लघु-परिचारक व्यवहार की अनुमति दे सकते थे , जैसा कि C ++ 11 (जब लैम्ब्डा को पेश किया गया था, 1979 में "सी विद क्लासेस" शुरू होने के 32 साल बाद, अभी भी एक सम्मानजनक 16 है c ++ 98 के बाद):

C ++ को एक तर्क की व्याख्या करने के लिए एक तरीके की आवश्यकता होगी जैसे कि आलसी-मूल्यांकित - एक छिपी-लंबो - मूल्यांकन से बचने के लिए जब तक परिशिष्ट और अनुमति नहीं दी जाती (पूर्व-शर्तों को पूरा किया गया)।


वह सैद्धांतिक विशेषता क्या दिखेगी (याद रखें कि कोई भी नई सुविधाएँ व्यापक रूप से प्रयोग करने योग्य होनी चाहिए)?

एक एनोटेशन lazy, जो एक फ़ंक्शन-तर्क पर लागू होता है, फ़ंक्शन को फ़ंक्टर की अपेक्षा करने वाला टेम्पलेट बनाता है, और कंपाइलर को फ़नकार में अभिव्यक्ति पैक करता है:

A operator&&(B b, __lazy C c) {return c;}

// And be called like
exp_b && exp_c;
// or
operator&&(exp_b, exp_c);

यह कवर के नीचे लगेगा:

template<class Func> A operator&&(B b, Func& f) {auto&& c = f(); return c;}
// With `f` restricted to no-argument functors returning a `C`.

// And the call:
operator&&(exp_b, [&]{return exp_c;});

इस बात का विशेष ध्यान रखें कि लैम्ब्डा छिपी रहे, और उसे एक ही बार में बुलाया जाएगा। इस वजह से कोई प्रदर्शन-क्षरण नहीं
होना चाहिए , जो सामान्य-सब-एक्सप्रेशन-उन्मूलन की कम संभावनाओं से अलग है।


कार्यान्वयन-जटिलता और वैचारिक जटिलता के अलावा (प्रत्येक सुविधा दोनों को बढ़ाती है, जब तक कि यह कुछ अन्य विशेषताओं के लिए उन जटिलताओं को पर्याप्त रूप से कम नहीं करती है), आइए एक और महत्वपूर्ण विचार पर गौर करें: बैकवर्ड-संगतता।

हालांकि यह भाषा-सुविधा किसी भी कोड को नहीं तोड़ती है, लेकिन यह इसका लाभ उठाते हुए किसी भी एपीआई को आसानी से बदल देगा, जिसका अर्थ है कि मौजूदा पुस्तकालयों में कोई भी उपयोग एक मौन ब्रेकिंग परिवर्तन होगा।

BTW: यह सुविधा, जबकि उपयोग करने में आसान है, कड़ाई से विभाजन के C # समाधान &&और ||अलग परिभाषा के लिए प्रत्येक दो कार्यों में से अधिक मजबूत है ।


6
@iFreilicht: फॉर्म का कोई भी प्रश्न "फ़ीचर X मौजूद क्यों नहीं है?" इसका एक ही उत्तर है: इस सुविधा को मौजूद करने के लिए एक अच्छा विचार माना जाता है, जिसे डिजाइन करने, निर्दिष्ट करने, लागू करने, परीक्षण करने, प्रलेखित करने और अंतिम उपयोगकर्ता को भेजने के लिए माना जाता है। यदि उन चीजों में से कोई भी नहीं हुआ, तो कोई विशेषता नहीं। उन चीजों में से एक आपकी प्रस्तावित विशेषता के साथ नहीं हुई; यह पता लगाना कि कौन सी एक ऐतिहासिक शोध समस्या है; डिज़ाइन कमेटी के लोगों से बात करना शुरू करें यदि आप परवाह करते हैं कि उन चीजों में से एक कभी नहीं किया गया था।
एरिक लिपिपर्ट

1
@ एरिकलिपर्ट: और, यह किस कारण पर निर्भर करता है, इसे लागू होने तक दोहराएं: शायद यह बहुत जटिल माना जाता था, और किसी ने पुनर्मूल्यांकन करने के लिए नहीं सोचा था। या पुनर्मूल्यांकन अलग-अलग कारणों से समाप्त हो गया, क्योंकि पहले आयोजित किया गया था। (btw: अपनी टिप्पणी के सार को जोड़ा)
डेडुप्लिकेटर 19

@Deduplicator अभिव्यक्ति टेम्पलेट्स के साथ न तो आलसी कीवर्ड और न ही लैम्ब्डा की आवश्यकता होती है।
सुमंत

एक ऐतिहासिक बात के रूप में, ध्यान दें कि मूल अल्गोल 68 भाषा में एक "प्रक्रियात्मक" जबरदस्ती थी (साथ ही साथ अवक्षेपण, जिसका अर्थ है कि संदर्भ रूप में फ़ंक्शन प्रकार के बजाय परिणाम प्रकार की आवश्यकता होने पर एक पैरामीटर रहित फ़ंक्शन को कॉल करना)। इसका मतलब यह है कि टाइप टी की एक स्थिति में टाइप के एक मान की आवश्यकता होती है, जिसमें "पैरामीटर रिटर्निंग टी" रिटर्निंग की वैल्यू होती है ( अल्गोल 68 में स्पेल्ड " प्रोक्योर टी") को दिए गए एक्सप्रेशन को वापस करने वाले फंक्शन बॉडी में बदल दिया जाएगा (निहित लैम्ब्डा)। भाषा के संशोधन में 1973 में इस सुविधा को हटा दिया गया था।
मार्क वैन लीउवेन

... सी ++ के लिए एक समान दृष्टिकोण ऑपरेटरों को घोषित करने के लिए हो सकता है जैसे &&टाइप करने के लिए "लॉजिक टू फंक्शन रिटर्निंग टी" का एक तर्क और एक अतिरिक्त रूपांतरण नियम जो टाइप टी के एक तर्क अभिव्यक्ति को स्पष्ट रूप से लंबोदर अभिव्यक्ति में परिवर्तित करने की अनुमति देता है। ध्यान दें कि यह एक सामान्य रूपांतरण नहीं है, क्योंकि इसे वाक्य-विन्यास के स्तर पर किया जाना चाहिए: रनटाइम को एक फ़ंक्शन में टाइप T के मान में बदलने से कोई फायदा नहीं होगा क्योंकि मूल्यांकन पहले ही हो चुका होगा।
मार्क वैन लीउवेन

13

भूतलक्षी युक्तिकरण के साथ, मुख्यतः क्योंकि

  • शॉर्ट-सर्कुलेटिंग (नए सिंटैक्स को पेश किए बिना) की गारंटी देने के लिए ऑपरेटरों को प्रतिबंधित करना होगा परिणामवास्तविक पहला तर्क परिवर्तनीय है bool, और

  • शॉर्ट सर्किटिंग को अन्य तरीकों से आसानी से व्यक्त किया जा सकता है, जब जरूरत होती है।


उदाहरण के लिए, यदि कोई वर्ग Tसंबद्ध है &&और ||ऑपरेटर है, तो अभिव्यक्ति

auto x = a && b || c;

जहाँ a, bऔर cप्रकार की अभिव्यक्तियाँ हैं T, लघु परिपथ के साथ व्यक्त की जा सकती है

auto&& and_arg = a;
auto&& and_result = (and_arg? and_arg && b : and_arg);
auto x = (and_result? and_result : and_result || c);

या शायद अधिक स्पष्ट रूप से

auto x = [&]() -> T_op_result
{
    auto&& and_arg = a;
    auto&& and_result = (and_arg? and_arg && b : and_arg);
    if( and_result ) { return and_result; } else { return and_result || b; }
}();

स्पष्ट अतिरेक ऑपरेटर चालानों से किसी भी दुष्प्रभाव को संरक्षित करता है।


जबकि लैम्बडा रीराइट अधिक क्रियात्मक है, इसके बेहतर एनकैप्सुलेशन से ऐसे ऑपरेटरों को परिभाषित किया जा सकता है

मैं निम्नलिखित में से सभी के मानक-अनुरूपता के बारे में पूरी तरह से आश्वस्त नहीं हूं (फिर भी थोड़ा प्रभावशाली), लेकिन यह विजुअल C ++ 12.0 (2013) और MinGW g ++ 4.8.2 के साथ साफ-साफ संकलन करता है:

#include <iostream>
using namespace std;

void say( char const* s ) { cout << s; }

struct S
{
    using Op_result = S;

    bool value;
    auto is_true() const -> bool { say( "!! " ); return value; }

    friend
    auto operator&&( S const a, S const b )
        -> S
    { say( "&& " ); return a.value? b : a; }

    friend
    auto operator||( S const a, S const b )
        -> S
    { say( "|| " ); return a.value? a : b; }

    friend
    auto operator<<( ostream& stream, S const o )
        -> ostream&
    { return stream << o.value; }

};

template< class T >
auto is_true( T const& x ) -> bool { return !!x; }

template<>
auto is_true( S const& x ) -> bool { return x.is_true(); }

#define SHORTED_AND( a, b ) \
[&]() \
{ \
    auto&& and_arg = (a); \
    return (is_true( and_arg )? and_arg && (b) : and_arg); \
}()

#define SHORTED_OR( a, b ) \
[&]() \
{ \
    auto&& or_arg = (a); \
    return (is_true( or_arg )? or_arg : or_arg || (b)); \
}()

auto main()
    -> int
{
    cout << boolalpha;
    for( int a = 0; a <= 1; ++a )
    {
        for( int b = 0; b <= 1; ++b )
        {
            for( int c = 0; c <= 1; ++c )
            {
                S oa{!!a}, ob{!!b}, oc{!!c};
                cout << a << b << c << " -> ";
                auto x = SHORTED_OR( SHORTED_AND( oa, ob ), oc );
                cout << x << endl;
            }
        }
    }
}

आउटपुट:

000 -> !! !! || असत्य
001 -> !! !! || सच
010 -> !! !! || असत्य
011 -> !! !! || सच
100 -> !! && !! || असत्य
101 -> !! && !! || सच
110 -> !! && !! सच
111 -> !! && !! सच

यहां प्रत्येक !!बैंग-बैंग रूपांतरण को दर्शाता है bool, अर्थात तर्क मान की जांच।

चूंकि एक कंपाइलर आसानी से एक ही कर सकता है, और इसके अतिरिक्त इसे ऑप्टिमाइज़ कर सकता है, यह एक प्रदर्शन किया गया संभावित कार्यान्वयन है और असंभवता के किसी भी दावे को उसी श्रेणी में रखा जाना चाहिए, जैसे कि सामान्य रूप से असंभवता के दावे, आमतौर पर बोललॉक।


मुझे आपके शॉर्ट सर्किट प्रतिस्थापन पसंद हैं, विशेष रूप से टर्नरी एक, जो जितना संभव हो उतना करीब है।
26फ्रेलीचट

आप शॉर्ट-सर्कुलेटिंग को याद कर रहे हैं &&- इसके लिए एक अतिरिक्त लाइन की आवश्यकता होगी if (!a) { return some_false_ish_T(); }- और आपकी पहली बुलेट के लिए: शॉर्ट-सर्कुलेटिंग बूल के लिए परिवर्तनीय मापदंडों के बारे में है, परिणाम नहीं।
अरने मर्ट्ज़

@ArneMertz: "मिसिंग" के बारे में आपकी टिप्पणी स्पष्ट रूप से अर्थहीन है। इसके बारे में टिप्पणी क्या है, हाँ मैं उससे अवगत हूं। शॉर्ट-सर्किटिंग boolकरने के लिए रूपांतरण आवश्यक है ।
चीयर्स एंड हीथ। - अल्फ

@ चेरसन्ध।-अल्फ गायब होने की टिप्पणी आपके उत्तर के पहले संशोधन के लिए थी, जहाँ आपने शॉर्ट-सर्किट किया था, ||लेकिन ऐसा नहीं किया गया &&। अन्य टिप्पणी का उद्देश्य "आपके पहले बुलेट बिंदु में बूल में परिवर्तनीय परिणामों तक सीमित रहना होगा" - इसे " बूल के लिए परिवर्तनीय मापदंडों के लिए प्रतिबंधित" पढ़ना चाहिए ।
Arne Mertz

@ArneMertz: ठीक है, पुन: संस्करण, खेद है कि मैं धीमी गति से संपादन कर रहा हूं। प्रतिबंधित है, नहीं, यह कोई ऑपरेटर परिणाम नहीं है जिसे प्रतिबंधित किया जाना है, क्योंकि इसे boolअभिव्यक्ति में आगे के ऑपरेटरों की छोटी चक्कर लगाने की जांच करने के लिए परिवर्तित करना होगा । जैसे, परिणामी को तार्किक या में शॉर्ट-सर्क्यूटिंग की जांच के लिए a && bपरिवर्तित करना boolहोगा a && b || c
चीयर्स एंड हीथ। - अल्फा

5

tl; dr : यह उच्च लागत (विशेष सिंटैक्स आवश्यक) की तुलना में बहुत कम मांग (जो सुविधा का उपयोग करेगा?) के कारण प्रयास के लायक नहीं है।

पहली बात जो दिमाग में आती है वह यह है कि ऑपरेटर ओवरलोडिंग कार्यों को लिखने के लिए सिर्फ एक फैंसी तरीका है, जबकि ऑपरेटरों का बुलियन संस्करण ||और &&बाइटलिन सामान हैं। इसका मतलब है कि कंपाइलर को उन्हें शॉर्ट-सर्किट करने की स्वतंत्रता है, जबकि x = y && zनॉनबुलियन के साथ अभिव्यक्ति yऔर zजैसे फ़ंक्शन के लिए कॉल करना है X operator&& (Y, Z)। इसका मतलब यह होगा कि y && zलिखने के लिए सिर्फ एक फैंसी तरीका है operator&&(y,z)जो केवल एक अजीब तरह से नामित फ़ंक्शन का एक कॉल है, जहां फ़ंक्शन को कॉल करने से पहले दोनों मापदंडों का मूल्यांकन किया जाना है (जिसमें कुछ भी ऐसा है जो शॉर्ट-सर्कुलेटिंग एप्रिपेट को हटा देगा)।

हालांकि, कोई यह तर्क दे सकता है कि &&ऑपरेटरों के अनुवाद को कुछ और अधिक परिष्कृत करना संभव हो सकता है , जैसे कि यह उस newऑपरेटर के लिए है जिसे फ़ंक्शन को कॉल करने के operator newबाद अनुवाद किया जाता है।

तकनीकी रूप से यह कोई समस्या नहीं होगी, किसी को उस भाषा के सिंटैक्स को परिभाषित करना होगा जो उस पूर्व शर्त के लिए विशिष्ट है जो शॉर्ट-सर्कुलेटिंग को सक्षम बनाता है। हालाँकि, शॉर्ट-सर्किट का उपयोग उन मामलों तक ही सीमित रहेगा, जो Yकि अनुकूल है X, वरना शॉर्ट सर्किटिंग (वास्तव में केवल पहले पैरामीटर से परिणाम की गणना कैसे करें) के बारे में अतिरिक्त जानकारी होनी चाहिए। परिणाम कुछ इस तरह देखना होगा:

X operator&&(Y const& y, Z const& z)
{
  if (shortcircuitCondition(y))
    return shortcircuitEvaluation(y);

  <"Syntax for an evaluation-Point for z here">

  return actualImplementation(y,z);
}

एक शायद ही कभी अधिभार चाहता है operator||और operator&&, क्योंकि वहाँ शायद ही कभी एक मामला है जहाँ लेखन a && bवास्तव में एक nonboolean संदर्भ में सहज है। केवल अपवादों के बारे में मुझे पता है कि एक्सपीरिएंस्ड डीएसएल के लिए एक्सप्रेशन टेम्प्लेट हैं। और केवल कुछ ही मामलों में शॉर्ट सर्किट मूल्यांकन से लाभ होगा। अभिव्यक्ति टेम्पलेट आमतौर पर नहीं होते हैं, क्योंकि उनका उपयोग अभिव्यक्ति के पेड़ों को बनाने के लिए किया जाता है जिनका मूल्यांकन बाद में किया जाता है, इसलिए आपको हमेशा अभिव्यक्ति के दोनों किनारों की आवश्यकता होती है।

संक्षेप में: न तो संकलक लेखकों और न ही मानकों के लेखकों को हुप्स के माध्यम से कूदने और अतिरिक्त बोझिल वाक्यविन्यास को परिभाषित करने और लागू करने की आवश्यकता महसूस हुई, सिर्फ इसलिए कि एक लाख में से एक को यह विचार मिल सकता है कि उपयोगकर्ता द्वारा परिभाषित operator&&और operator||केवल शॉर्ट सर्किटिंग करना अच्छा होगा इस निष्कर्ष पर पहुंचने के लिए कि यह प्रति हाथ तर्क लिखने से कम प्रयास नहीं है।


क्या वास्तव में लागत इतनी अधिक है? डी प्रोग्रामिंग भाषा मापदंडों को घोषित करने की अनुमति देती है, lazyजो दी गई अभिव्यक्तियों को तर्क के रूप में गुमनाम फ़ंक्शन में बदल देती है। यह उस फ़ंक्शन को उस तर्क को कॉल करने का विकल्प देता है, या नहीं। इसलिए यदि भाषा में पहले से ही लंबोदा है तो अतिरिक्त वाक्य रचना की जरूरत बहुत कम है। "स्यूडोकोड": एक्स और (ए, आलसी बी बी) {अगर (कंडोम (ए)) {वापसी कम (ए); } और {वास्तविक (ए, बी ()); }}
ब्लेक जेक

@ ब्लेकजैक कि आलसी पैरामीटर को स्वीकार करके लागू किया जा सकता है std::function<B()>, जो एक निश्चित ओवरहेड को उकसाएगा । या यदि आप इनलाइन बनाना चाहते हैं तो इसे बना लें template <class F> X and(A a, F&& f){ ... actual(a,F()) ...}। और शायद इसे "सामान्य" Bपैरामीटर के साथ अधिभारित किया जाए, इसलिए कॉल करने वाला तय कर सकता है कि किस संस्करण को चुनना है। lazyवाक्य रचना और अधिक सुविधाजनक हो सकता है लेकिन एक निश्चित प्रदर्शन दुविधा यह है हो सकता है।
Arne Mertz

1
std::functionबनाम समस्याओं में से lazyएक यह है कि पहले कई बार मूल्यांकन किया जा सकता है। एक आलसी पैरामीटर fooजिसका उपयोग foo+fooअभी भी केवल एक बार मूल्यांकन किया जाता है।
MSALERS

"शॉर्ट-सर्किट का उपयोग उन मामलों तक सीमित होगा जहां वाई एक्स के लिए अनुकूल है" ... नहीं, यह Xउन मामलों तक ही सीमित है जहां Yअकेले के आधार पर गणना की जा सकती है । बहुत अलग। std::ostream& operator||(char* a, lazy char*b) {if (a) return std::cout<<a;return std::cout<<b;}। जब तक आप "रूपांतरण" का बहुत ही आकस्मिक उपयोग नहीं कर रहे हैं।
मूविंग डक

1
@ शायद वे कर सकते हैं। लेकिन आप operator&&हाथ से शॉर्ट-सर्कुलेटिंग रिवाज का तर्क भी लिख सकते हैं । सवाल यह नहीं है कि अगर यह संभव है, लेकिन एक छोटा सुविधाजनक तरीका क्यों नहीं है।
Arne Mertz

5

लैम्ब्डा केवल आलस्य का परिचय देने का तरीका नहीं है। C ++ में अभिव्यक्ति टेम्प्लेट का उपयोग करके आलसी मूल्यांकन अपेक्षाकृत सीधा-आगे है। कीवर्ड की कोई आवश्यकता नहीं है lazyऔर इसे C ++ 98 में लागू किया जा सकता है। अभिव्यक्ति के पेड़ पहले से ही ऊपर उल्लेख हैं। अभिव्यक्ति टेम्पलेट गरीब (लेकिन चतुर) आदमी की अभिव्यक्ति के पेड़ हैं। चाल को Exprटेम्पलेट के पुनरावर्ती नेस्टेड इंस्ट्रमेंट के एक पेड़ में अभिव्यक्ति को परिवर्तित करना है । निर्माण के बाद पेड़ का मूल्यांकन अलग से किया जाता है।

निम्नलिखित कोड लागू शॉर्ट सर्किट &&और ||वर्ग के लिए ऑपरेटरों Sतक यह प्रदान करता है के रूप में के रूप में logical_andऔर logical_orमुक्त कार्य करता है और इसे करने के लिए परिवर्तनीय है bool। कोड C ++ 14 में है, लेकिन विचार C ++ 98 में भी लागू है। लाइव उदाहरण देखें ।

#include <iostream>

struct S
{
  bool val;

  explicit S(int i) : val(i) {}  
  explicit S(bool b) : val(b) {}

  template <class Expr>
  S (const Expr & expr)
   : val(evaluate(expr).val)
  { }

  template <class Expr>
  S & operator = (const Expr & expr)
  {
    val = evaluate(expr).val;
    return *this;
  }

  explicit operator bool () const 
  {
    return val;
  }
};

S logical_and (const S & lhs, const S & rhs)
{
    std::cout << "&& ";
    return S{lhs.val && rhs.val};
}

S logical_or (const S & lhs, const S & rhs)
{
    std::cout << "|| ";
    return S{lhs.val || rhs.val};
}


const S & evaluate(const S &s) 
{
  return s;
}

template <class Expr>
S evaluate(const Expr & expr) 
{
  return expr.eval();
}

struct And 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? logical_and(temp, evaluate(r)) : temp;
  }
};

struct Or 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? temp : logical_or(temp, evaluate(r));
  }
};


template <class Op, class LExpr, class RExpr>
struct Expr
{
  Op op;
  const LExpr &lhs;
  const RExpr &rhs;

  Expr(const LExpr& l, const RExpr & r)
   : lhs(l),
     rhs(r)
  {}

  S eval() const 
  {
    return op(lhs, rhs);
  }
};

template <class LExpr>
auto operator && (const LExpr & lhs, const S & rhs)
{
  return Expr<And, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator && (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<And, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

template <class LExpr>
auto operator || (const LExpr & lhs, const S & rhs)
{
  return Expr<Or, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator || (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<Or, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

std::ostream & operator << (std::ostream & o, const S & s)
{
  o << s.val;
  return o;
}

S and_result(S s1, S s2, S s3)
{
  return s1 && s2 && s3;
}

S or_result(S s1, S s2, S s3)
{
  return s1 || s2 || s3;
}

int main(void) 
{
  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << and_result(S{i}, S{j}, S{k}) << std::endl;

  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << or_result(S{i}, S{j}, S{k}) << std::endl;

  return 0;
}

5

तार्किक परिचालकों को लघु परिचालित करने की अनुमति है क्योंकि यह संबद्ध सत्य तालिकाओं के मूल्यांकन में एक "अनुकूलन" है। यह स्वयं तर्क का कार्य है, और यह तर्क परिभाषित है।

क्या वास्तव में एक कारण है कि अतिभारित &&और ||शॉर्ट सर्किट क्यों नहीं है?

कस्टम अतिभारित तार्किक ऑपरेटर इन सत्य तालिकाओं के तर्क का पालन करने के लिए बाध्य नहीं हैं ।

लेकिन अतिभारित होने पर वे इस व्यवहार को क्यों खो देते हैं?

इसलिए पूरे समारोह को सामान्य के अनुसार मूल्यांकन करने की आवश्यकता है। संकलक को इसे एक सामान्य अतिभारित ऑपरेटर (या फ़ंक्शन) के रूप में मानना ​​चाहिए और यह अभी भी अनुकूलन लागू कर सकता है क्योंकि यह किसी अन्य फ़ंक्शन के साथ होगा।

लोग विभिन्न कारणों से तार्किक ऑपरेटरों को अधिभारित करते हैं। उदाहरण के लिए; उनका एक विशिष्ट डोमेन में विशिष्ट अर्थ हो सकता है जो "सामान्य" तार्किक नहीं है जो लोग आदी हैं।


4

शॉर्ट-सर्कुलेटिंग "और" और "या" की सत्य तालिका के कारण है। आपको कैसे पता चलेगा कि उपयोगकर्ता किस ऑपरेशन को परिभाषित करने जा रहा है और आपको कैसे पता चलेगा कि आपको दूसरे ऑपरेटर का मूल्यांकन नहीं करना है?


जैसा कि टिप्पणियों में और @Deduplicators उत्तर में बताया गया है, यह एक अतिरिक्त भाषा सुविधा के साथ संभव होगा। मुझे पता है कि यह अब काम नहीं करता है। मेरा सवाल यह था कि इस तरह की सुविधा न होने के पीछे तर्क क्या था।
१३:१४

वैसे यह निश्चित रूप से एक जटिल विशेषता होगी, इस पर विचार करते हुए हमें उपयोगकर्ता की परिभाषा के बारे में अनुमान लगाना होगा!
एनजे-एथलीट

: (<condition>)ऑपरेटर द्वारा दूसरी शर्त का मूल्यांकन नहीं करने की घोषणा करने के बाद क्या होगा?
13:फ्रिलिच्ट सेप १ich

@iFreilicht: आपको अभी भी एक वैकल्पिक संयुक्त कार्य निकाय की आवश्यकता होगी।
एमएसलटर्स

3

लेकिन बूल के लिए ऑपरेटरों का यह व्यवहार है, इसे इस एकल प्रकार तक ही सीमित क्यों रखा जाना चाहिए?

मैं सिर्फ इस एक हिस्से का जवाब देना चाहता हूं। कारण यह है कि अंतर्निहित &&और ||अभिव्यक्ति कार्यों के साथ लागू नहीं होते हैं क्योंकि ओवरलोडेड ऑपरेटर हैं।

शॉर्ट-सर्कुलेटिंग लॉजिक बिल्ट-इन होने से कंपाइलर की विशिष्ट अभिव्यक्तियों को समझने में आसानी होती है। यह किसी भी अन्य अंतर्निहित नियंत्रण प्रवाह की तरह है।

लेकिन ऑपरेटर ओवरलोडिंग को इसके बजाय फ़ंक्शंस के साथ लागू किया जाता है, जिसमें विशेष नियम हैं, जिनमें से एक यह है कि तर्कों के रूप में उपयोग किए जाने वाले सभी अभिव्यक्तियों का मूल्यांकन फ़ंक्शन को कॉल करने से पहले किया जाता है। जाहिर है कि विभिन्न नियमों को परिभाषित किया जा सकता है, लेकिन यह एक बड़ा काम है।


1
मुझे आश्चर्य है कि अगर किसी भी विचार के सवाल को दिया गया था कि क्या की भार के &&, ||और ,अनुमति दी जानी चाहिए? तथ्य यह है कि C ++ में फ़ंक्शन कॉल के अलावा कुछ भी व्यवहार करने की अनुमति देने के लिए अधिभार की अनुमति देने के लिए कोई तंत्र नहीं है, यह बताता है कि उन कार्यों के अधिभार कुछ और क्यों नहीं कर सकते हैं, लेकिन यह नहीं समझाता है कि उन ऑपरेटरों को पहले स्थान पर अधिभार क्यों है। मुझे संदेह है कि असली कारण बस इतना है कि वे बिना सोचे समझे ऑपरेटरों की सूची में फेंक दिए गए।
सुपरकैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.