C ++ เทียบเท่ากับรูปแบบตัวสร้าง Python


117

ฉันมีตัวอย่างโค้ด Python ที่ต้องเลียนแบบใน C ++ ฉันไม่ต้องการโซลูชันที่เฉพาะเจาะจงใด ๆ (เช่นโซลูชันผลตอบแทนตามกิจวัตรร่วมแม้ว่าจะเป็นคำตอบที่ยอมรับได้เช่นกัน) แต่ฉันต้องสร้างความหมายในบางลักษณะ

หลาม

นี่คือตัวสร้างลำดับขั้นพื้นฐานซึ่งมีขนาดใหญ่เกินไปที่จะจัดเก็บเวอร์ชันที่เป็นรูปธรรมอย่างชัดเจน

def pair_sequence():
    for i in range(2**32):
        for j in range(2**32):
            yield (i, j)

เป้าหมายคือการรักษาสองอินสแตนซ์ของลำดับข้างต้นและวนซ้ำในช่วงกึ่งล็อค แต่เป็นชิ้น ๆ ในตัวอย่างด้านล่างfirst_passใช้ลำดับของคู่เพื่อเริ่มต้นบัฟเฟอร์และsecond_passสร้างลำดับที่แน่นอนเหมือนกันและประมวลผลบัฟเฟอร์อีกครั้ง

def run():
    seq1 = pair_sequence()
    seq2 = pair_sequence()

    buffer = [0] * 1000
    first_pass(seq1, buffer)
    second_pass(seq2, buffer)
    ... repeat ...

C ++

สิ่งเดียวที่ฉันสามารถหาวิธีแก้ปัญหาใน C ++ คือการเลียนแบบโครูทีนของyieldC ++ แต่ฉันไม่พบข้อมูลอ้างอิงที่ดีเกี่ยวกับวิธีการทำเช่นนี้ ฉันสนใจทางเลือกอื่น (ไม่ใช่วิธีแก้ปัญหาทั่วไป) สำหรับปัญหานี้ ฉันไม่มีงบประมาณหน่วยความจำเพียงพอที่จะเก็บสำเนาของลำดับระหว่างรอบ


ดังที่คุณเห็นจากที่นี่stackoverflow.com/questions/3864410/… Coroutine ไม่ควรนำไปใช้ คุณไม่สามารถทำได้ด้วยการอ่านบัฟเฟอร์? stackoverflow.com/questions/4685862/…
batbaatar

ตัววนซ้ำ C ++ ควรรองรับสิ่งนี้
Lalaland

คำตอบ:


79

เครื่องกำเนิดไฟฟ้าที่มีอยู่ใน C ++ เพียงภายใต้ชื่ออื่น: Iterators การป้อนข้อมูล ยกตัวอย่างเช่นการอ่านจากเป็นคล้ายกับมีเครื่องกำเนิดไฟฟ้าของstd::cinchar

คุณต้องเข้าใจว่าเครื่องกำเนิดไฟฟ้าทำอะไร:

  • มีข้อมูลหยดหนึ่ง: ตัวแปรภายในกำหนดสถานะ
  • มีวิธีการเริ่มต้น
  • มีวิธี "ถัดไป"
  • มีวิธีส่งสัญญาณการยุติ

ในตัวอย่างเล็กน้อยของคุณมันง่ายพอ แนวคิด:

struct State { unsigned i, j; };

State make();

void next(State&);

bool isDone(State const&);

แน่นอนเราห่อสิ่งนี้เป็นชั้นเรียนที่เหมาะสม:

class PairSequence:
    // (implicit aliases)
    public std::iterator<
        std::input_iterator_tag,
        std::pair<unsigned, unsigned>
    >
{
  // C++03
  typedef void (PairSequence::*BoolLike)();
  void non_comparable();
public:
  // C++11 (explicit aliases)
  using iterator_category = std::input_iterator_tag;
  using value_type = std::pair<unsigned, unsigned>;
  using reference = value_type const&;
  using pointer = value_type const*;
  using difference_type = ptrdiff_t;

  // C++03 (explicit aliases)
  typedef std::input_iterator_tag iterator_category;
  typedef std::pair<unsigned, unsigned> value_type;
  typedef value_type const& reference;
  typedef value_type const* pointer;
  typedef ptrdiff_t difference_type;

  PairSequence(): done(false) {}

  // C++11
  explicit operator bool() const { return !done; }

  // C++03
  // Safe Bool idiom
  operator BoolLike() const {
    return done ? 0 : &PairSequence::non_comparable;
  }

  reference operator*() const { return ij; }
  pointer operator->() const { return &ij; }

  PairSequence& operator++() {
    static unsigned const Max = std::numeric_limts<unsigned>::max();

    assert(!done);

    if (ij.second != Max) { ++ij.second; return *this; }
    if (ij.first != Max) { ij.second = 0; ++ij.first; return *this; }

    done = true;
    return *this;
  }

  PairSequence operator++(int) {
    PairSequence const tmp(*this);
    ++*this;
    return tmp;
  }

private:
  bool done;
  value_type ij;
};

