हमें किन कारणों की आवश्यकता है?


161

C ++ 20 अवधारणाओं के कोनों में से एक यह है कि कुछ निश्चित परिस्थितियां हैं जिनमें आपको लिखना है requires requires। उदाहरण के लिए, इस उदाहरण से [expr.prim.req] / 3 :

एक आवश्यकता-अभिव्यक्ति का उपयोग एक आवश्यकता-खंड ([अस्थायी]) में भी किया जा सकता है, जैसे कि नीचे दिए गए टेम्पलेट तर्कों पर तदर्थ बाधाओं को लिखने के तरीके के रूप में:

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

पहले के लिए आवश्यकता-खंड का परिचय होता है , और दूसरा आवश्यकता-अभिव्यक्ति का परिचय देता है ।

उस दूसरे requiresकीवर्ड की आवश्यकता के पीछे तकनीकी कारण क्या है ? हम सिर्फ लिखने की अनुमति क्यों नहीं दे सकते:

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(नोट: कृपया जवाब न दें कि व्याकरण requiresयह)


25
सुझाव: "क्या ऐसा कुछ है जिसके लिए आवश्यकता होती है?" अधिक गंभीरता से, मेरे पास एक कूबड़ है जो इसके पीछे एक ही कारण है noexcept(noexcept(...))
क्वेंटिन

11
requiresमेरी राय में दो समलैंगिक हैं: वे एक जैसे दिखते हैं, एक ही जादू करते हैं, एक ही गंध करते हैं, लेकिन आंतरिक रूप से अलग हैं। अगर मुझे एक सुझाव देना था, तो मैं उनमें से एक का नाम बदलने का सुझाव दूंगा।
YSC

5
@ यस - co_requires? (क्षमा करें, विरोध नहीं कर सका)।
स्टोरीटेलर - Unslander मोनिका

122
पागलपन कहां रुक जाएगा? अगली बात जो आप जानते हैं, हमारे पास होगी long long
एलयज

8
@StoryTeller: "आवश्यकता के लिए आवश्यकता होती है?" और भी अधिक महत्वपूर्ण होगा!
पीडब्लू

जवाबों:


81

यह इसलिए है क्योंकि व्याकरण को इसकी आवश्यकता होती है। ऐसा होता है।

एक requiresबाधा को एक requiresअभिव्यक्ति का उपयोग करने की आवश्यकता नहीं है । यह किसी भी अधिक या कम मनमानी बूलियन स्थिर अभिव्यक्ति का उपयोग कर सकता है। इसलिए, requires (foo)एक वैध requiresबाधा होना चाहिए ।

एक requires अभिव्यक्ति (वह चीज जो यह परीक्षण करती है कि कुछ चीजें निश्चित बाधाओं का पालन करती हैं) एक अलग निर्माण है; यह केवल उसी कीवर्ड द्वारा पेश किया गया है। requires (foo f)एक वैध requiresअभिव्यक्ति की शुरुआत होगी ।

आप जो चाहते हैं, वह यह है कि यदि आप requiresकिसी ऐसी जगह का उपयोग करते हैं जो बाधाओं को स्वीकार करता है, तो आपको requiresक्लॉज़ से बाहर एक "बाधा + अभिव्यक्ति" बनाने में सक्षम होना चाहिए ।

यहाँ सवाल है: यदि आप requires (foo)एक जगह है कि एक बाधा की आवश्यकता के लिए उपयुक्त है में डाल दिया ... कितना दूर पार्सर जाने से पहले यह महसूस कर सकते हैं कि यह बाधा + अभिव्यक्ति के बजाय बाधा की आवश्यकता है जिस तरह से आप इसे चाहते हैं होने के लिए?

इस पर विचार करो:

void bar() requires (foo)
{
  //stuff
}

यदि fooएक प्रकार है, तो (foo)एक आवश्यक अभिव्यक्ति की एक पैरामीटर सूची है, और सब कुछ {}फ़ंक्शन का शरीर नहीं है, लेकिन उस requiresअभिव्यक्ति का शरीर है । अन्यथा, fooएक requiresखंड में एक अभिव्यक्ति है ।

