วิธีสร้างตัวแปร for loop const ยกเว้นคำสั่งส่วนเพิ่ม

พิจารณามาตรฐานสำหรับการวนซ้ำ:

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

ฉันต้องการป้องกันไม่ให้ตัวแปร i ถูกแก้ไขในส่วนเนื้อหาของ for loop

อย่างไรก็ตาม ฉันไม่สามารถประกาศ i เป็น const ได้ เนื่องจากจะทำให้คำสั่งส่วนเพิ่มไม่ถูกต้อง มีวิธีสร้างตัวแปร i เป็น const ภายนอกคำสั่งส่วนเพิ่มหรือไม่


person jhourback    schedule 13.08.2020    source แหล่งที่มา
comment
ฉันเชื่อว่าไม่มีทางที่จะทำเช่นนี้   -  person Itay    schedule 13.08.2020
comment
คุณจะต้องซ่อนตัวแปรจากเนื้อหาของลูป ซึ่งอาจเปลี่ยนเป็น while(i_copy = loop()) { }   -  person stark    schedule 13.08.2020
comment
คุณสามารถอ้างอิงถึงมันได้อย่างต่อเนื่องในตัวของลูป เช่น const int& i_safe = i. คอมไพเลอร์ของคุณควรลบล้างการเบี่ยงเบนใดๆ   -  person Brian    schedule 13.08.2020
comment
@Brian นั่นยังคงทำให้ i เสี่ยงต่อตัวลูปที่เป็นอันตราย   -  person stark    schedule 13.08.2020
comment
ฉันคิดว่าวิธีแก้ปัญหาที่กำจัด i นั้นไม่ดีใช่ไหม คุณต้องการเข้าถึง i แบบอ่านอย่างเดียวในลูปหรือไม่   -  person cigien    schedule 13.08.2020
comment
ฉันอยากจะเสนอ for({int i=0; i<10; ++i}){ สำหรับเรื่องนี้มาโดยตลอด แต่ก็ไม่เคยมีความกล้าที่จะเสนอเรื่องนี้ต่อคณะกรรมการมาตรฐาน หรือ Bjarne เมื่อเขาทำงานอย่างซื่อสัตย์ที่ Morgan Stanley แน่นอนว่าคำแนะนำของฉันไม่อนุญาตให้คุณเข้าถึง i ในร่างกายด้วยซ้ำ   -  person Bathsheba    schedule 13.08.2020
comment
ถ้าคุณเงียบไป จะไม่มีอะไรเปลี่ยนแปลง เว้นแต่เขาจะเฆี่ยนตีคุณและไล่คุณออกจากมหาวิทยาลัยเพื่อให้คำแนะนำที่ดี (ถ้าเป็นเช่นนั้น คุณกำลังทำอะไรอยู่ที่นั่น?) ก็ไม่มีอะไรกล้าเสี่ยงหรือไม่มีอะไรได้รับ   -  person Michael Dorgan    schedule 13.08.2020
comment
ดูเหมือนเป็นวิธีแก้ปัญหาในการค้นหาปัญหา   -  person Pete Becker    schedule 13.08.2020
comment
เปลี่ยนเนื้อความของ for-loop ของคุณให้เป็นฟังก์ชันที่มีอาร์กิวเมนต์ const int i ความไม่แน่นอนของดัชนีจะแสดงเฉพาะเมื่อจำเป็นเท่านั้น และคุณสามารถใช้คีย์เวิร์ด inline เพื่อทำให้ไม่มีผลกระทบต่อเอาต์พุตที่คอมไพล์แล้ว   -  person Monty Thibault    schedule 14.08.2020
comment
อะไร (หรือมากกว่าใคร) ที่สามารถเปลี่ยนค่าของดัชนีได้นอกเหนือจาก .... คุณ? คุณไม่ไว้ใจตัวเองหรือเปล่า? อาจจะเป็นเพื่อนร่วมงาน? ฉันเห็นด้วยกับ @PeteBecker   -  person Z4-tier    schedule 14.08.2020
comment
@ Z4-tier ใช่ แน่นอนฉันไม่ไว้ใจตัวเอง ฉันรู้ว่าฉันทำผิดพลาด โปรแกรมเมอร์ที่ดีทุกคนรู้ดี นั่นเป็นเหตุผลที่เรามีสิ่งเช่น const ที่จะเริ่มต้นด้วย   -  person Konrad Rudolph    schedule 16.08.2020
comment
หากคุณต้องการหลีกเลี่ยงข้อผิดพลาด คุณไม่ควรใช้การจัดทำดัชนีสำหรับลูปเลย เนื่องจากลูปเหล่านั้นอาจถูกปิดด้วยข้อผิดพลาดเดียว   -  person Phil1970    schedule 17.08.2020
comment
ในทางปฏิบัติก็ไม่ควรเป็นปัญหา หากลูปของคุณมีขนาดเล็ก ก็จะเห็นได้ง่ายว่ามีการแก้ไข ì ในลูป หากลูปของคุณมีขนาดใหญ่ ก็ควรจะปรับโครงสร้างใหม่ให้เป็นฟังก์ชัน   -  person Phil1970    schedule 17.08.2020


คำตอบ (9)


จาก c++20 คุณสามารถใช้ ranges::views::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 ถูกจับโดยสำเนาที่ไม่สามารถเปลี่ยนแปลงได้ และสิ่งอื่นๆ จะถูกบันทึกโดยการอ้างอิงที่ไม่แน่นอน (); ที่ส่วนท้ายของลูปหมายความว่าแลมบ์ดาถูกเรียกใช้ทันที

person cigien    schedule 13.08.2020
comment
เกือบจะเรียกร้องให้มีการสร้าง for loop แบบพิเศษ เนื่องจากข้อเสนอนี้เป็นทางเลือกที่ปลอดภัยกว่าสำหรับโครงสร้างทั่วไปทั่วไป - person Michael Dorgan; 13.08.2020
comment
@MichaelDorgan ตอนนี้ที่มีการรองรับไลบรารีสำหรับฟีเจอร์นี้แล้ว มันไม่คุ้มค่าที่จะเพิ่มมันเป็นฟีเจอร์ภาษาหลัก - person cigien; 14.08.2020
comment
ยุติธรรม แม้ว่างานจริงของฉันเกือบทั้งหมดจะยังคงเป็น C หรือ C++11 ก็ตาม ฉันเรียนเผื่อว่ามันสำคัญสำหรับฉันในอนาคต... - person Michael Dorgan; 14.08.2020
comment
เคล็ดลับ C ++ 11 ที่คุณเพิ่มด้วยแลมบ์ดานั้นเรียบร้อย แต่ใช้ไม่ได้จริงในสถานที่ทำงานส่วนใหญ่ที่ฉันเคยไปมา การวิเคราะห์แบบคงที่จะบ่นเกี่ยวกับการดักจับ & ทั่วไปซึ่งจะบังคับให้จับการอ้างอิงแต่ละรายการอย่างชัดเจน - ซึ่งทำให้สิ่งนี้ ค่อนข้างยุ่งยาก ฉันยังสงสัยว่าสิ่งนี้อาจนำไปสู่ข้อผิดพลาดง่าย ๆ ที่ผู้เขียนลืม () ทำให้โค้ดไม่ถูกเรียกใช้ นี่มีขนาดเล็กพอที่จะพลาดในการตรวจสอบโค้ดได้อย่างง่ายดายเช่นกัน - person Human-Compiler; 14.08.2020
comment
มันเป็น IIILE หรือไม่ถ้ามันไม่เริ่มต้นอะไรเลย? แน่นอนว่ามากเกินไป 'ฉัน' - person Pete Kirkham; 14.08.2020
comment
แม้ว่าในทางเทคนิคแล้ว lambda จะเป็นวิธีแก้ปัญหาที่ดี แต่ในทางปฏิบัติ คุณมีโอกาสที่จะเกิดข้อผิดพลาดมากขึ้น ซึ่งก็คือข้อผิดพลาดที่ไม่น่าจะเป็นไปได้ในการแก้ไข i โดยไม่ได้ตั้งใจ และโค้ดนั้นเข้าใจยากกว่าสำหรับผู้อ่านส่วนใหญ่ - person Phil1970; 17.08.2020
comment
@ Human-Compiler ฉันไม่แน่ใจว่าฉันเห็นปัญหาที่คุณชี้ให้เห็น อย่างน้อยเสียงดังกราวจะ เตือน หากคุณพลาด () คำเตือนการวิเคราะห์แบบคงที่ก็ดูผิดเช่นกัน เนื้อความของลูปในโค้ดของ OP ควรมีสิทธิ์เข้าถึง ทุกประการ ตัวแปรเดียวกันอันเป็นผลมาจากการจับ & ในแลมบ์ดา - person cigien; 17.08.2020
comment
@ Phil1970 ฉันเข้าใจปัญหาที่ผู้อ่านส่วนใหญ่ไม่ทราบถึงสำนวน แต่คุณอ้างถึงข้อผิดพลาดอะไรกันแน่? คุณช่วยแสดงข้อผิดพลาดที่อาจเกิดขึ้นได้ไหม? - person cigien; 17.08.2020
comment
@cigien เครื่องมือวิเคราะห์แบบคงที่ เช่น SonarQube และ cppcheck ตั้งค่าสถานะการบันทึกทั่วไป เช่น [&] เนื่องจากสิ่งเหล่านี้ขัดแย้งกับ มาตรฐานการเข้ารหัสเช่น AUTOSAR (กฎ A5-1-2), HIC++ และฉันคิดว่า MISRA ด้วย (ไม่แน่ใจ) ไม่ใช่ว่ามันไม่ถูกต้อง องค์กรต่างๆ ห้ามใช้รหัสประเภทนี้เพื่อให้เป็นไปตามมาตรฐาน สำหรับ () นั้น เวอร์ชัน gcc ใหม่ล่าสุดจะไม่ติดธงสถานะนี้ แม้ว่าจะมี -Wextra ก็ตาม ฉันยังคิดว่าแนวทางนี้เรียบร้อย มันไม่ได้ผลสำหรับหลายๆ องค์กร - person Human-Compiler; 18.08.2020
comment
ฉันรู้ว่ากฎ SonarQube ที่ฉันเชื่อมโยงระบุว่าไม่มีอันตรายหากนำไปใช้ทันที อย่างไรก็ตามในทางปฏิบัติ ฉันเห็นว่ามันตั้งค่าสถานะการใช้งาน ใดๆ ของ [&] ในสภาพแวดล้อมที่แตกต่างกัน อาจเป็นไปได้ว่านี่อาจมาจากกฎเพิ่มเติมที่ตั้งขึ้นสำหรับการปฏิบัติตามมาตรฐานการเข้ารหัสที่ฉันกล่าวถึงข้างต้น ความจริงที่ว่า gcc ไม่ได้เตือนเมื่อไม่ได้เรียกใช้แลมบ์ดาทันที ฉันคิดว่าเป็นหนึ่งในความหายนะที่อาจเกิดขึ้นที่ @ Phil1970 อาจอ้างถึง - person Human-Compiler; 18.08.2020

สำหรับใครก็ตามที่ชอบคำตอบ std::views::iota ของ Cigien แต่ไม่ทำงานใน C++20 หรือสูงกว่า มันค่อนข้างตรงไปตรงมาที่จะใช้คลาส std::views::iota ="post-tag" title="show questions tag 'c++11'" rel="tag">c++11 หรือสูงกว่า

สิ่งที่ต้องการคือ:

  • ประเภท 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++ เวอร์ชันก่อนหน้า เช่น C++11/14 คุณอาจต้องลบ constexpr โดยที่มันไม่ถูกกฎหมายในเวอร์ชันเหล่านั้นจึงจะทำเช่นนั้นได้

ต้นแบบด้านบนช่วยให้โค้ดต่อไปนี้ทำงานใน pre-C ++ 20:

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

ซึ่งจะสร้างแอสเซมบลีเดียวกันเป็นโซลูชัน C++20 std::views::iota และโซลูชัน for-loop แบบคลาสสิก เมื่อปรับให้เหมาะสม

ซึ่งใช้งานได้กับคอมไพเลอร์ที่สอดคล้องกับ C++11 (เช่น คอมไพเลอร์เช่น gcc-4.9.4) และยังคงสร้าง แอสเซมบลีที่เกือบจะเหมือนกัน เป็นคู่พื้นฐาน for-loop

หมายเหตุ: ฟังก์ชันตัวช่วย iota มีไว้สำหรับความเท่าเทียมกันของคุณลักษณะกับโซลูชัน C++20 std::views::iota เท่านั้น แต่ในความเป็นจริงแล้ว คุณสามารถสร้าง iota_range{...} ได้โดยตรง แทนที่จะเรียก iota(...) แบบแรกเป็นเพียงการนำเสนอเส้นทางการอัพเกรดที่ง่ายหากผู้ใช้ต้องการเปลี่ยนไปใช้ C ++ 20 ในอนาคต

person Human-Compiler    schedule 13.08.2020
comment
มันต้องใช้เวลาสำเร็จรูปนิดหน่อย แต่จริงๆ แล้วมันไม่ได้ซับซ้อนขนาดนั้นในแง่ของสิ่งที่กำลังทำอยู่ จริงๆ แล้ว มันเป็นเพียงรูปแบบตัววนซ้ำพื้นฐาน แต่ล้อม int จากนั้นสร้างคลาสช่วงเพื่อส่งคืนจุดเริ่มต้น/จุดสิ้นสุด - person Human-Compiler; 14.08.2020
comment
ไม่สำคัญอย่างยิ่ง แต่ฉันยังเพิ่มโซลูชัน c ++ 11 ที่ไม่มีใครโพสต์ ดังนั้นคุณอาจต้องการเปลี่ยนคำในบรรทัดแรกของคำตอบของคุณใหม่เล็กน้อย :) - person cigien; 14.08.2020
comment
ฉันไม่แน่ใจว่าใครลงคะแนนเสียง แต่ฉันจะยินดีเป็นอย่างยิ่งหากคุณรู้สึกว่าคำตอบของฉันไม่น่าพอใจ เพื่อที่ฉันจะได้ปรับปรุงให้ดีขึ้น การลงคะแนนเสียงเป็นวิธีที่ดีในการแสดงว่าคุณรู้สึกว่าคำตอบไม่สามารถตอบคำถามได้เพียงพอ แต่ในกรณีนี้ ไม่มีการวิพากษ์วิจารณ์หรือข้อบกพร่องที่ชัดเจนในคำตอบที่ฉันอาจปรับปรุง - person Human-Compiler; 14.08.2020
comment
@ Human-Compiler ฉันได้รับ DV ในเวลาเดียวกันและพวกเขาไม่ได้แสดงความคิดเห็นว่าทำไมเช่นกัน :( เดาว่ามีคนไม่ชอบนามธรรมของช่วง ฉันจะไม่กังวลเกี่ยวกับมัน - person cigien; 14.08.2020
comment
Assembly เป็นคำนามที่สื่อถึงมวล เช่น กระเป๋าเดินทางหรือน้ำ การใช้ถ้อยคำปกติจะคอมไพล์เป็น แอสเซมบลี เดียวกันกับ C++20 .... เอาต์พุต asm ของคอมไพลเลอร์สำหรับฟังก์ชันเดียวไม่ใช่ a แอสเซมบลีเอกพจน์ มันคือแอสเซมบลี (ลำดับของคำสั่งภาษาแอสเซมบลี) - person Peter Cordes; 14.08.2020
comment
@PeterCordes - เช่นเดียวกับพหูพจน์ของน้ำก็คือน้ำ พหูพจน์ของแอสเซมบลีก็คือแอสเซมบลี คำว่า assembly ที่ใช้ในที่นี้มักถูกมองว่าเป็น sbeing horthand สำหรับการสอนภาษาแอสเซมบลี ซึ่งในกรณีนี้ บางที assemblies จะดีกว่าถ้าเป็นพหูพจน์ แต่ก็อาจมองว่าเป็นการชวเลขสำหรับชุดคำสั่งภาษาแอสเซมบลี (หรือที่เรียกว่าแอสเซมบลี) ซึ่งในกรณีนี้คำที่ถูกต้องคือแอสเซมบลีจริงๆ - person David Hammen; 15.08.2020
comment
@ Human-Compiler - downvotes แบบสุ่มสำหรับคำตอบที่ดีโดยไม่มีความคิดเห็นสำหรับ downvote นั้นเป็นเหตุการณ์ปกติในเครือข่าย StackExchange - person David Hammen; 15.08.2020
comment
คำวิจารณ์เดียวของฉันคือฟังก์ชัน iota ขัดแย้งกับเทมเพลตฟังก์ชัน c++11 std::iota ซึ่งทำบางสิ่งที่แตกต่างออกไปมาก วิธีแก้ไขคือเพิ่ม Constructor ให้กับคลาส iota_range ของคุณ แม้ว่าสิ่งนี้จะขัดแย้งกับ c++20 std::iota_range แต่ iota_range และ c++20 std::iota_range ของคุณก็ทำสิ่งเดียวกันได้สำเร็จ - person David Hammen; 15.08.2020
comment
@DavidHammen: ใช่แล้ว มีรูปแบบคำนามนับได้ของคำว่า Assembly ในภาษาอังกฤษ เพื่ออธิบายการประกอบสิ่งต่าง ๆ ในการคำนวณ คุณสามารถมีแอสเซมบลี .NET ได้ สังเกตบทความที่ไม่มีกำหนด a ที่นั่น; คุณสามารถมีแอสเซมบลี .NET ได้หลายรายการ แต่ในบริบทของเอาต์พุตภาษาแอสเซมบลีจากคอมไพเลอร์ มันเป็นคำนามจำนวนมากที่ไม่เป็นเอกพจน์ พิจารณาคอมไพเลอร์ C++ ที่คอมไพล์เป็น C: หากคุณพูดถึงเอาต์พุต คุณอาจบอกว่าแหล่งข้อมูล C++ ทั้งสองนี้ให้ผลลัพธ์เป็น C เดียวกัน ไม่ใช่ Cs เดียวกัน ภาษาแอสเซมบลีนั้นมีพื้นฐานมาจากรูปแบบที่นับไม่ได้ - person Peter Cordes; 15.08.2020
comment
@PeterCordes ฉันใช้คำว่า assemblies ในกรณีนี้เพราะฉันอ้างถึงผลลัพธ์ของโค้ดแอสเซมบลีจากสองแหล่งที่แตกต่างกัน C ++ 20 ปรับให้เหมาะสม std::views::Iota และสำนวน for วนซ้ำ ฉันคิดว่าในกรณีนี้ แอสเซมบลีเดียวกันหรือแอสเซมบลีเดียวกันที่มีคำศัพท์ถูกต้องทั้งคู่ แต่เมื่ออ่านซ้ำแล้ว ฉันก็เข้าใจว่าทำไมมันถึงฟังดูผิด - person Human-Compiler; 16.08.2020
comment
@DavidHammen ฉันเห็นด้วยกับความคิดเห็นที่คุณ iota ขัดแย้งกัน แต่ std::views::iota ก็ขัดแย้งกับ std::iota เช่นกัน ส่วนใหญ่ฉันเพิ่งเพิ่มสิ่งนี้เพื่อความเท่าเทียมกันกับเทียบเท่า C ++ 20 - แต่อย่างอื่นฉันก็เห็นด้วยอย่างยิ่งว่าฟังก์ชันนี้เป็นเพียงผิวเผินทั้งหมดและสามารถทำได้โดยใช้เพียงช่วง ฉันคิดว่ามันง่ายกว่าที่จะนำเสนอวิธีแก้ปัญหาที่มีลักษณะเหมือนกันกับคำตอบมาตรฐานที่กำหนดไว้ เนื่องจากเป็นการนำเสนอโซลูชันการอัพเกรดที่ชัดเจนยิ่งขึ้นหากโครงการใช้มาตรฐานที่ใหม่กว่า - person Human-Compiler; 16.08.2020

เวอร์ชั่นจูบ...

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

    // use i here
}