อืมใช่ ... อาจเป็นไปได้ว่า C ++ เป็น verbose เล็กน้อย :)


2
ฉันยอมรับคำตอบของคุณ (ขอบคุณ!) เพราะเป็นเทคนิคที่ถูกต้องสำหรับคำถามที่ฉันให้ไว้ คุณมีคำแนะนำสำหรับเทคนิคในกรณีที่ลำดับที่ต้องสร้างมีความซับซ้อนมากขึ้นหรือฉันแค่ตีม้าตายที่นี่ด้วย C ++ และโครูทีนเป็นวิธีเดียวสำหรับความเป็นทั่วไป
Noah Watkins

3
@NoahWatkins: โครูทีนช่วยให้ใช้งานได้ง่ายเมื่อภาษารองรับ น่าเสียดายที่ C ++ ไม่มีดังนั้นการทำซ้ำจึงง่ายกว่า หากคุณต้องการโครูทีนจริงๆคุณต้องมีเธรดเต็มรูปแบบเพื่อเก็บ "สแต็ก" ของการเรียกฟังก์ชันไว้ที่ด้านข้าง มันแน่นอน overkill เพื่อเปิดดังกล่าวสามารถของเวิร์มเพียงว่าในตัวอย่างนี้ แต่ระยะของคุณอาจแตกต่างกันขึ้นอยู่กับความต้องการที่แท้จริงของคุณ
Matthieu M.

1
การใช้งานโครูทีนแบบไม่ใช้เธรดมีอยู่ใน Boost boost.org/doc/libs/1_57_0/libs/coroutine/doc/html/index.htmlพร้อมข้อเสนอสำหรับการกำหนดมาตรฐานที่นี่: open-std.org/jtc1/sc22/ wg21 / docs / papers / 2014 / n3985.pdf
boycy

2
@boycy: มีข้อเสนอหลายข้อสำหรับโครูทีนโดยเฉพาะอย่างยิ่งหนึ่งสแต็กน้อยกว่าและอีกสแต็กเต็ม มันยากที่จะแตกดังนั้นตอนนี้ฉันกำลังรอ ในขณะเดียวกันโครูทีนแบบไม่ใช้สแต็กสามารถนำไปใช้งานได้ใกล้เคียงกับ Input Iterators โดยตรง (เพียงแค่ไม่ใส่น้ำตาล)
Matthieu M.

3
แต่ในทำนองเดียวกันตัวทำซ้ำไม่เหมือนกับเครื่องกำเนิดไฟฟ้า
ОгњенШобајић

52

ใน C ++ มีตัวทำซ้ำ แต่การใช้ตัววนซ้ำนั้นไม่ตรงไปตรงมาเราต้องปรึกษาแนวคิดตัววนซ้ำและออกแบบคลาสตัววนซ้ำใหม่อย่างรอบคอบเพื่อนำไปใช้ โชคดีที่ Boost มีเทมเพลตiterator_facadeซึ่งจะช่วยในการใช้งานตัวสร้างซ้ำและเครื่องกำเนิดไฟฟ้าที่เข้ากันได้กับตัวทำซ้ำ

บางครั้งcoroutine stackless สามารถใช้ในการดำเนินการ iterator

ป.ล. ดูบทความนี้ซึ่งกล่าวถึงทั้งการswitchแฮ็กโดย Christopher M. Kohlhoff และBoost.Coroutineโดย Oliver Kowalke ผลงานของ Oliver Kowalke เป็นผลงานต่อจากBoost.Coroutineโดย Giovanni P. Deretta

ปล. ฉันคิดว่าคุณสามารถเขียนเครื่องกำเนิดไฟฟ้าด้วย lambdas ได้ด้วย :

