🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Yield in C++

Started by
8 comments, last by c t o a n 20 years, 10 months ago
Ok, this is just an informational post, because I found this tip to be VERY cool and thought everyone who doesn''t know about it should. Here''s a way of resuming execution in a C++ file (like Python ''generators'' with the ''yield'' function)

#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()
{
        // be sure to call resume() at the START of a generator function

	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();
}

*/
Enjoy guys! Please feel free to post comments, improvements, etc. Chris Pergrossi My Realm | "Good Morning, Dave"
Chris PergrossiMy Realm | "Good Morning, Dave"
Advertisement
Why does this seem like C? (goto maybe?)
Can you explain to people that don''t know Python(like me) what yield is?

.lick
Can you explain to people that don't know Python(like me) what yield is?

A python 'function' containing the 'yield' keyword is called a generator. Calling it returns a function object. When you call that function object, 'yield' behaves like 'return' in an ordinary function, with the exception that subsequent calls to that function object will resume right after the 'yield' statement instead of starting over. All the local variables, etc. are preserved in between calls. When no yield statements are left, further calls raise a StopIteration exception that Python's 'for' statement intercepts.


For example
def foo(x):   print "Hello"   yield 1   print "World"   yield 2   print "!"   yield xa = foo(10)  # get a fresh instancea()          # prints "Hello" and returns 1a()          # prints "World" and returns 2a()          # print "!" and returns xa()          # raises a StopIteration exceptionfor x in foo(255):    print x # prints "Hello" "1" "World" "2" "!" "255"# but no exception filters through  


Looping over generators has the advantage that the sequence is generated as needed, instead of having to create and hold the entire sequence (which could be infinite) in memory first.

You can also use them to do 'micro-threads' : execution of the function is interrupted and can be resumed at will. You can have as many function objects as you want, you can interleave calls as you want ... which is an alternative to using actual threads.

For more information see - continuations, microthreads.

ctoan - Cool, too bad local variables are not preserved. And it looks like it will only work once - I see no way to reset _myStaticMkr.

[ Start Here ! | How To Ask Smart Questions | Recommended C++ Books | C++ FAQ Lite | Function Ptrs | CppTips Archive ]
[ Header Files | File Format Docs | LNK2001 | C++ STL Doc | STLPort | Free C++ IDE | Boost C++ Lib | MSVC6 Lib Fixes ]

[edited by - Fruny on September 3, 2003 11:24:07 AM]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Hmm...true, I didn''t think about that...! I needed it to do like storytelling stuff like:

cout << "Hello Sir Joe\n";
yield(1)
cout << "How are you?\n";
mySystem.ShakeHand()
yield(2)

Hmm... I''ll get on the case of the local variables and try to preserve them. Hmmm... the only way I can think of doing it right now is to allocate memory on the heap, and then copy all the variables to/from the memory.... Hmm, don''t think it''s worth it :S

Chris Pergrossi
My Realm | "Good Morning, Dave"
Chris PergrossiMy Realm | "Good Morning, Dave"
quote: Original post by c t o a n
Hmm... I''ll get on the case of the local variables and try to preserve them. Hmmm... the only way I can think of doing it right now is to allocate memory on the heap, and then copy all the variables to/from the memory.... Hmm, don''t think it''s worth it :S


Might as well just call Python code from C

[ Start Here ! | How To Ask Smart Questions | Recommended C++ Books | C++ FAQ Lite | Function Ptrs | CppTips Archive ]
[ Header Files | File Format Docs | LNK2001 | C++ STL Doc | STLPort | Free C++ IDE | Boost C++ Lib | MSVC6 Lib Fixes ]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
There is an article here on gamedev dealing with implementing coroutines in C - the code isn''t pretty. But it may warrant a look at. Its called "Using Co-routines in C" or similar.

--
Dustin
quote: Original post by c t o a n
Please feel free to post comments, improvements, etc.

Why can''t you use standard C++ features to achieve what you need? For example, if you wanted to generate a sequence of random numbers...

struct rand_functor : std::unary_function<int,int> {  int operator()(int N)   {    return N * (std::rand() / (RAND_MAX + 1.0));  }};

[Obviously, the idea can be extended to simulate the capturing of an environment with the addition of member data.]

rand_functor isn''t a true generator, but it has the advantage that it is standard and it is idiomatic, thus there is a reasonable chance of it compiling on any reasonable C++ compiler, and it will fit in with the C++ Standard Library algorithms.
Uh... In case y''all didn''t notice, I wasn''t building a random # generator. Just thought I''d mention that... And SabreMan, if you''re going to do it that way, why not just make the variables global? Or something similar? This is intended AS IS as just a cool little snippet, and I thought it would be helpful to other people. Thanks for the comments though, I tried saving the stack, but what I ended up with was just using static variables to preserve much needed variables without even using the stack (they are preserved even when you jmp the initialization as long as you passed it at least once). Anyway, I intended to use this code as a means of resuming execution of a function in a seperate DLL file, such that the namespace of the DLL file would be the same as a class namespace, preserving the required variables for resuming of execution. Anyway, enjoy the code snippet. Thanks

Chris Pergrossi
My Realm | "Good Morning, Dave"
Chris PergrossiMy Realm | "Good Morning, Dave"
quote: Original post by c t o a n
Uh... In case y''all didn''t notice, I wasn''t building a random # generator.

I wasn''t demonstrating a random number generator, I was demonstrating a technique. Perhaps a different example will help...
class generator{public:	generator()		: state_(0)	{		initialise();	}	int operator()() const	{		if(state_ >= v_.size())			throw std::string("Stopped!");		else			return v_[state_++];	}	void reset()	{		state_ = 0;	}private:	void initialise()	{		for(int i=0; i<8; ++i)			v_.push_back(i);	}private:	mutable int state_;	std::vector<int> v_;};

quote:
Just thought I''d mention that... And SabreMan, if you''re going to do it that way, why not just make the variables global?

Which variables?
quote:
Or something similar? This is intended AS IS as just a cool little snippet, and I thought it would be helpful to other people.

I was curious about the motivation. i.e. couldn''t you achieve your ends within the confines of standard C++?
quote:
Thanks for the comments though, I tried saving the stack, but what I ended up with was just using static variables to preserve much needed variables without even using the stack

objects (instances of classes) provide you with that ability.
I prefer the elegance of the non-state-based resume/yield commands, as they closely mimic that of the Python scripts I was previously using, allowing for easy conversion of scripts into C++ code. Again, thank you all for reviewing this for me.

Chris Pergrossi
My Realm | "Good Morning, Dave"
Chris PergrossiMy Realm | "Good Morning, Dave"

This topic is closed to new replies.

Advertisement