หากกรณีการใช้งานของคุณมีไว้เพื่อป้องกันการแก้ไขดัชนีลูปโดยไม่ได้ตั้งใจ สิ่งนี้น่าจะทำให้ข้อผิดพลาดดังกล่าวชัดเจน (หากคุณต้องการป้องกันการดัดแปลงโดยเจตนา ก็ขอให้โชคดี...)

person Artelius    schedule 14.08.2020
comment
ฉันคิดว่าคุณสอนบทเรียนผิดเรื่องการใช้ตัวระบุที่มีมนต์ขลังซึ่งขึ้นต้นด้วย _ และคำอธิบายเล็กน้อย (เช่น ขอบเขต) ก็จะเป็นประโยชน์ ไม่อย่างนั้นก็ KISSy อย่างดี - person Yunnosch; 14.08.2020
comment
การเรียกตัวแปรที่ซ่อนอยู่ i_ จะเข้ากันได้มากกว่า - person Yirkha; 14.08.2020
comment
ฉันไม่แน่ใจว่าสิ่งนี้ตอบคำถามอย่างไร ตัวแปรลูปคือ _i ซึ่งยังคงสามารถแก้ไขได้ในลูป - person cigien; 14.08.2020
comment
@cigien: IMO วิธีแก้ปัญหาบางส่วนนี้คุ้มค่าที่จะไม่ใช้ C++20 std::views::iota เพื่อการป้องกันกระสุนอย่างสมบูรณ์ ข้อความของคำตอบจะอธิบายข้อจำกัดและวิธีที่คำตอบพยายามตอบคำถาม C++11 ที่ซับซ้อนมากเกินไปทำให้การรักษาแย่กว่าโรคในแง่ของ IMO ที่อ่านง่าย ดูแลรักษาง่าย เรื่องนี้ยังคงอ่านได้ง่ายมากสำหรับทุกคนที่รู้ C++ และดูเหมือนเป็นสำนวนที่สมเหตุสมผล (แต่ควรหลีกเลี่ยงชื่อนำหน้า-ขีดล่าง) - person Peter Cordes; 14.08.2020
comment
@Yunnosch สงวนไว้เฉพาะตัวระบุ _Uppercase และ double__underscore เท่านั้น _lowercase ตัวระบุถูกสงวนไว้เฉพาะในขอบเขตส่วนกลางเท่านั้น - person Roman Odaisky; 14.08.2020