std::function<int()> generator = []{
  int i = 0;
  return [=]() mutable {
    return i < 10 ? i++ : -1;
  };
}();
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

หรือกับ functor:

struct generator_t {
  int i = 0;
  int operator() () {
    return i < 10 ? i++ : -1;
  }
} generator;
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

ป.ล. นี่คือเครื่องกำเนิดไฟฟ้าที่ใช้กับMordor coroutines:

#include <iostream>
using std::cout; using std::endl;
#include <mordor/coroutine.h>
using Mordor::Coroutine; using Mordor::Fiber;

void testMordor() {
  Coroutine<int> coro ([](Coroutine<int>& self) {
    int i = 0; while (i < 9) self.yield (i++);
  });
  for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;
}

22

เนื่องจากBoost.Coroutine2รองรับได้เป็นอย่างดี (ฉันพบว่าเพราะฉันต้องการแก้ปัญหาเดียวกันทั้งหมดyield) ฉันจึงโพสต์รหัส C ++ ที่ตรงกับความตั้งใจเดิมของคุณ:

#include <stdint.h>
#include <iostream>
#include <memory>
#include <boost/coroutine2/all.hpp>

typedef boost::coroutines2::coroutine<std::pair<uint16_t, uint16_t>> coro_t;

void pair_sequence(coro_t::push_type& yield)
{
    uint16_t i = 0;
    uint16_t j = 0;
    for (;;) {
        for (;;) {
            yield(std::make_pair(i, j));
            if (++j == 0)
                break;
        }
        if (++i == 0)
            break;
    }
}

int main()
{
    coro_t::pull_type seq(boost::coroutines2::fixedsize_stack(),
                          pair_sequence);
    for (auto pair : seq) {
        print_pair(pair);
    }
    //while (seq) {
    //    print_pair(seq.get());
    //    seq();
    //}
}

ในตัวอย่างpair_sequenceนี้ไม่ใช้อาร์กิวเมนต์เพิ่มเติม หากจำเป็นต้องใช้std::bindหรือควรใช้แลมบ์ดาเพื่อสร้างอ็อบเจ็กต์ฟังก์ชันที่รับอาร์กิวเมนต์เดียว (จากpush_type) เท่านั้นเมื่อส่งผ่านไปยังตัวcoro_t::pull_typeสร้าง


โปรดทราบว่า Coroutine2 ต้องการ c ++ 11 ซึ่ง Visual Studio 2013 ไม่เพียงพอเนื่องจากได้รับการสนับสนุนเพียงบางส่วน
Weston

5

คำตอบทั้งหมดที่เกี่ยวข้องกับการเขียนตัววนซ้ำของคุณเองนั้นผิดทั้งหมด คำตอบดังกล่าวพลาดจุดกำเนิดของ Python อย่างสิ้นเชิง (หนึ่งในคุณสมบัติที่ยิ่งใหญ่ที่สุดและเป็นเอกลักษณ์ของภาษา) สิ่งที่สำคัญที่สุดเกี่ยวกับเครื่องกำเนิดไฟฟ้าคือการดำเนินการจะดึงจุดที่ค้างไว้ สิ่งนี้ไม่เกิดขึ้นกับผู้ทำซ้ำ แต่คุณต้องจัดเก็บข้อมูลสถานะด้วยตนเองเช่นนั้นเมื่อตัวดำเนินการ ++ หรือตัวดำเนินการ * ถูกเรียกใหม่ข้อมูลที่ถูกต้องจะอยู่ที่จุดเริ่มต้นของการเรียกใช้ฟังก์ชันถัดไป นี่คือเหตุผลว่าทำไมการเขียนตัววนซ้ำ C ++ ของคุณเองจึงเป็นความเจ็บปวดอย่างมาก ในขณะที่เครื่องกำเนิดไฟฟ้ามีความสวยงามและอ่าน + เขียนได้ง่าย

ฉันไม่คิดว่าจะมีอะนาล็อกที่ดีสำหรับเครื่องกำเนิด Python ใน C ++ ดั้งเดิมอย่างน้อยก็ยังไม่มี (มีข่าวลือว่าให้ผลใน C ++ 17 ) คุณสามารถหาสิ่งที่คล้ายกันได้โดยใช้บุคคลที่สาม (เช่นคำแนะนำ Boost ของ Yongwei) หรือหมุนของคุณเอง

