Convoluted code

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Convoluted code

Post by albinopapa » August 25th, 2019, 10:03 am

Code: Select all

#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

constexpr std::size_t size = std::size_t( 10 );

// Custom struct to hold data between and after loop iterations
struct loop_vars
{
	std::vector<int> container;
	int i = 0;
};

// Control flow enum, LoopEscape::Break and LoopEscape::Return 
// both exit out of the loop thus ending any further iterations
enum class LoopEscape { Continue, Return, Break, Null };

// These functors are only used to initalize the template intellisense
auto init_generate_func = [ vars = loop_vars{ std::vector<int>( size ) } ]()mutable->loop_vars&{ return vars; };
auto cond_generate_func = []( loop_vars const& _value ) { return _value.i < size; };
auto incr_generate_func = []( loop_vars& _value ) { ++_value.i; };
auto code_generate_func = [ escape = LoopEscape::Null ]( loop_vars& _value )->LoopEscape{  _value.container[ _value.i ] = _value.i; return escape;  };

// Wrap for loop to allow for multiple variable initializations,
// Early out through Condition functor or CodeBlock functor return values
// For early exit from the loop, use the return value from the CodeBlock functor
// Since there is no other processing inside the for loop, a return or break
// ...will break out of the loop and return a copy of the Initializer functor
// This way you can still use any modified results.
template<typename Initializer,
	typename Condition, 
	typename Incrementer, 
	typename CodeBlock>
constexpr Initializer loop( Initializer _initializer, Condition _condition, Incrementer _incrementer, CodeBlock _codeblock )noexcept
{
	for( auto& params = _initializer(); _condition( params ); _incrementer( params ) )
	{
		if( auto const escape = _codeblock( params );
			escape == LoopEscape::Break|| escape == LoopEscape::Return )
		{
			break;
		}
	}

	return _initializer;
}

int main( int argc, char* argv[] )
{
	// Inner loop() generates a vector<int> from 0 to size ( 10 )
	// Outer loop() prints the vector to the screen
	loop(
		[ initer = loop_vars{ 
			// loop returns the Initializer functor, cuz I didn't want to include a 
			// return type template variable
			loop(
			// Initialize the loop_vars structure, return reference
			[ vars = loop_vars{ std::vector<int>( size ) } ]()mutable->loop_vars&{ return vars; },
			// Check if i < predetermined size ( 10 )
			[]( loop_vars const& _value ) { return _value.i < size; },
			//  Increment the i value
			[]( loop_vars& _value ) { ++_value.i; },
			// Assign i to next element in the container
			[ escape = LoopEscape::Null ]( loop_vars& _value )->LoopEscape{  _value.container[ _value.i ] = _value.i; return escape;  }
		// Call the Initializer fuctor and pass the container to the new Initializer functor
		)().container } ]()mutable->loop_vars&{ return initer; },
		// Check if i < predetermined size ( 10 )
		[]( loop_vars const& _value ) {return _value.i < size; },
		// Increment i value
		[]( loop_vars& _value ) { ++_value.i; },
		// Print ith element in container
		[ escape = LoopEscape::Null ]( loop_vars& _value )->LoopEscape{ std::cout << _value.container[ _value.i ] << '\n'; return escape; }
	);


	{
		//  STL algorithms equivelent to above code:
		auto container = std::vector<int>( 10 );
		int i = 0;
		std::generate( container.begin(), container.end(), [ & ]() {return ++i; } );
		std::copy( container.begin(), container.end(), std::ostreambuf_iterator( std::cout ) );
	}
	
	{
		// Or, raw loops
		auto container = std::vector<int>( size );
		for( int i = 0; i < int( container.size() ); ++i )
		{
			container[ i ] = i;
		}
		for( auto const& value : container )
		{
			std::cout << value << '\n';
		}
	}
	return 1;
}
Just messing around with lambda captures. Just thought it would be useful in some way, then I saw how convoluted it would be to do something like this. Aside from taking significantly more time and effort, the Disassembly still uses call and doesn't inline any of it.

Basically,
you get multiple variable initializations
you get to have all your initialization variables scoped
you get all your variables accessible to all functors
you get the initalization functor returned back so you can actually get to the loop_vars data for use after the loop call.

I've always been frustrated about not being able to use ranged based for loops or other algorithms with multiple containers or needing the index value for more than just indexing into a container. Sometimes it's helpful to have a count as a way of procedurally generate uniformly spaced points ( think grid of soldiers, or vertices in a ground mesh. You'd want the X and Y or I and J values. What if you wanted an index value and needed the position vector after the loop? You'd have to declare one of them outside the loop and pass in as reference to functions which means no const. You could put the for loop in a lambda, then just return the position thus allowing you to make the outside position vector const.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Convoluted code

Post by chili » August 25th, 2019, 11:46 am

If you've used python you'll be familiar with zip which turns a bunch of iterables into a single composite iterable which solves the problem you mention. And we're going to get zips along with a ton of other amazing <algorithm>-enhancing goodies with Ranges in C++20.
Chili

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Convoluted code

Post by albinopapa » August 27th, 2019, 6:36 pm

Code: Select all

struct main_loop_vars
{
	Timer fps_timer;
	int frame_count = 0;
	bool is_quit = false;
};

for( auto vars = main_loop_vars{}; !vars.is_quit; vars.frame_count = ( vars.frame_count + 1 ) % 600 )
{
	for( auto msg = MSG{}; PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) && msg.message != WM_QUIT; vars.is_quit = ( msg.message == WM_QUIT ) )
	{
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}

	m_game.Go();
	update_framerate( vars.fps_timer.Elapsed(), vars.frame_count );
}
This has the benefit of getting the same thing while being a bit less abstract.

No, I haven't used python, I've pretty much stayed away from scripting languages with the exception of some Lua and that was just because it was a mod for Minecraft ( mod was called ComputerCraft ).

I haven't really looked into the Ranges v3 stuffs, but it does sound nice.

The range based for loop getting an addition initializer will be a big help also.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply