c++ - सरकारी जमीन पर कब्जा करने की सजा




एक कांस्टेबल चर को कभी-कभी लंबोदर में कैद करने की आवश्यकता क्यों नहीं होती है? (3)

निम्नलिखित उदाहरण पर विचार करें:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

मुझे दूसरे लैम्ब्डा में n पर कब्जा करने की आवश्यकता क्यों है लेकिन पहले लैम्ब्डा में m नहीं? मैंने C ++ 14 मानक में खंड 5.1.2 ( लैम्ब्डा एक्सप्रेशन ) की जाँच की लेकिन मुझे इसका कारण नहीं मिल पाया। क्या आप मुझे उस अनुच्छेद की ओर संकेत कर सकते हैं जिसमें यह समझाया गया है?

अद्यतन: मैंने इस व्यवहार को GCC 6.3.1 और 7 (ट्रंक) दोनों के साथ देखा। क्लैंग 4.0 और 5 (ट्रंक) दोनों मामलों में एक त्रुटि के साथ विफल रहता है ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified )।

https://code.i-harness.com


इसकी वजह यह एक निरंतर अभिव्यक्ति है, संकलक व्यवहार करता है जैसे कि यह था [] { 42; }(); [] { 42; }();

[ expr.prim.lambda ] में नियम है:

यदि एक लैम्ब्डा-एक्सप्रेशन या जेनेरिक लैम्ब्डा ओड्र-यूज़ (3.2) के फंक्शन कॉल ऑपरेटर टेम्प्लेट का एक तात्पर्य यह है या इसके पहुँच क्षेत्र से स्वचालित स्टोरेज अवधि वाला वैरिएबल है, तो उस लैम्बडा-एक्सप्रेशन द्वारा उस एंटिटी को कैप्चर किया जाएगा।

यहाँ मानक से एक उद्धरण [ basic.def.odr ]:

एक चर x जिसका नाम संभावित रूप से मूल्यांकित अभिव्यक्ति पूर्व के रूप में प्रकट होता है, जब तक कि x की एक स्थिर अभिव्यक्ति (...) या e नहीं होती है, तब तक ई-रिजेक्टेड रूपांतरण को लागू करने से पहले ई-उपयोग किया जाता है।

(इसे कम रखने के लिए इतना महत्वपूर्ण हिस्सा नहीं निकाला गया)

मेरी सरल समझ यह है: संकलक जानता है कि संकलन-समय पर m स्थिर है, जबकि n रन-टाइम में बदल जाएगा और इसलिए n को पकड़ना होगा। n ओडीआर का उपयोग किया जाएगा, क्योंकि आपको वास्तव में रन समय में n अंदर एक नज़र रखना होगा। दूसरे शब्दों में तथ्य यह है कि "केवल एक ही हो सकता है" n परिभाषा प्रासंगिक है।

यह MM की एक टिप्पणी से है:

m एक स्थिर अभिव्यक्ति है क्योंकि यह निरंतर अभिव्यक्ति initializer के साथ एक कास्ट ऑटोमैटिक वैरिएबल है, लेकिन n एक स्थिर अभिव्यक्ति नहीं है क्योंकि इसका initializer निरंतर अभिव्यक्ति नहीं था। यह [expr.const] / 2.7 में कवर किया गया है। [Basic.def.odr] / 3 के पहले वाक्य के अनुसार, निरंतर अभिव्यक्ति ODR- प्रयुक्त नहीं है

demo लिए यहां देखें।


ब्लॉक स्कोप में एक लैम्ब्डा के लिए, वेरिएबल तक पहुँचने के दायरे में कुछ मानदंडों को पूरा करने वाले वेरिएबल को लैम्बडा के अंदर सीमित तरीके से उपयोग किया जा सकता है, भले ही वे कैप्चर न हों।

मोटे तौर पर, स्कोप तक पहुंचने वाले किसी भी वैरिएबल लोकल में लैम्बडा वाले फंक्शन शामिल हैं, जो उस लैम्बडा को परिभाषित करने वाले बिंदु पर स्कोप में होगा। तो इसमें उपरोक्त उदाहरणों में m और n शामिल हैं।

"कुछ मानदंड" और "सीमित तरीके" विशेष रूप से (C ++ 14 के अनुसार) हैं:

  • लैम्ब्डा के अंदर, वैरिएबल ओड-यूज़ नहीं होना चाहिए, जिसका अर्थ है कि इसे छोड़कर किसी भी ऑपरेशन से गुजरना नहीं चाहिए:
    • एक परित्यक्त-मूल्य अभिव्यक्ति ( m; इन में से एक है) के रूप में दिखाई दे रहा है, या
    • इसके मूल्य को पुनः प्राप्त किया।
  • चर या तो होना चाहिए:
    • एक const , गैर- volatile पूर्णांक या एनम जिसका इनिशलाइज़र एक स्थिर अभिव्यक्ति था , या
    • एक constexpr , गैर- volatile चर (या इस तरह की एक उप-वस्तु)