คุณไม่สามารถย้ายเนื้อหาบางส่วนหรือทั้งหมดของ for loop ในฟังก์ชันที่ยอมรับ i เป็น const ได้หรือไม่

มันเหมาะสมน้อยกว่าวิธีแก้ปัญหาบางอย่างที่เสนอ แต่ถ้าเป็นไปได้ ก็ทำได้ค่อนข้างง่าย

แก้ไข: เป็นเพียงตัวอย่างเพราะฉันมักจะไม่ชัดเจน

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

void looper ( const int v )
{
    // do your thing here
}
person Al rl    schedule 15.08.2020

หากคุณไม่สามารถเข้าถึง c ++20 การปรับปรุงทั่วไปโดยใช้ฟังก์ชัน

#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
}

(ดูการสาธิต)


อัปเดต: แรงบันดาลใจจากความคิดเห็นของ @Human-Compiler ฉันสงสัยว่าคำตอบที่ให้มามีความแตกต่างในกรณีของประสิทธิภาพหรือไม่ ปรากฎว่า ยกเว้นแนวทางนี้ สำหรับแนวทางอื่นๆ ทั้งหมดมีประสิทธิภาพเท่ากันอย่างน่าประหลาดใจ (สำหรับช่วง [0, 10)) แนวทาง std::vector นั้นแย่ที่สุด