ฉันจะบอกว่าสิ่งที่ใกล้เคียงที่สุดใน C ++ ดั้งเดิมคือเธรด เธรดสามารถรักษาชุดตัวแปรโลคัลที่ถูกระงับไว้และสามารถดำเนินการต่อจากจุดที่ค้างไว้ได้เหมือนกับเครื่องกำเนิดไฟฟ้า แต่คุณต้องม้วนโครงสร้างพื้นฐานเพิ่มเติมเล็กน้อยเพื่อรองรับการสื่อสารระหว่างอ็อบเจ็กต์ตัวสร้างและตัวเรียกใช้ เช่น

// Infrastructure

template <typename Element>
class Channel { ... };

// Application

using IntPair = std::pair<int, int>;

void yield_pairs(int end_i, int end_j, Channel<IntPair>* out) {
  for (int i = 0; i < end_i; ++i) {
    for (int j = 0; j < end_j; ++j) {
      out->send(IntPair{i, j});  // "yield"
    }
  }
  out->close();
}

void MyApp() {
  Channel<IntPair> pairs;
  std::thread generator(yield_pairs, 32, 32, &pairs);
  for (IntPair pair : pairs) {
    UsePair(pair);
  }
  generator.join();
}

วิธีนี้มีข้อเสียหลายประการแม้ว่า:

  1. เธรดมีราคาแพง คนส่วนใหญ่คิดว่านี่เป็นการใช้เธรดแบบ "ฟุ่มเฟือย" โดยเฉพาะอย่างยิ่งเมื่อเครื่องกำเนิดไฟฟ้าของคุณเรียบง่าย
  2. มีการดำเนินการทำความสะอาดสองสามอย่างที่คุณต้องจำไว้ สิ่งเหล่านี้อาจเป็นไปโดยอัตโนมัติ แต่คุณจำเป็นต้องมีโครงสร้างพื้นฐานเพิ่มเติมซึ่งอีกครั้งอาจถูกมองว่า "ฟุ่มเฟือยเกินไป" อย่างไรก็ตามการทำความสะอาดที่คุณต้องการคือ:
    1. นอก> ปิด ()
    2. generator.join ()
  3. สิ่งนี้ไม่อนุญาตให้คุณหยุดเครื่องกำเนิดไฟฟ้า คุณสามารถปรับเปลี่ยนบางอย่างเพื่อเพิ่มความสามารถนั้นได้ แต่จะเพิ่มความยุ่งเหยิงให้กับโค้ด มันจะไม่สะอาดเท่าคำสั่งผลตอบแทนของ Python
  4. นอกจาก 2 แล้วยังมีชิ้นส่วนสำเร็จรูปอื่น ๆ ที่จำเป็นในแต่ละครั้งที่คุณต้องการ "สร้างอินสแตนซ์" วัตถุเครื่องกำเนิดไฟฟ้า:
    1. พารามิเตอร์ Channel * out
    2. ตัวแปรเพิ่มเติมใน main: pair, generator

คุณกำลังสับสนไวยากรณ์กับฟังก์ชันการทำงาน คำตอบสองสามข้อข้างต้นทำให้ C ++ รับการดำเนินการจากจุดที่ค้างไว้ระหว่างการโทรครั้งล่าสุด มันไม่มีอะไรวิเศษ ตามความเป็นจริง Python ถูกนำไปใช้ใน C ดังนั้นสิ่งที่เป็นไปได้ใน Python ก็เป็นไปได้ใน C แม้ว่าจะไม่สะดวกเท่า
Edy

@edy ไม่ได้ระบุไว้ในย่อหน้าแรกแล้วหรือ? เขาไม่ได้อ้างว่าไม่สามารถสร้างฟังก์ชันที่เทียบเท่าใน C ++ แบบเดิมได้เพียงแต่ว่ามันเป็น "ความเจ็บปวดที่ยิ่งใหญ่"
Kaitain

@Kaitain คำถามที่นี่ไม่ใช่ว่าจะสร้างความเจ็บปวดใน C ++ แต่มีรูปแบบให้ทำหรือไม่ คำกล่าวอ้างของเขาว่าแนวทาง "พลาดประเด็น" ที่ "สิ่งที่ใกล้เคียงที่สุด" คือเธรด ... เป็นเพียงการเข้าใจผิด มันปวด? เราสามารถอ่านคำตอบอื่น ๆ และตัดสินใจด้วยตัวเอง
Edy