C ++ 14 का संदर्भ: [expr.const] / 2.7, [basic.def.odr] / 3 (पहला वाक्य), [expr.prim.lambda] / 12, [expr.prim.lambda] / 10।

इन नियमों के लिए तर्क, जैसा कि अन्य टिप्पणियों / उत्तरों द्वारा सुझाया गया है, यह है कि संकलक को ब्लॉक से स्वतंत्र कार्य के रूप में एक नो-कैप्चर लैम्ब्डा को "संश्लेषित" करने में सक्षम होने की आवश्यकता है (क्योंकि ऐसी चीजों को एक पॉइंटर में बदला जा सकता है) कार्य करना); यह चर का उल्लेख करने के बावजूद ऐसा कर सकता है यदि यह जानता है कि चर का हमेशा एक ही मूल्य होगा, या यह चर के संदर्भ के स्वतंत्र मूल्य प्राप्त करने के लिए प्रक्रिया को दोहरा सकता है। लेकिन यह ऐसा नहीं कर सकता है यदि चर समय-समय पर भिन्न हो सकता है, या उदाहरण के लिए चर का पता आवश्यक है।

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

m को एक स्थिर अभिव्यक्ति 42 द्वारा आरंभ किया गया था, इसलिए यह "कुछ मानदंडों" को पूरा करता है। एक खारिज-मूल्य अभिव्यक्ति अभिव्यक्ति का उपयोग नहीं करता है, इसलिए m; m कैप्चर किए बिना उपयोग किया जा सकता है। जीसीसी सही है।

मैं कहूंगा कि दो संकलक के बीच का अंतर यह है कि क्लैंग m; मानता है m; odr-use m , लेकिन gcc नहीं करता है। [Basic.def.odr] / 3 का पहला वाक्य काफी जटिल है:

एक चर x जिसका नाम संभावित-मूल्यांकन अभिव्यक्ति ex रूप में प्रकट होता है, ex द्वारा ओआरडी-उपयोग किया जाता है जब तक कि ex -रे-लवल्यू रूपांतरण को x लागू करने से एक निरंतर अभिव्यक्ति प्राप्त होती है जो किसी भी गैर-तुच्छ कार्यों को लागू नहीं करती है और यदि 1 x एक वस्तु है , ex एक अभिव्यक्ति e के संभावित परिणामों के सेट का एक तत्व है, जहां या तो लवल्यू-टू-रेवल्यू रूपांतरण e लागू होता है, या e एक त्याग-मूल्य अभिव्यक्ति है।

लेकिन निकट से पढ़ने पर यह विशेष रूप से उल्लेख करता है कि एक त्याग-मूल्य अभिव्यक्ति अभिव्यक्ति का उपयोग नहीं करता है।

C ++ 11 के संस्करण [basic.def.odr] में मूल रूप से खारिज-मूल्य अभिव्यक्ति का मामला शामिल नहीं था, इसलिए क्लैंग का व्यवहार प्रकाशित C ++ 11 के तहत सही होगा। हालाँकि C ++ 14 में दिखाई देने वाला पाठ C ++ 11 ( अंक 712 ) के विरुद्ध दोष के रूप में स्वीकार किया गया था, इसलिए कंपाइलरों को C ++ 11 मोड में भी अपने व्यवहार को अपडेट करना चाहिए।


संपादित करें: मेरे उत्तर का पिछला संस्करण गलत था। शुरुआत सही है, यहां प्रासंगिक मानक उद्धरण है:

basic.def.odr

  1. एक चर x जिसका नाम संभावित-मूल्यांकन अभिव्यक्ति एक्स के रूप में प्रकट होता है, पूर्व द्वारा ओआरडी-उपयोग किया जाता है जब तक कि एक्स lvalue-to-rvalue रूपांतरण को एक्स पर लागू करने से एक निरंतर अभिव्यक्ति प्राप्त होती है जो किसी भी गैर-तुच्छ कार्यों को लागू नहीं करती है और यदि 1 एक्स एक वस्तु है , पूर्व एक अभिव्यक्ति ई के संभावित परिणामों के सेट का एक तत्व है, जहां या तो लवल्यू-टू-रेवल्यू रूपांतरण ई पर लागू होता है, या ई एक त्याग-मूल्य अभिव्यक्ति है। ...

चूंकि m एक स्थिर अभिव्यक्ति है, इसलिए यह ओड-यूज़ नहीं है और इसलिए इसे कैप्चर करने की आवश्यकता नहीं है।

ऐसा प्रतीत होता है कि क्लैंग्स व्यवहार मानक के अनुरूप नहीं है।







language-lawyer