ป้อนคำอธิบายรูปภาพที่นี่

(ดู Quick-Bench ออนไลน์)

person JeJo    schedule 13.08.2020
comment
แม้ว่าจะใช้งานได้กับ pre-c++20 แต่ก็มีค่าใช้จ่ายค่อนข้างมากเนื่องจากต้องใช้ vector หากช่วงมีขนาดใหญ่มากสิ่งนี้อาจไม่ดี - person Human-Compiler; 13.08.2020
comment
@ Human-Compiler: A std::vector ค่อนข้างแย่มากในระดับสัมพัทธ์หากช่วงนั้นเล็กเช่นกัน และอาจแย่มากหากนี่ควรจะเป็นวงในเล็ก ๆ ที่วิ่งหลายครั้ง คอมไพเลอร์บางตัว (เช่น clang ที่มี libc++ แต่ไม่ใช่ libstdc++) สามารถเพิ่มประสิทธิภาพการจัดสรรใหม่/การลบการจัดสรรที่ไม่หนีจากฟังก์ชันได้ แต่มิฉะนั้น นี่อาจเป็นความแตกต่างได้อย่างง่ายดายระหว่างการวนซ้ำแบบวนซ้ำขนาดเล็กกับการเรียกไปยัง new + delete และอาจจัดเก็บไว้ในหน่วยความจำนั้นจริงๆ - person Peter Cordes; 14.08.2020
comment
IMO ประโยชน์เล็กน้อยของ const i นั้นไม่คุ้มกับค่าใช้จ่ายในกรณีส่วนใหญ่ หากไม่มีวิธี C ++ 20 ที่ทำให้ราคาถูก โดยเฉพาะอย่างยิ่งกับช่วงตัวแปรรันไทม์ที่ทำให้คอมไพเลอร์มีโอกาสน้อยที่จะปรับทุกอย่างให้เหมาะสม - person Peter Cordes; 14.08.2020