@edy แต่สิ่งนี้ไม่ได้กลายเป็นจุดที่ว่างเปล่าเนื่องจากในท้ายที่สุดภาษาที่สมบูรณ์ของทัวริงจะสามารถใช้งานฟังก์ชันเดียวกันได้หรือไม่? "สิ่งที่เป็นไปได้ใน X เป็นไปได้ใน Y" รับประกันได้ว่าเป็นจริงสำหรับทุกภาษาดังกล่าว แต่ดูเหมือนว่าฉันจะไม่ได้เป็นข้อสังเกตที่ชัดเจน
Kaitain

@Kaitain อย่างแม่นยำเนื่องจากภาษาทัวริงที่สมบูรณ์ทั้งหมดควรมีความสามารถเดียวกันดังนั้นคำถามที่ว่าการนำคุณลักษณะหนึ่งไปใช้ในภาษาอื่นนั้นถูกต้องตามกฎหมายอย่างไร ไม่มีสิ่งใดที่ Python ไม่สามารถทำได้ด้วยภาษาอื่น คำถามคือประสิทธิภาพและการบำรุงรักษา ในทั้งสองเรื่อง C ++ เป็นทางเลือกที่ดี (r)
Edy

4

คุณควรตรวจสอบเครื่องกำเนิดไฟฟ้าใน std :: trial ใน Visual Studio 2015 เช่น: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/

ฉันคิดว่ามันเป็นสิ่งที่คุณกำลังมองหา เครื่องกำเนิดไฟฟ้าโดยรวมควรมีอยู่ใน C ++ 17 เนื่องจากเป็นคุณลักษณะทดลองของ Microsoft VC เท่านั้น


2

หากคุณต้องการทำสิ่งนี้สำหรับเครื่องกำเนิดไฟฟ้าเฉพาะจำนวนค่อนข้างน้อยคุณสามารถใช้แต่ละตัวเป็นคลาสโดยที่ข้อมูลสมาชิกเทียบเท่ากับตัวแปรภายในของฟังก์ชัน Python generator จากนั้นคุณจะมีฟังก์ชันถัดไปที่ส่งคืนสิ่งต่อไปที่เครื่องกำเนิดไฟฟ้าจะให้ผลโดยอัปเดตสถานะภายในตามที่ทำ

โดยพื้นฐานแล้วสิ่งนี้คล้ายคลึงกับวิธีการใช้งานเครื่องกำเนิด Python ฉันเชื่อว่า ความแตกต่างที่สำคัญคือพวกเขาสามารถจำค่าชดเชยใน bytecode สำหรับฟังก์ชันเครื่องกำเนิดไฟฟ้าซึ่งเป็นส่วนหนึ่งของ "สถานะภายใน" ซึ่งหมายความว่าเครื่องกำเนิดไฟฟ้าสามารถเขียนเป็นลูปที่มีผลตอบแทนได้ คุณจะต้องคำนวณค่าถัดไปจากก่อนหน้านี้แทน ในกรณีของไฟล์pair_sequenceมันเป็นเรื่องเล็กน้อย อาจไม่ใช่สำหรับเครื่องกำเนิดไฟฟ้าที่ซับซ้อน

คุณต้องมีวิธีระบุการยุติ หากสิ่งที่คุณส่งกลับมาคือ "เหมือนตัวชี้" และ NULL ไม่ควรเป็นค่าที่ให้ผลตอบแทนที่ถูกต้องคุณสามารถใช้ตัวชี้ NULL เป็นตัวบ่งชี้การสิ้นสุดได้ มิฉะนั้นคุณจะต้องมีสัญญาณนอกย่านความถี่


1

สิ่งนี้คล้ายกันมาก:

struct pair_sequence
{
    typedef pair<unsigned int, unsigned int> result_type;
    static const unsigned int limit = numeric_limits<unsigned int>::max()

    pair_sequence() : i(0), j(0) {}

    result_type operator()()
    {
        result_type r(i, j);
        if(j < limit) j++;
        else if(i < limit)
        {
          j = 0;
          i++;
        }
        else throw out_of_range("end of iteration");
    }

    private:
        unsigned int i;
        unsigned int j;
}

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


1

ใช้range-v3 :

#include <iostream>
#include <tuple>
#include <range/v3/all.hpp>