ठीक है, आप कह सकते हैं कि संकलक को यह पता लगाना चाहिए कि fooपहले क्या है। लेकिन सी ++ वास्तव में इसे पसंद नहीं करता है जब टोकन के अनुक्रम को पार्स करने का मूल कार्य करने के लिए आवश्यक है कि कंपाइलर यह पता लगाए कि उन पहचानकर्ताओं का क्या मतलब है इससे पहले कि यह टोकन की समझ बना सके। हां, C ++ संदर्भ-संवेदनशील है, इसलिए ऐसा होता है। लेकिन समिति जहां संभव हो, वहां इसे टालना पसंद करती है।

तो हाँ, यह व्याकरण है।


2
यह एक प्रकार के साथ एक पैरामीटर सूची है, लेकिन एक नाम के बिना समझ में आता है?
नाथनऑलिवर

3
@ क्वेंटिन: C ++ व्याकरण में संदर्भ-निर्भरता के मामले निश्चित रूप से हैं। लेकिन समिति वास्तव में इसे कम करने की कोशिश करती है, और वे निश्चित रूप से अधिक जोड़ना पसंद नहीं करते हैं ।
निकोल बोलस

3
@RobertAndrzejuk: यदि टेम्पलेट तर्कों के requiresएक <>सेट के बाद या फ़ंक्शन पैरामीटर सूची के बाद दिखाई देता है , तो यह एक आवश्यकता-खंड है। यदि requiresऐसा प्रतीत होता है जहां एक अभिव्यक्ति वैध है, तो यह एक आवश्यकता-अभिव्यक्ति है। यह पार्स ट्री की संरचना द्वारा निर्धारित किया जा सकता है, न कि पार्स ट्री की सामग्री (एक पहचानकर्ता कैसे परिभाषित होता है की विशिष्टताएं पेड़ की सामग्री होगी)।
निकोल बोलस

6
@RobertAndrzejuk: निश्चित रूप से, आवश्यकता-अभिव्यक्ति एक भिन्न कीवर्ड का उपयोग कर सकती थी। लेकिन C ++ में कीवर्ड्स की बड़ी लागत होती है, क्योंकि उनके पास किसी भी प्रोग्राम को तोड़ने की क्षमता होती है जो पहचानकर्ता का उपयोग करता है जो एक कीवर्ड बन गया है। अवधारणाओं के प्रस्ताव ने पहले से ही दो कीवर्ड पेश किए हैं: conceptऔर requires। एक तीसरे का परिचय, जब दूसरा बिना किसी व्याकरणिक मुद्दों और कुछ उपयोगकर्ता-सामना की समस्याओं के साथ दोनों मामलों को कवर करने में सक्षम होगा, बस बेकार है। आखिरकार, केवल दृश्य समस्या यह है कि कीवर्ड दो बार दोहराया जाना होता है।
निकोल बोलस

3
@RobertAndrzejuk वैसे भी इनलाइन बाधाओं की तरह यह बुरा अभ्यास है क्योंकि आपको इस तरह की सदस्यता नहीं मिलती है जैसे कि आपने एक अवधारणा लिखी थी। तो इस तरह के एक कम उपयोग के लिए पहचानकर्ता की सिफारिश की सुविधा नहीं एक अच्छा विचार नहीं है।
Rakete1111

60

स्थिति बिल्कुल अनुरूप है noexcept(noexcept(...))। यकीन है, यह एक अच्छी बात की तुलना में एक बुरी बात की तरह लगता है, लेकिन मुझे समझाने दो। :) हम वही शुरू करेंगे जो आप पहले से जानते हैं:

सी ++ 11 में " noexcept-क्लॉस" और "-एक्सप्रेसेंस" हैं noexcept। वे अलग-अलग काम करते हैं।

  • ए- noexceptक्लॉज़ कहते हैं, "इस फ़ंक्शन को जब (कुछ स्थिति) ... यह एक फ़ंक्शन घोषणा पर जाता है, एक बूलियन पैरामीटर लेता है, और घोषित फ़ंक्शन में व्यवहार परिवर्तन का कारण बनता है।

  • ए- noexceptएक्सप्रेशन कहते हैं, "कंपाइलर, कृपया मुझे बताएं कि क्या (कुछ अभिव्यक्ति) नॉइसेप्ट है।" यह स्वयं एक बूलियन अभिव्यक्ति है। कार्यक्रम के व्यवहार पर इसका कोई "दुष्प्रभाव" नहीं है - यह सिर्फ एक हां / नहीं सवाल के जवाब के लिए संकलक से पूछ रहा है। "क्या यह अभिव्यक्ति noexcept है?"