และนี่คือเวอร์ชัน C++11:

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

นี่คือการสาธิตสด

person Vlad Feinstein    schedule 13.08.2020
comment
สิ่งนี้จะไม่ปรับขนาดหากจำนวนสูงสุดถูกกำหนดโดยค่ารันไทม์ - person Human-Compiler; 13.08.2020
comment
@ Human-Compiler เพียงขยายรายการเป็นค่าที่ต้องการและคอมไพล์โปรแกรมทั้งหมดของคุณใหม่แบบไดนามิก ;) - person Monty Thibault; 14.08.2020
comment
คุณไม่ได้พูดถึงกรณีของ {..} ว่าเป็นอย่างไร คุณต้องรวมบางอย่างเพื่อให้ฟีเจอร์นี้ใช้งานได้ ตัวอย่างเช่น รหัสของคุณจะใช้งานไม่ได้หากคุณไม่เพิ่มส่วนหัวที่เหมาะสม: godbolt.org/z/esbhra . การส่งต่อ <iostream> สำหรับส่วนหัวอื่นเป็นความคิดที่ไม่ดี! - person JeJo; 14.08.2020

#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
}
person Kaz    schedule 14.08.2020
comment
นี่คือคำตอบที่ดีที่สุดอย่างง่ายดาย การใช้ประโยชน์จาก 'การแชโดว์ตัวแปร' ของ C ++ เพื่อทำให้ตัวระบุแก้ไขตัวแปร const ref ที่อ้างอิงถึงตัวแปรขั้นตอนดั้งเดิมเป็นวิธีแก้ปัญหาที่หรูหรา หรืออย่างน้อยก็หรูหราที่สุดที่มีอยู่ - person Max Barraclough; 15.08.2020

