वृद्धिशील बयान के अपवाद के साथ लूप चर कास्ट के लिए कैसे करें?


84

लूप के लिए एक मानक पर विचार करें:

for (int i = 0; i < 10; ++i) 
{
   // do something with i
}

मैं चर iको forलूप के शरीर में संशोधित होने से रोकना चाहता हूं ।

हालांकि, मैं घोषणा नहीं कर सकते हैं iके रूप में constके रूप में इस वेतन वृद्धि बयान अमान्य बना देता है। क्या वेतन वृद्धि वक्तव्य के बाहर iएक constचर बनाने का एक तरीका है ?


4
मेरा मानना ​​है कि ऐसा करने का कोई तरीका नहीं है
Itay

27
यह एक समस्या की तलाश में एक समाधान की तरह लगता है।
पीट बेकर

14
एक const int iतर्क के साथ अपने शरीर के लूप को एक फ़ंक्शन में बदल दें । इंडेक्स की परिवर्तनशीलता केवल उसी स्थान पर उजागर होती है जहां इसकी आवश्यकता होती है और आप inlineकीवर्ड का उपयोग कर सकते हैं ताकि संकलित आउटपुट पर इसका कोई प्रभाव न हो।
मोंटी थिबॉल्ट

4
क्या (या बल्कि, कौन) संभवतः सूचकांक के मूल्य को बदल सकता है .... आप? क्या आप अपना अविश्वास करते हैं? शायद एक सहकर्मी? मैं @ पेबैक के साथ सहमत हूं।
Z4-tier

5
@ Z4- स्तरीय हाँ, निश्चित रूप से मैं खुद को अविश्वास करता हूं। मुझे पता है कि मैं गलतियाँ करता हूँ। हर अच्छा प्रोग्रामर जानता है। इसलिए हमारे पास constशुरू करने के लिए चीजें हैं ।
कोनराड रुडोल्फ

जवाबों:


120

C ++ 20 से, आप श्रेणियों का उपयोग कर सकते हैं :: विचार :: iota इस तरह:

for (int const i : std::views::iota(0, 10))
{
   std::cout << i << " ";  // ok
   i = 42;                 // error
}

यहाँ एक डेमो है


C ++ 11 से, आप निम्न तकनीक का भी उपयोग कर सकते हैं, जिसमें एक IIILE का उपयोग किया जाता है (तुरंत इनवॉल्ड लैम्बडा एक्सप्रेशन)

int x = 0;
for (int i = 0; i < 10; ++i) [&,i] {
    std::cout << i << " ";  // ok, i is readable
    i = 42;                 // error, i is captured by non-mutable copy
    x++;                    // ok, x is captured by mutable reference
}();     // IIILE

यहाँ एक डेमो है

ध्यान दें कि [&,i]इसका मतलब है कि iगैर-उत्परिवर्तित प्रतिलिपि द्वारा कैप्चर किया गया है, और बाकी सब कुछ उत्परिवर्तित संदर्भ द्वारा कैप्चर किया गया है। ();पाश के अंत में का अर्थ है कि लैम्ब्डा तुरंत शुरू हो जाती है।


लूप निर्माण के लिए एक विशेष के लिए लगभग कॉल करता है क्योंकि यह ऑफ़र बहुत ही सामान्य निर्माण के लिए एक सुरक्षित विकल्प है।
माइकल डोरगन

2
@MichaelDorgan खैर, अब जब इस सुविधा के लिए पुस्तकालय का समर्थन है, तो यह इसे मुख्य भाषा सुविधा के रूप में जोड़ने के लायक नहीं होगा।
22

1
हालांकि, मेरा लगभग सभी वास्तविक काम अभी भी C या C ++ 11 है। मैं सिर्फ उस स्थिति में अध्ययन करता हूं जब यह मेरे लिए भविष्य में मायने रखता है ...
माइकल डोरगन

9
लैंबडा के साथ आपके द्वारा जोड़ा गया C ++ 11 ट्रिक साफ-सुथरा है, लेकिन मेरे द्वारा किए गए अधिकांश कार्यस्थलों में व्यावहारिक नहीं होगा। स्टेटिक विश्लेषण सामान्यीकृत &कैप्चर पर शिकायत करेगा , जो प्रत्येक संदर्भ को स्पष्ट रूप से कैप्चर करने पर मजबूर करेगा - जो इसे काफी बनाता है बोझिल। मुझे यह भी संदेह है कि यह आसान बग पैदा कर सकता है जहां एक लेखक भूल जाता है (), जिससे कोड कभी भी आह्वान नहीं होता है। यह आसानी से कोड-समीक्षा में याद करने के लिए काफी छोटा है।
मानव-संकलक