हम -क्लोज़ के अंदर- एफ़एक्सप्रेशन को घोंसला बना सकते हैं , लेकिन हम आम तौर पर ऐसा करने के लिए इसे खराब शैली मानते हैं।noexceptnoexcept

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

यह noexceptएक प्रकार की विशेषता में theexpression एन्कैप्सुलेट करने के लिए बेहतर शैली माना जाता है ।

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

C ++ 2a वर्किंग ड्राफ्ट में " requires-clauses" और "-expressions" हैं requires। वे अलग-अलग काम करते हैं।

  • A requires-clause कहता है, "इस फ़ंक्शन को ओवरलोड रिज़ॉल्यूशन में भाग लेना चाहिए जब ... (कुछ शर्त)।" यह एक फ़ंक्शन घोषणा पर जाता है, एक बूलियन पैरामीटर लेता है, और घोषित फ़ंक्शन में व्यवहार परिवर्तन का कारण बनता है।

  • ए- requiresएक्सप्रेशन कहते हैं, "कंपाइलर, कृपया मुझे बताएं कि क्या (कुछ भावों का सेट) अच्छी तरह से बना है।" यह अपने आप में एक बूलियन अभिव्यक्ति है। कार्यक्रम के व्यवहार पर इसका कोई "दुष्प्रभाव" नहीं है - यह सिर्फ एक हां / नहीं सवाल के जवाब के लिए संकलक से पूछ रहा है। "क्या यह अभिव्यक्ति अच्छी तरह से बनाई गई है?"

हम -क्लोज़ के अंदर- एफ़एक्सप्रेशन को घोंसला बना सकते हैं , लेकिन हम आम तौर पर ऐसा करने के लिए इसे खराब शैली मानते हैं।requiresrequires

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

यह बेहतर शैली माना जाता है कि requiresएक प्रकार की विशेषता में -कंपनी को एनकैप्सुलेट करना ...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

... या (C ++ 2a वर्किंग ड्राफ्ट) अवधारणा में।

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2

1
मैं वास्तव में इस तर्क को नहीं खरीदता। noexceptसमस्या यह है कि है noexcept(f())मतलब हो सकता है या तो व्याख्या f()एक बूलियन कि हम विनिर्देश सेट करने के लिए प्रयोग कर रहे हैं के रूप में या जाँच करें कि क्या है या नहीं f()है noexceptrequiresयह अस्पष्टता नहीं है, क्योंकि इसकी वैधता की जाँच करने वाले भावों को पहले से ही {}एस के साथ पेश किया जाना है । उसके बाद, तर्क मूल रूप से "व्याकरण ऐसा कहता है।"
बैरी

@ बैरी: इस टिप्पणी को देखें । ऐसा लगता है {}कि वैकल्पिक हैं।
एरिक

1
@ ईरिक {}वैकल्पिक नहीं हैं, यह वह नहीं है जो टिप्पणी दिखाता है। हालांकि, यह एक महान टिप्पणी है जो पार्सिंग अस्पष्टता का प्रदर्शन करती है। शायद उस टिप्पणी को (कुछ स्पष्टीकरण के साथ) एक स्टैंडअलोन जवाब के रूप में स्वीकार करेंगे
बैरी

1
requires is_nothrow_incrable_v<T>;होना चाहिएrequires is_incrable_v<T>;
रुस्लान

16

मुझे लगता है कि cppreference के अवधारणा पृष्ठ यह बताते हैं। मैं "गणित" के साथ यह बताने के लिए व्याख्या कर सकता हूं कि ऐसा क्यों होना चाहिए:

यदि आप एक अवधारणा को परिभाषित करना चाहते हैं, तो आप ऐसा करते हैं:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression

यदि आप एक फ़ंक्शन घोषित करना चाहते हैं जो उस अवधारणा का उपयोग करता है, तो आप ऐसा करते हैं:

template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }

अब यदि आप अवधारणा को अलग से परिभाषित नहीं करना चाहते हैं, तो मुझे लगता है कि आपको बस कुछ प्रतिस्थापन करना होगा। इस भाग को लें requires (T x) { x + x; };और भाग को बदलें Addable<T>, और आपको मिलेगा:

template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }

जो आप के बारे में पूछ रहे हैं।


4
मुझे नहीं लगता कि सवाल क्या पूछ रहा है। यह व्याकरण की व्याख्या कर रहा है, कम या ज्यादा।
राहगीर

लेकिन आप क्यों नहीं कर सकते हैं template<typename T> requires (T x) { x + x; }और आवश्यकता है कि आवश्यकता दोनों खंड और अभिव्यक्ति हो सकती है?
नाथनऑलिवर

2
@NathanOliver: क्योंकि आप कंपाइलर को एक निर्माण को दूसरे के रूप में व्याख्या करने के लिए मजबूर कर रहे हैं। एक requires-as-बाधा खंड नहीं है के लिए है एक हो requires-expression। वह इसका केवल एक संभव उपयोग है।
निकोल बोलस

2
@ TheQuantumPhysicist मुझे अपनी टिप्पणी पर क्या मिल रहा था, यह जवाब सिर्फ वाक्यविन्यास बताता है। नहीं कि वास्तविक तकनीकी कारण क्या है requires requires। वे अनुमति देने के लिए व्याकरण में कुछ जोड़ सकते थे template<typename T> requires (T x) { x + x; }लेकिन उन्होंने ऐसा नहीं किया। बैरी जानना चाहते हैं कि उन्होंने ऐसा क्यों नहीं किया
नाथनऑलिवर

3
अगर हम वास्तव में यहाँ व्याकरण-अस्पष्टता खोज रहे हैं, तो ठीक है, मैं काटूँगा। godbolt.org/z/i6n8kM का template<class T> void f(T) requires requires(T (x)) { (void)x; }; मतलब है कि अगर आप किसी एक को हटाते हैं तो कुछ अलग है requires
क्क्सप्लसोन

12

मुझे एंड्रयू सुटन (कॉन्सेप्ट के लेखकों में से एक, जिसने इसे gcc में लागू किया था) से एक टिप्पणी मिली, जो इस संबंध में काफी मददगार है, इसलिए मैंने सोचा कि मैं इसे इसके निकट-पूर्णता में उद्धृत करूंगा:

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

हालांकि, 2016 में, उस प्रतिबंध को शिथिल करने का प्रस्ताव था [संपादक का नोट: P0266 ]। पेपर के खंड 4 में पैरा 4 के स्ट्राइकथ्रू पर ध्यान दें। और इस तरह पैदा हुआ था आवश्यकता की आवश्यकता है।

सच बताने के लिए, मैंने वास्तव में जीसीसी में उस प्रतिबंध को कभी लागू नहीं किया था, इसलिए यह हमेशा संभव था। मुझे लगता है कि वाल्टर ने उस कागज को खोजा और उसे उपयोगी पाया होगा।

किसी को भी यह नहीं लगता है कि मैं दो बार लिखने के लिए संवेदनशील नहीं था, मैंने यह निर्धारित करने में कुछ समय बिताया कि क्या इसे सरल बनाया जा सकता है। संक्षिप्त उत्तर: नहीं।

समस्या यह है कि दो व्याकरणिक निर्माण हैं जिन्हें टेम्पलेट पैरामीटर सूची के बाद पेश करने की आवश्यकता है: बहुत ही सामान्य रूप से एक बाधा अभिव्यक्ति (जैसे P && Q) और कभी-कभी वाक्यगत आवश्यकताएं (जैसे requires (T a) { ... })। इसे एक आवश्यकता-अभिव्यक्ति कहा जाता है।

पहली आवश्यकता बाधा का परिचय देती है। दूसरा आवश्यकता-अभिव्यक्ति का परिचय देता है। जिस तरह से व्याकरण रचना करता है। मुझे यह बिल्कुल भ्रामक नहीं लगता।