using namespace std;
using namespace ranges;

auto generator = [x = view::iota(0) | view::take(3)] {
    return view::cartesian_product(x, x);
};

int main () {
    for (auto x : generator()) {
        cout << get<0>(x) << ", " << get<1>(x) << endl;
    }

    return 0;
}

0

สิ่งนี้ :

ตัวอย่างการใช้งาน:

using ull = unsigned long long;

auto main() -> int {
    for (ull val : range_t<ull>(100)) {
        std::cout << val << std::endl;
    }

    return 0;
}

จะพิมพ์ตัวเลขตั้งแต่ 0 ถึง 99


0

วันนี้ฉันยังมองหาการใช้งานคอลเลกชันที่ง่ายภายใต้ C ++ 11 จริงๆแล้วฉันรู้สึกผิดหวังเพราะทุกสิ่งที่ฉันพบอยู่ห่างไกลจากสิ่งต่างๆเช่นเครื่องกำเนิด python หรือตัวดำเนินการให้ผลผลิต C # ... หรือซับซ้อนเกินไป

มีจุดประสงค์เพื่อสร้างคอลเลกชันซึ่งจะปล่อยไอเท็มเมื่อจำเป็นเท่านั้น

ฉันอยากให้มันเป็นแบบนี้:

auto emitter = on_range<int>(a, b).yield(
    [](int i) {
         /* do something with i */
         return i * 2;
    });

ผมพบว่าการโพสต์นี้ IMHO ตอบที่ดีที่สุดคือประมาณ boost.coroutine2 โดยYongwei วู เนื่องจากเป็นสิ่งที่ใกล้ที่สุดกับสิ่งที่ผู้เขียนต้องการ

มันคุ้มค่าที่จะเรียนรู้หลักสูตรส่งเสริม .. และฉันอาจจะทำในวันหยุดสุดสัปดาห์ แต่จนถึงตอนนี้ฉันใช้การใช้งานเพียงเล็กน้อย หวังว่าจะช่วยให้คนอื่น

ด้านล่างนี้คือตัวอย่างการใช้งานจากนั้นนำไปใช้งาน

Example.cpp

#include <iostream>
#include "Generator.h"
int main() {
    typedef std::pair<int, int> res_t;

    auto emitter = Generator<res_t, int>::on_range(0, 3)
        .yield([](int i) {
            return std::make_pair(i, i * i);
        });

    for (auto kv : emitter) {
        std::cout << kv.first << "^2 = " << kv.second << std::endl;
    }

    return 0;
}

Generator.h

template<typename ResTy, typename IndexTy>
struct yield_function{
    typedef std::function<ResTy(IndexTy)> type;
};

template<typename ResTy, typename IndexTy>
class YieldConstIterator {
public:
    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;

    typedef YieldConstIterator<ResTy, IndexTy> mytype_t;
    typedef ResTy value_type;

    YieldConstIterator(index_t index, yield_function_t yieldFunction) :
            mIndex(index),
            mYieldFunction(yieldFunction) {}

    mytype_t &operator++() {
        ++mIndex;
        return *this;
    }

    const value_type operator*() const {
        return mYieldFunction(mIndex);
    }

    bool operator!=(const mytype_t &r) const {
        return mIndex != r.mIndex;
    }

protected:

    index_t mIndex;
    yield_function_t mYieldFunction;
};

template<typename ResTy, typename IndexTy>
class YieldIterator : public YieldConstIterator<ResTy, IndexTy> {
public:

    typedef YieldConstIterator<ResTy, IndexTy> parent_t;

    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;
    typedef ResTy value_type;

    YieldIterator(index_t index, yield_function_t yieldFunction) :
            parent_t(index, yieldFunction) {}

    value_type operator*() {
        return parent_t::mYieldFunction(parent_t::mIndex);
    }
};

template<typename IndexTy>
struct Range {
public:
    typedef IndexTy index_t;
    typedef Range<IndexTy> mytype_t;

    index_t begin;
    index_t end;
};

template<typename ResTy, typename IndexTy>
class GeneratorCollection {
public:

    typedef Range<IndexTy> range_t;

    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;
    typedef YieldIterator<ResTy, IndexTy> iterator;
    typedef YieldConstIterator<ResTy, IndexTy> const_iterator;

    GeneratorCollection(range_t range, const yield_function_t &yieldF) :
            mRange(range),
            mYieldFunction(yieldF) {}