1
@cigien सोनारक्यूब और कैपचेक फ्लैग जनरल जैसे स्टैटिस्टिक एनालिसिस टूल सामान्य कैप्चर करते हैं [&]क्योंकि ये कोडिंग मानकों जैसे कि AUTOSAR (नियम A5-1-2), HIC ++, और मुझे लगता है कि MISRA (सुनिश्चित नहीं हैं)। ऐसा नहीं है कि यह सही नहीं है; यह है कि संगठन मानकों के अनुरूप होने के लिए इस प्रकार के कोड पर प्रतिबंध लगाते हैं। के लिए के रूप में (), नवीनतम gcc संस्करण के साथ भी यह झंडा नहीं है-Wextra । मुझे अभी भी लगता है कि दृष्टिकोण साफ-सुथरा है; यह सिर्फ कई संगठनों के लिए काम नहीं करता है।
मानव-संकलक

44

किसी के लिए जो कि Cigien का std::views::iotaजवाब पसंद करता है, लेकिन C ++ 20 या इसके बाद के संस्करण में काम नहीं कर रहा है, यह std::views::iotaसंगत के सरलीकृत और हल्के लागू करने के लिए सीधा है या ऊपर।

इसकी आवश्यकता है:

  • एक मूल " LegacyInputIterator " प्रकार (कुछ ऐसा जो परिभाषित करता है operator++और operator*) जो एक अभिन्न मूल्य (जैसे एक int) को लपेटता है
  • कुछ "रेंज" जैसी श्रेणी जिसके पास है begin()और end()जो उपरोक्त पुनरावृत्तियों को लौटाता है। यह इसे रेंज-आधारित forलूप में काम करने की अनुमति देगा

इसका एक सरलीकृत संस्करण हो सकता है:

#include <iterator>

// This is just a class that wraps an 'int' in an iterator abstraction
// Comparisons compare the underlying value, and 'operator++' just
// increments the underlying int
class counting_iterator
{
public:
    // basic iterator boilerplate
    using iterator_category = std::input_iterator_tag;
    using value_type = int;
    using reference  = int;
    using pointer    = int*;
    using difference_type = std::ptrdiff_t;

    // Constructor / assignment
    constexpr explicit counting_iterator(int x) : m_value{x}{}
    constexpr counting_iterator(const counting_iterator&) = default;
    constexpr counting_iterator& operator=(const counting_iterator&) = default;

    // "Dereference" (just returns the underlying value)
    constexpr reference operator*() const { return m_value; }
    constexpr pointer operator->() const { return &m_value; }

    // Advancing iterator (just increments the value)
    constexpr counting_iterator& operator++() {
        m_value++;
        return (*this);
    }
    constexpr counting_iterator operator++(int) {
        const auto copy = (*this);
        ++(*this);
        return copy;
    }

    // Comparison
    constexpr bool operator==(const counting_iterator& other) const noexcept {
        return m_value == other.m_value;
    }
    constexpr bool operator!=(const counting_iterator& other) const noexcept {
        return m_value != other.m_value;
    }
private:
    int m_value;
};

// Just a holder type that defines 'begin' and 'end' for
// range-based iteration. This holds the first and last element
// (start and end of the range)
// The begin iterator is made from the first value, and the
// end iterator is made from the second value.
struct iota_range
{
    int first;
    int last;
    constexpr counting_iterator begin() const { return counting_iterator{first}; }
    constexpr counting_iterator end() const { return counting_iterator{last}; }
};

// A simple helper function to return the range
// This function isn't strictly necessary, you could just construct
// the 'iota_range' directly
constexpr iota_range iota(int first, int last)
{
    return iota_range{first, last};
}

मैंने ऊपर constexprजहां यह समर्थित है, के साथ परिभाषित किया है, लेकिन C ++ 11/14 जैसे C ++ के पुराने संस्करणों के लिए, आपको constexprयह हटाने की आवश्यकता हो सकती है कि ऐसा करने के लिए उन संस्करणों में कानूनी नहीं है।

उपरोक्त बॉयलरप्लेट प्री-सी ++ 20 में काम करने के लिए निम्नलिखित कोड को सक्षम करता है:

for (int const i : iota(0, 10))
{
   std::cout << i << " ";  // ok
   i = 42;                 // error
}

जो अनुकूलित होने पर C ++ 20 समाधान और क्लासिक- लूप समाधान के समान विधानसभा उत्पन्न करेगा ।std::views::iotafor

यह किसी भी C ++ 11-कंपाइलर कंपाइलर (जैसे कंपाइलर gcc-4.9.4) के साथ काम करता है और फिर भी एक बेसिक- क्लोजर समकक्ष के लिए लगभग समान असेंबली का उत्पादन करता forहै।

नोट:iota सहायक समारोह सिर्फ सी ++ 20 के साथ सुविधा समता के लिए है std::views::iotaसमाधान; लेकिन वास्तविक रूप से, आप iota_range{...}कॉल करने के बजाय सीधे निर्माण भी कर सकते हैं iota(...)। यदि उपयोगकर्ता भविष्य में C ++ 20 पर स्विच करना चाहता है तो पूर्व केवल एक आसान अपग्रेड पथ प्रस्तुत करता है।


3
इसे बॉयलरप्लेट की थोड़ी आवश्यकता होती है, लेकिन यह वास्तव में यह सब जटिल नहीं है कि यह क्या कर रहा है। यह वास्तव में सिर्फ एक बुनियादी पुनरावृत्ति पैटर्न है, लेकिन एक लपेटकर int, फिर शुरुआत / अंत लौटने के लिए एक "रेंज" वर्ग का निर्माण
मानव-संकलक

1
सुपर महत्वपूर्ण नहीं है, लेकिन मैं भी एक c ++ 11 समाधान कि कोई और पोस्ट जोड़ा है, तो आप थोड़ा अपने जवाब की पहली पंक्ति reword को :) चाहते हो सकता है
cigien

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

@ मानव-कंपाइलर मुझे एक ही समय में DV भी मिला, और उन्होंने इस पर कोई टिप्पणी नहीं की: या तो किसी को लगता है कि किसी को रेंज एब्सट्रैक्शन पसंद नहीं है। मैं इसके बारे में चिंता नहीं
करूंगा

1
"असेंबली" एक सार्वजनिक संज्ञा है जैसे "सामान" या "पानी"। सामान्य वाक्यांशलेखन " C ++ 20 ..." के समान सभा में संकलित होगा । किसी एकल फ़ंक्शन के लिए कंपाइलर का asm आउटपुट एक विलक्षण असेंबली नहीं है , यह "असेंबली" (असेंबली-भाषा निर्देशों का एक क्रम) है।
पीटर कॉर्ड्स

29

KISS संस्करण ...

for (int _i = 0; _i < 10; ++_i) {
    const int i = _i;

    // use i here
}

यदि आपका उपयोग मामला सिर्फ लूप इंडेक्स के आकस्मिक संशोधन को रोकने के लिए है, तो इस तरह के बग को स्पष्ट करना चाहिए। (यदि आप जानबूझकर संशोधन, अच्छी तरह से, सौभाग्य को रोकना चाहते हैं ...)


11
मुझे लगता है कि आप जादुई पहचानकर्ताओं का उपयोग करने के लिए गलत सबक सिखाते हैं जो इसके साथ शुरू होता है _। और थोडा स्पष्टीकरण (जैसे स्कोप) मददगार होगा। अन्यथा, हाँ, अच्छी तरह से चुंबन देता हुअा।
युनानोश

14
"छिपे हुए" चर i_को कॉल करना अधिक आज्ञाकारी होगा।
यरकाह

9
मुझे यकीन नहीं है कि यह सवाल का जवाब कैसे देता है। लूप वेरिएबल वह है _iजो लूप में अभी भी परिवर्तनीय है।
सिजेरियन

4
@ साइगैन: IMO, यह आंशिक समाधान है जहाँ तक यह std::views::iotaपूरी तरह से बुलेटप्रूफ तरीके से C ++ 20 के बिना जाने लायक है । उत्तर का पाठ इसकी सीमाओं को बताता है और यह प्रश्न का उत्तर देने का प्रयास करता है। अधिक जटिल सी ++ 11 का एक गुच्छा आसानी से पढ़ने, आसानी से बनाए रखने योग्य, आईएमओ के मामले में बीमारी से बदतर बना देता है। यह उन सभी के लिए पढ़ना बहुत आसान है जो C ++ जानते हैं, और मुहावरे के रूप में उचित लगते हैं। (लेकिन प्रमुख-अंडरस्कोर नामों से बचना चाहिए।)
पीटर कॉर्ड्स

