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;
}
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.