    iterator begin() {
        return iterator(mRange.begin, mYieldFunction);
    }

    iterator end() {
        return iterator(mRange.end, mYieldFunction);
    }

    const_iterator begin() const {
        return const_iterator(mRange.begin, mYieldFunction);
    }

    const_iterator end() const {
        return const_iterator(mRange.end, mYieldFunction);
    }

private:
    range_t mRange;
    yield_function_t mYieldFunction;
};

template<typename ResTy, typename IndexTy>
class Generator {
public:
    typedef IndexTy index_t;
    typedef ResTy res_t;
    typedef typename yield_function<res_t, index_t>::type yield_function_t;

    typedef Generator<ResTy, IndexTy> mytype_t;
    typedef Range<IndexTy> parent_t;
    typedef GeneratorCollection<ResTy, IndexTy> finalized_emitter_t;
    typedef  Range<IndexTy> range_t;

protected:
    Generator(range_t range) : mRange(range) {}
public:
    static mytype_t on_range(index_t begin, index_t end) {
        return mytype_t({ begin, end });
    }

    finalized_emitter_t yield(yield_function_t f) {
        return finalized_emitter_t(mRange, f);
    }
protected:

    range_t mRange;
};      

0

คำตอบนี้ใช้ได้ใน C (และด้วยเหตุนี้ฉันจึงคิดว่าทำงานใน c ++ ด้วย)

#include <stdio.h>

const uint64_t MAX = 1ll<<32;

typedef struct {
    uint64_t i, j;
} Pair;

Pair* generate_pairs()
{
    static uint64_t i = 0;
    static uint64_t j = 0;
    
    Pair p = {i,j};
    if(j++ < MAX)
    {
        return &p;
    }
        else if(++i < MAX)
    {
        p.i++;
        p.j = 0;
        j = 0;
        return &p;
    }
    else
    {
        return NULL;
    }
}

int main()
{
    while(1)
    {
        Pair *p = generate_pairs();
        if(p != NULL)
        {
            //printf("%d,%d\n",p->i,p->j);
        }
        else
        {
            //printf("end");
            break;
        }
    }
    return 0;
}

นี่เป็นวิธีที่เรียบง่ายและไม่ใช่เชิงวัตถุในการเลียนแบบเครื่องกำเนิดไฟฟ้า สิ่งนี้ได้ผลตามที่ฉันคาดไว้


-1

เช่นเดียวกับฟังก์ชันจำลองแนวคิดของสแต็กเครื่องกำเนิดไฟฟ้าจะจำลองแนวคิดของคิว ส่วนที่เหลือเป็นความหมาย

ตามหมายเหตุด้านข้างคุณสามารถจำลองคิวด้วยสแต็กได้ตลอดเวลาโดยใช้การดำเนินการหลายชุดแทนข้อมูล สิ่งที่หมายความว่าในทางปฏิบัติคือคุณสามารถใช้พฤติกรรมคล้ายคิวได้โดยส่งคืนคู่ค่าที่สองซึ่งมีฟังก์ชันถัดไปที่จะเรียกหรือบ่งชี้ว่าเราไม่มีค่า แต่นี่เป็นเรื่องทั่วไปมากกว่าผลตอบแทนเทียบกับผลตอบแทน อนุญาตให้จำลองคิวของค่าใด ๆ มากกว่าค่าที่เป็นเนื้อเดียวกันที่คุณคาดหวังจากเครื่องกำเนิดไฟฟ้า แต่ไม่ต้องเก็บคิวภายในแบบเต็ม

โดยเฉพาะอย่างยิ่งเนื่องจาก C ++ ไม่มีนามธรรมตามธรรมชาติสำหรับคิวคุณจึงต้องใช้โครงสร้างที่ใช้คิวภายใน ดังนั้นคำตอบที่ยกตัวอย่างด้วยตัววนซ้ำคือการนำแนวคิดไปใช้อย่างเหมาะสม

สิ่งนี้หมายความว่าในทางปฏิบัติคือคุณสามารถใช้บางสิ่งบางอย่างด้วยฟังก์ชันการทำงานของคิวแบบ bare-bone ได้หากคุณต้องการบางสิ่งบางอย่างที่รวดเร็วจากนั้นใช้ค่าของคิวเช่นเดียวกับที่คุณใช้ค่าที่ได้จากเครื่องกำเนิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.