5
@Yunnosch केवल _Uppercaseऔर double__underscoreपहचानकर्ता आरक्षित हैं। _lowercaseपहचानकर्ता केवल वैश्विक दायरे में आरक्षित हैं।
रोमन ओडिसी

13

क्या आप किसी फंक्शन में लूप के लिए अपने या अपने कंटेंट की सभी सामग्री को स्थानांतरित नहीं कर सकते हैं जो मुझे एक कास्ट के रूप में स्वीकार करता है?

प्रस्तावित कुछ समाधानों की तुलना में इसकी कम इष्टतम है, लेकिन यदि संभव हो तो यह करना काफी सरल है।

संपादित करें: जैसा कि मैं स्पष्ट नहीं कर रहा हूँ बस एक उदाहरण है।

for (int i = 0; i < 10; ++i) 
{
   looper( i );
}

void looper ( const int v )
{
    // do your thing here
}

12

यदि आपके पास पहुंच नहीं है , एक समारोह का उपयोग कर विशिष्ट बदलाव

#include <vector>
#include <numeric> // std::iota

std::vector<int> makeRange(const int start, const int end) noexcept
{
   std::vector<int> vecRange(end - start);
   std::iota(vecRange.begin(), vecRange.end(), start);
   return vecRange;
}

अब आप कर सकते हैं

for (const int i : makeRange(0, 10))
{
   std::cout << i << " ";  // ok
   //i = 100;              // error
}

( एक डेमो देखें )


अद्यतन : @ मानव-संकलक की टिप्पणी से प्रेरित , मैं सोच रहा था कि दिए गए उत्तरों में प्रदर्शन के मामले में कोई अंतर है। यह पता चला है कि, इस दृष्टिकोण को छोड़कर, अन्य सभी दृष्टिकोणों के लिए आश्चर्यजनक रूप से समान प्रदर्शन (सीमा के लिए [0, 10)) है। std::vectorदृष्टिकोण सबसे खराब है।

यहां छवि विवरण दर्ज करें

( ऑनलाइन क्विक-बेंच देखें )


4
हालांकि यह प्री-सी ++ 20 के लिए काम करता है, इसमें बहुत बड़ी मात्रा में ओवरहेड है क्योंकि इसे उपयोग करने की आवश्यकता होती है vector। यदि सीमा बहुत बड़ी है, तो यह खराब हो सकता है।
ह्यूमन-कंपाइलर

@ ह्यूमन-कंपाइलर: std::vectorयदि रेंज छोटा है, तो एक रिश्तेदार पैमाने पर बहुत भयानक है, और यह बहुत बुरा हो सकता है यदि यह एक छोटा आंतरिक लूप माना जाए जो कई बार चलता हो। कुछ संकलक (जैसे कि libcd ++ के साथ क्लैंग, लेकिन libstdc ++ नहीं) एक कार्य आवंटन से नए / डिलीट को हटा सकता है जो फ़ंक्शन से बच नहीं सकता है, लेकिन अन्यथा यह आसानी से एक छोटे से पूर्ण-अनियंत्रित लूप बनाम कॉल टू new+ के बीच का अंतर हो सकता है delete, और शायद वास्तव में उस मेमोरी में स्टोर हो रहा है।
पीटर कॉर्ड्स

IMO, का मामूली लाभ const iज्यादातर मामलों के लिए ओवरहेड के लायक नहीं है, बिना C ++ 20 तरीके के जो इसे सस्ता बनाते हैं। खासकर रनटाइम-वैरिएबल रेंज के साथ जो कंपाइलर के लिए हर चीज को ऑप्टिमाइज़ करने की संभावना कम कर देती है।
पीटर कॉर्ड्स

10

और यहाँ एक C ++ 11 संस्करण है:

for (int const i : {0,1,2,3,4,5,6,7,8,9,10})
{
    std::cout << i << " ";
    // i = 42; // error
}

यहाँ लाइव डेमो है


6
यदि अधिकतम संख्या एक रनटाइम मान द्वारा तय की जाती है तो यह पैमाना नहीं है।
ह्यूमन-कंपाइलर

12
@ ह्यूमन-कंपाइलर केवल सूची को वांछित मान तक बढ़ाता है और आपके पूरे कार्यक्रम को गतिशील रूप से पुन: व्यवस्थित करता है;)
मोंटी थिबॉल्ट

5
आपने यह नहीं बताया कि मामला क्या है {..}। इस सुविधा को सक्रिय करने के लिए आपको कुछ शामिल करना होगा। उदाहरण के लिए, यदि आप उचित हेडर नहीं जोड़ते हैं तो आपका कोड टूट जाएगा: godbolt.org/z/esbhra<iostream>अन्य हेडर के लिए पर रिले करना एक बुरा विचार है!
JeJo