मैंने कोशिश की, एक बिंदु पर, इन्हें एक ही आवश्यकता के रूप में ढहाने के लिए। दुर्भाग्य से, यह कुछ गंभीर रूप से कठिन पार्सिंग समस्याओं की ओर जाता है। आप आसानी से नहीं बता सकते हैं, उदाहरण के लिए यदि (आवश्यकता के बाद नेस्टेड सब-डेप्रिसिएशन या पैरामीटर-सूची को दर्शाता है। मुझे विश्वास नहीं है कि उन सिंटैक्स का एक पूर्ण विस्मरण है (यूनिफ़ॉर्म इनिशियलाइज़ेशन सिंटैक्स के लिए तर्क देखें; यह समस्या वहाँ भी है)।

तो आप एक विकल्प बनाते हैं: मेक इन ए एक्सप्रेशन की आवश्यकता है (जैसा कि यह अभी करता है) या इसे आवश्यकताओं की एक मानकीकृत सूची का परिचय दें।

मैंने वर्तमान दृष्टिकोण को चुना क्योंकि अधिकांश समय (लगभग 100% समय में), मैं एक आवश्यकता-अभिव्यक्ति के अलावा कुछ और चाहता हूं। और बहुत ही दुर्लभ मामले में मैं तदर्थ बाधाओं के लिए एक आवश्यकता-अभिव्यक्ति चाहता था, मुझे वास्तव में दो बार शब्द लिखने में कोई आपत्ति नहीं है। यह एक स्पष्ट संकेतक है कि मैंने टेम्पलेट के लिए पर्याप्त ध्वनि अमूर्तता विकसित नहीं की है। (क्योंकि अगर मेरे पास होता तो इसका एक नाम होता।)

मैं एक आवश्यकता-अभिव्यक्ति की आवश्यकता को लागू करने के लिए चुना जा सकता था। यह वास्तव में बदतर है, क्योंकि व्यावहारिक रूप से आपके सभी अवरोध इस तरह दिखने लगेंगे:

template<typename T>
  requires { requires Eq<T>; }
void f(T a, T b);

यहां, 2 की आवश्यकता को नेस्टेड-आवश्यकता कहा जाता है; यह अपनी अभिव्यक्ति का मूल्यांकन करता है (आवश्यकता-अभिव्यक्ति के ब्लॉक में अन्य कोड का मूल्यांकन नहीं किया जाता है)। मुझे लगता है कि यह यथास्थिति से भी बदतर है। अब, आपको हर जगह दो बार लिखने की आवश्यकता है।

मैं और भी कीवर्ड इस्तेमाल कर सकता था। यह अपने आप में एक समस्या है --- और यह सिर्फ बाइक शेडिंग नहीं है। दोहराव से बचने के लिए कीवर्ड को "पुनर्वितरित" करने का एक तरीका हो सकता है, लेकिन मैंने उस गंभीर विचार को नहीं दिया है। लेकिन यह वास्तव में समस्या का सार नहीं बदलता है।


-10

क्योंकि आप कह रहे हैं कि A की आवश्यकता B की आवश्यकता है, और B की आवश्यकता C की आवश्यकता है।

A को B की आवश्यकता है जो बदले में C की आवश्यकता है।

"आवश्यकता" खंड के लिए स्वयं कुछ की आवश्यकता होती है।

आपके पास ए (बी की आवश्यकता सी) की आवश्यकता है।

भावहीन। :)


4
लेकिन अन्य उत्तरों के अनुसार, पहला और दूसरा requiresवैचारिक रूप से एक ही चीज़ नहीं है (एक खंड है, एक अभिव्यक्ति है)। वास्तव में, यदि मैं सही ढंग से समझ, के दो सेट ()में requires (requires (T x) { x + x; })बहुत अलग अर्थ है (बाहरी किया जा रहा है वैकल्पिक और हमेशा एक बूलियन constexpr युक्त; भीतरी शुरू करने का एक अनिवार्य हिस्सा एक अभिव्यक्ति की आवश्यकता जा रहा है और नहीं वास्तविक भाव अनुमति)।
मैक्स लैंगहॉफ

2
@MaxLanghof क्या आप कह रहे हैं कि आवश्यकताएं अलग हैं? : D
ऑर्बिट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.