【问题标题】:Equivalent in C++ of Yield in C#?等效于 C# 中的 Yield 的 C++?
【发布时间】:2011-11-05 01:03:07
【问题描述】:
public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

有没有办法通过模板技巧(或其他)在 c++ 中获得相同的语法?

【问题讨论】:

标签: c++


【解决方案1】:

Coroutines are in the C++20 draft 并使用co_yield 而不是yield

另见:What are coroutines in C++20?

第一个链接中有一些示例用法:(第二个可能是您要查找的)

  • 使用co_await 运算符暂停执行直到恢复

    task<> tcp_echo_server() {
      char data[1024];
      for (;;) {
        size_t n = co_await socket.async_read_some(buffer(data));
        co_await async_write(socket, buffer(data, n));
      }
    }
    
  • 使用关键字co_yield 暂停执行返回值

    generator<int> iota(int n = 0) {
      while(true)
        co_yield n++;
    }
    
  • 使用关键字co_return完成执行返回值

    lazy<int> f() {
      co_return 7;
    }
    

【讨论】:

    【解决方案2】:

    这是 ASM “自己动手”的版本:http://www.flipcode.com/archives/Yield_in_C.shtml

    #include <stdio.h
    #include <conio.h
    #include <iostream.h
    
    
    //
    // marks a location in the program for resume
    // does not return control, exits function from inside macro
    //
    // yield( x, ret )
    //      x : the 'name' of the yield, cannot be ambiguous in the
    //          function namespace
    //    ret : the return value for when yield() exits the function;
    
    //          must match function return type (leave blank for no return type)
    
    #define yield(x,ret)                            \
        {                                           \
            /* store the resume location */         \
            __asm {                                 \
                mov _myStaticMkr,offset label_##x   \
            }                                       \
                                                    \
            /* return the supplied value */         \
            return ret;                             \
        }                                           \
        /* our offset in the function */            \
        label_##x:
    
    
    
    //
    // resumes function from the stored offset, or
    // continues without notice if there's not one
    // stored
    //
    // resume()
    //   <void
    
    #define resume()                        \
        /* our stored offset */             \
        static _myStaticMkr=0;              \
                                            \
        /* test for no offset */            \
        if( _myStaticMkr )                  \
        {                                   \
            /* resume from offset */        \
            __asm                           \
            {                               \
                jmp _myStaticMkr            \
            }                               \
        }
    
    
    // example demonstrating a function with an int return type
    // using the yield() and resume() macros
    //
    // myFunc()
    //   <void
    
    int myFunc()
    {
        resume();
    
        cout << "1\n";
    
        yield(1,1);
    
        cout << "2\n";
    
        yield(2,1);
    
        cout << "3\n";
    
        yield(3,1);
    
        cout << "4\n";
    
        return 0;
    }
    
    
    
    // main function
    //
    // main()
    //   <void
    
    void main( void )
    {
        cout << "Yield in C++\n";
        cout << "Chris Pergrossi\n\n";
    
        myFunc();
    
        do
    
        {
            cout << "main()\n";
            cout.flush();
        } while( myFunc() );
    
        cout.flush();
    
        getch();
    }
    
    
    /*
    
    // example demonstrating a function with no return type
    // using the yield() and resume() macros
    //
    // myFunc()
    //   <void
    
    void myFunc()
    {
        resume();
    
        cout << "1\n";
    
        yield(1);
    
        cout << "2\n";
    
        yield(2);
    
        cout << "3\n";
    
        yield(3);
    
        cout << "4\n";
    
        return;
    }
    
    
    
    // main function
    //
    // main()
    //   <void
    
    void main( void )
    {
        cout << "Yield in C++\n";
        cout << "Chris Pergrossi\n\n";
    
        myFunc();
    
        for( int k = 0; k < 4; k ++ )
        {
            cout << "main()\n";
            cout.flush();
    
            myFunc();
        }
    
        cout.flush();
    
        getch();
    }
    
    */  
    

    【讨论】:

    • 很好,但是这是跨平台的吗?
    【解决方案3】:

    您始终可以手动编写代码。说实话,yield 对我来说真的像是糖衣(以及协同程序)。

    什么是协程,真的吗?一些状态捆绑在一起:

    • 一个函数来创建它(不是构造函数吗?)
    • 一个移动到下一个状态的函数(传统上不是 operator++ 吗?)

    在 C++ 中称为InputIterator,可以任意胖。

    所以,语法确实不会那么漂亮,但这应该可以,只需使用标准库:

    static std::array<int, 6> const Array = {{1, 2, 4, 8, 16, 16777216}};
    
    class Integers: public std::iterator<std::input_iterator_tag,
                                          int, ptrdiff_t, int const*, int>
    {
    public:
      Integers(): _index(0) {}
    
      operator bool() const { return _index < Array.size(); }
    
      Integers& operator++() { assert(*this); ++_index; return *this; }
      Integers operator++(int) { Integers tmp = *this; ++*this; return tmp; }
    
      int operator*() const { assert(*this); return Array[_index]; }
      int const* operator->() const { assert(*this); return &Array[_index]; }
    
    private:
      size_t _index;
    }; // class Integers
    

    显然,由于 决定存储什么状态,因此您决定是否全部预先计算,或者是否部分(或全部)延迟计算,并可能缓存,并且可能多-线程,并且......你明白了:)

    【讨论】:

    • 我不明白为什么“糖衣”是一件坏事。如果你深入它,一个类也不过是糖衣,循环等等也是如此。动手方法的一个明显问题是,您基本上必须编写一个任意复杂的状态机(我可以想到几个现实世界的应用程序,这不会那么容易)
    • @Voo:糖衣引入了复杂性,简单地说->还有更多需要学习。 OP 询问了 C++ 中的 yield,我的看法是,与其将 C# 语法“移植”到 C++ 中,不如反思它在做什么并找到 C++ 中的惯用语。协程只不过是InputIterator
    • 我不同意“引入复杂性”-根据我的经验,生成器语义简单易懂(如果有一种语言不遵循“可能的最简单语法”方法,那就是 c++ !)。此外,它不是 C# 语法,而是 CS 中众所周知的概念,以多种语言实现(当然与 InputIterator 不同!)。在许多情况下,为某些功能手动实现状态机是非常重要的。例如,尝试使用 InputerIterator 实现 this - 当然更难理解
    • Matthieu,for 循环是什么,但while 循环上的糖衣是什么?什么是switch,而是if 的级联?语法糖不一定是坏事,因为没有它我们仍然会将十六进制操作码直接打入内存。这只是你在哪里画线的问题。您似乎将它画在只有一个循环语句加一个分支语句的语言和一种包括yield 的语言之间。其他包括yield。我,我用过它,看到了它的意义,但不管有没有它都可以。
    • @Matthieu M. 是的,看看next_permutation 的实现几乎证明了我的观点,即它要复杂几倍(毕竟这只是一个例子而不是唯一的用例)。而且我从来没有听说过将参数重新注入到挂起的函数中——而且维基百科上列出的任何一种语言似乎都没有这种功能。 “糖衣”的全部意义不是隐藏编译器可以完成但对程序员来说相当复杂的东西吗?在我看来,c++ 与 c 相反,抽象了很多。
    【解决方案4】:

    在 C++14 中,您可以这样模仿 yield

    auto&& function = []() { 
        int i = 0; 
        return [=]() mutable { 
            int arr[] = {1,2,4,8,16,16777216}; 
            if ( i < 6 ) 
                return arr[i++]; 
            return 0; 
        }; 
    }();
    

    http://ideone.com/SQZ1qZ 上提供了一个实时示例

    【讨论】:

    • 我没有,但现场示例可以轻松适应您的答案,而无需显示来自 ideone.com 的广告。
    • yield的目的不就是防止一系列对象(本例为int[])被立即放入内存吗?
    【解决方案5】:

    针对 C++17 提出了类似的建议,并且已经在 Visual C++ 2015 中进行了实验性实现。这里有一个很好的概述 talk,来自该提案的主要作者之一 Gor Nishanov。

    【讨论】:

      【解决方案6】:

      如果您写static unsigned int checkpoint = 0;,则将所有变量设为staticswitch (checkpoint),将每个case: goto 设置为某个标签,在每个return 上方设置检查点为唯一值,在下方定义标签,并在函数结束将检查点设置为零,所有静态变量设置为其默认值,最后return 函数的结束值。如果你做了所有这些,那么函数就变成了enumerableiterative。您在每条return 行的上方和下方添加的两行使return 命令的行为类似于yield returngoto 允许您从中断的地方继续和恢复,static 整数变量,如检查点,帮助您记住停止的位置、从哪里继续/恢复以及去哪里。您可以使用 switch case 语句测试它的值。将其他所有变量设为static,就是将它们的值保存到下一次调用中,所以在下一次调用中,它们的值不会被重置!

      这里举例:

      #define PowerEnd INT_MIN
      int Power(int number, int exponent)
      {
          static unsigned int checkpoint = 0;
          static int result = 1, i = 0;
          switch (checkpoint)
          {
              case 1: goto _1;
          }
          for (i = 0; i < exponent; i++)
          {
              result *= number;
              checkpoint = 1;
              return result;
              _1:;
          }
          checkpoint = 0;
          result = 1;
          i = 0;
          return PowerEnd;
      }
      void main()
      {
          while (true)
          {
              int result = Power(2, 8);
              if (result == PowerEnd)
                  break;
              cout << result << endl;
          }
          //to print only the first 4 results (if there are at least 4 results) then
          for (int i = 0; i < 4; i++)
          {
              int result = Power(2, 8);
              if (result == PowerEnd)
                  break;
              cout << result << endl;
          }
      }
      

      上述程序产生以下输出:

      2 4 8 16 32 64 128 256 2 4 8 16

      【讨论】:

        【解决方案7】:

        如果您需要的只是类似 foreach 的东西,那么 C++ 中可以使用以下语法:

        #define GENERATOR(name) \
        struct name \
        { \
            template<typename F> \
            void operator()(F yield) \
        /**/
        #define _ };
        
        template<typename Gen>
        struct Adaptor
        {
            Gen f;
            template<typename C>
            void operator*(C cont)
            {
                f(cont);
            }
        };
        
        template<typename Gen>
        Adaptor<Gen> make_adaptor(Gen gen)
        {
            return {gen};
        }
        
        #define FOREACH(arg, gen) make_adaptor(gen) * [&](arg)
        

        #include <iostream>
        using namespace std;
        
        GENERATOR(integers)
        {
            yield(1);
            yield(2);
            yield(4);
            yield(8);
            yield(16777216);
        }_
        
        int main()
        {
            FOREACH(int i, integers())
            {
                cout << i << endl;
            };
        }
        

        Live Demo

        如果你需要一点协程“力量”,那么你可以试试stackless coroutines

        或者,如果您需要充分的能力 - 然后使用堆栈式协程。有Boost.Coroutine 库,为不同平台实现堆栈协程。

        【讨论】:

          【解决方案8】:
          #include <setjmp.h>
          
          class superclass
          {
          public:
              jmp_buf jbuf;
          public:
              virtual int enumerate(void) { return -1; }
          };
          
          class subclass: public superclass
          {
          public:
              int enumerate()
              {
                  static int i;
                  static bool b = false;
          
                  if(b) 
                      longjmp(jbuf, 1);
          
                  for(b = true, i = 0; i < 5; (i)++)
                  {
                      printf("\ndoing stuff: i = %d\n", i);
          
                      if(setjmp(jbuf) != 1) 
                          return i;    
                  }
                  return -1;
              }
          };
          

          要使用代码...

          int iret; 
          subclass *sc;
          
          sc = new subclass();
          while((iret = sc->enumerate()) != -1)
          {
              printf("\nsc->enumerate() returned: %d\n", iret);
          }
          

          刚刚开始工作;现在看起来很简单,虽然我有一些错误的开始:)

          【讨论】:

            【解决方案9】:

            尝试在c++中实现yield coroutine

            【讨论】:

              【解决方案10】:

              当然,您总是可以编写自己的迭代器并从它们返回您想要的任何东西,但您为什么要这样做呢?在给定的示例中,为什么不简单地将您的值放入像 vector 这样的容器中并对其进行迭代?

              【讨论】:

              • 考虑一种情况是要计算值。您可能希望对值序列进行惰性求值,这就是显示的代码所做的。您可以编写一个以这种方式返回无限列表的函数。
              • @TeaWolf 在实际应用程序中,整数可能不止几个,而且它们可能根本不是整数,但代价更高。也许调用者只是想找到满足特定条件的元素 - 将元素存储在容器中不仅会浪费空间,还会浪费时间,因为在所需元素之后的元素将被不必要地计算。此外,std::vector 需要通过重新分配/复制例程来增加其大小(除非事先知道,通常情况下您不需要知道迭代器块)。
              • @TeaWolf 一个很好的例子,一个生成器产生一个非常好的和简单的功能是例如this - 它是python,但无论如何你应该了解它的要点。
              • 在 Python 中,我习惯于产出很多,而在 C++ 中却错过了它。最好的例子是我想隐藏 MYSQL 实现。例如,顶层想知道餐厅的桌子,但看不到 SQL 实现:: for (x : sql.getAllTables()) ... 和函数 sql.getAllTables() { sql.query("select id, name from some_table order by name", for (x in result) yield one_table } ..
              【解决方案11】:

              看看 boost::Coroutine。它做你想做的事。 http://www.crystalclearsoftware.com/soc/coroutine/index.html#coroutine.intro

              教程中的示例

              http://www.crystalclearsoftware.com/soc/coroutine/coroutine/tutorial.html

              int range_generator(generator_type::self& self, int min, int max) 
               {
                 while(min < max)
                   self.yield(min++);
                 self.exit();
               }
              

              【讨论】:

              • +1,这真的很有趣,我很少/不知道self.exit() 是如何合法替代退货声明的。 (我怀疑这是对异常的可怕滥用或longjmp,但我不确定我想知道!)
              • Boost.Coroutine 在汇编中实现,并通过支持“Fibers”的平台上的操作系统调用实现。它不是用纯 C++ 实现的。
              • 我在官网的boost库列表中没有看到协程。有什么指点吗?
              • 如果这是针对 Win32 的,请,请,请理解,在 any 代码中使用 Fibers 是一个非常高级的话题,并且查看有效隐藏 Fiber 代码的库是真的很可怕。有一大堆 Win32 API 在存在 Fibers 的情况下无法工作,或者更可怕的是无法按预期工作。例如,Win32 中的锁是基于线程 id 的——这意味着对于 Fibers,如果您获取锁然后屈服,另一个在您的线程上运行的 Fiber 也可以成功获取锁!所以除非你真的很小心,否则它会咬你一口。
              猜你喜欢
              • 2013-10-30
              • 2012-01-02
              • 1970-01-01
              • 2011-04-08
              • 1970-01-01
              • 2012-02-18
              • 2011-01-29
              • 1970-01-01
              • 2010-12-31
              相关资源
              最近更新 更多