6
#include <cstdio>
  
#define protect(var) \
  auto &var ## _ref = var; \
  const auto &var = var ## _ref

int main()
{
  for (int i = 0; i < 10; ++i) 
  {
    {
      protect(i);
      // do something with i
      //
      printf("%d\n", i);
      i = 42; // error!! remove this and it compiles.
    }
  }
}

नोट: हमें भाषा में एक आश्चर्यजनक मूर्खता के कारण गुंजाइश को घोंसला बनाने की आवश्यकता है: for(...)शीर्ष लेख में घोषित चर को उसी घोंसले के स्तर पर माना जाता है क्योंकि {...}यौगिक विवरण में घोषित चर । इसका मतलब है कि, उदाहरण के लिए:

for (int i = ...)
{
  int i = 42; // error: i redeclared in same scope
}

क्या? क्या हमने सिर्फ एक घुंघराला ब्रेस नहीं खोला? इसके अलावा, यह असंगत है:

void fun(int i)
{
  int i = 42; // OK
}

1
यह आसानी से सबसे अच्छा जवाब है। पहचानकर्ता को मूल चरण चर का संदर्भ देने वाले कांस्ट रेफरी चर के समाधान के लिए C ++ का 'वैरिएबल शैडोइंग' का उपयोग करना एक सुंदर समाधान है। या कम से कम, सबसे सुंदर एक उपलब्ध है।
मैक्स बैरक्लोफ

4

एक सरल दृष्टिकोण जो अभी तक यहां उल्लेख नहीं किया गया है कि सी ++ के किसी भी संस्करण में काम करता है, एक रेंज के चारों ओर एक कार्यात्मक आवरण बनाने के लिए है, जो कि std::for_eachपुनरावृत्तियों के समान है। उपयोगकर्ता तब कॉलबैक के रूप में एक कार्यात्मक तर्क में पारित करने के लिए जिम्मेदार होता है जिसे प्रत्येक पुनरावृत्ति पर लागू किया जाएगा।

उदाहरण के लिए:

// A struct that holds the start and end value of the range
struct numeric_range
{
    int start;
    int end;

    // A simple function that wraps the 'for loop' and calls the function back
    template <typename Fn>
    void for_each(const Fn& fn) const {
        for (auto i = start; i < end; ++i) {
            const auto& const_i = i;
            fn(const_i);
        }
    }
};

उपयोग कहां होगा:

numeric_range{0, 10}.for_each([](const auto& i){
   std::cout << i << " ";  // ok
   //i = 100;              // error
});

C ++ 11 से अधिक पुरानी कोई भी चीज़ एक नामित-नामित फ़ंक्शन पॉइंटर में for_each(समान std::for_each) से गुज़रती हुई रुकी होगी , लेकिन यह अभी भी काम करती है।

यहाँ एक डेमो है


यद्यपि यह C ++for में लूप के लिए मुहावरेदार नहीं हो सकता है , यह दृष्टिकोण अन्य भाषाओं में काफी आम है। कार्यात्मक रैपर जटिल बयानों में उनकी रचनाशीलता के लिए वास्तव में चिकना हैं और उपयोग के लिए बहुत एर्गोनोमिक हो सकते हैं।

यह कोड लिखने, समझने और बनाए रखने के लिए भी सरल है।


इस दृष्टिकोण से अवगत होने के लिए एक सीमा यह है कि कुछ संगठन कुछ सुरक्षा मानकों का अनुपालन करने के लिए लैंबडास (जैसे [&]या [=]) पर डिफ़ॉल्ट कैप्चर पर प्रतिबंध लगाते हैं , जो लैंबडा को प्रत्येक सदस्य को मैन्युअल रूप से कैप्चर करने की आवश्यकता होती है। सभी संगठन ऐसा नहीं करते हैं, इसलिए मैं केवल उत्तर के बजाय टिप्पणी के रूप में इसका उल्लेख कर रहा हूं।
ह्यूमन-कंपाइलर

0
template<class T = int, class F>
void while_less(T n, F f, T start = 0){
    for(; start < n; ++start)
        f(start);
}

int main()
{
    int s = 0;
    
    while_less(10, [&](auto i){
        s += i;
    });
    
    assert(s == 45);
}

शायद इसे बुलाओ for_i

कोई ओवरहेड https://godbolt.org/z/e7asGj

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.