วิธีการง่ายๆ วิธีหนึ่งที่ยังไม่ได้กล่าวถึงในที่นี้ ซึ่งใช้ได้กับ C++ เวอร์ชันใดก็ตามคือการสร้าง wrapper ฟังก์ชันรอบช่วง คล้ายกับที่ 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) แต่ก็ยังใช้งานได้

นี่คือการสาธิต


แม้ว่านี่อาจไม่ใช่สำนวนสำหรับ for ลูปใน C++ แต่แนวทางนี้ค่อนข้างใช้กันทั่วไปในภาษาอื่น เครื่องห่ออเนกประสงค์มีความทันสมัยมากสำหรับความสามารถในการจัดองค์ประกอบในข้อความที่ซับซ้อน และสามารถใช้งานได้ตามหลักสรีระศาสตร์

โค้ดนี้ยังเขียน ทำความเข้าใจ และบำรุงรักษาได้ง่ายอีกด้วย

person Human-Compiler    schedule 20.08.2020
comment
ข้อจำกัดประการหนึ่งที่ต้องระวังเมื่อใช้แนวทางนี้คือ บางองค์กรห้ามไม่ให้จับภาพเริ่มต้นบน lambdas (เช่น [&] หรือ [=]) เพื่อให้เป็นไปตามมาตรฐานความปลอดภัยบางประการ ซึ่งอาจทำให้ lambda ขยายใหญ่ขึ้นโดยสมาชิกแต่ละคนจำเป็นต้องถูกจับด้วยตนเอง ไม่ใช่ทุกองค์กรที่ทำเช่นนี้ ดังนั้นฉันจึงพูดถึงสิ่งนี้ว่าเป็นความคิดเห็นมากกว่าในคำตอบ - person Human-Compiler; 20.08.2020

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

person Hrisip    schedule 09.11.2020
comment
การไม่โต้แย้งเรื่องแลมบ์ดา auto const i ดูเหมือนจะเอาชนะประเด็นได้จริงๆ...! - person underscore_d; 06.02.2021
comment
@underscore_d ประเด็นคือการทำให้ตัวแปรตัวนับไม่สามารถแก้ไขได้ในลูปซึ่งก็คือ start ในตัวอย่างของฉัน ไม่ว่าคุณจะต้องการ i const หรือไม่ก็ขึ้นอยู่กับคุณ เนื่องจากมันไม่ใช่ประเด็น แม้ว่าจะถือเป็นโบนัสที่ดีก็ตาม - person Hrisip; 06.02.2021