I'm a fan of "lambda-based foreach loops":
class SomeDataStructure
{
private:
std::vector<SomeData> data;
public:
template<typename TF> void forData(TF mFn)
{
for(int i{0}; i < data.size(); ++i)
mFn(i, data[i]);
}
};
SomeDataStructure sds;
int main()
{
sds.forData([](auto idx, auto& data)
{
// ...
});
}
I think it's a nice abstraction for more complex data structures, as it allows the user to intuitively loop over their contents with additional parameters as well. And compiler optimization should guarantee performance equal to the traditional for(...)
loop.
Unfortunately, using lambdas like this obviously prevents the use of the sometimes useful continue;
and break;
statements.
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
Is there any way to simulate the continue;
and break;
statements without any performance loss and not making the syntax less convenient?
Replace the member function forData
with member functions begin
and end
, that produce iterators, then replace
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
with
for( auto& data : sds )
{
if(data.isInvalid()) continue;
}
However, if you for some undisclosed reason would rather have the forData
member function, then you can implement pseudo-continue
and break
via some abuse of exceptions. For example, Python's for
loop is based on an exception. The forData
driver code would then just ignore the continue-exception, and honor the break-exception by stopping iteration.
template<typename TF> void forData(TF mFn)
{
for(int i{0}; i < data.size(); ++i)
{
try
{
mFn(i, data[i]);
}
catch( const Continue& )
{}
catch( const Break& )
{
return;
}
}
}
An alternative is to require the lambda to return a value that says "break" or "continue".
The most natural would be to use an enumeration type for this.
The main problem with the return value approach, as I see it, is that it hijacks the lambda result value, e.g. it can't then (very easily) be used to produce results that are accumulated by the loop, or anything like that.
I wouldn't do this, though. I would rather use the range-based for
loop, as recommended at the start of this answer. But if you do this, and you're concerned about efficiency, then remember that the first thing to do then is to measure.
You can implement a Python-like enumeration function that produces a logical view of a collection, so that the collection appears to be a collection of (value, index) pairs, very nice for use in a range based for
loop:
cout << "Vector:" << endl;
vector<int> v = {100, 101, 102};
for( const auto e : enumerated( v ) )
{
cout << " " << e.item << " at " << e.index << endl;
}
The following code (a bare minimum, cobbled together just for this answer) shows one way to do this:
#include <functional> // std::reference_wrapper
#include <iterator> // std::begin, std::end
#include <utility> // std::declval
#include <stddef.h> // ptrdiff_t
#include <type_traits> // std::remove_reference
namespace cppx {
using Size = ptrdiff_t;
using Index = Size;
template< class Type > using Reference = std::reference_wrapper<Type>;
using std::begin;
using std::declval;
using std::end;
using std::ref;
using std::remove_reference;
template< class Derived >
struct Rel_ops_from_compare
{
friend
auto operator!=( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) != 0; }
friend
auto operator<( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) < 0; }
friend
auto operator<=( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) <= 0; }
friend
auto operator==( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) == 0; }
friend
auto operator>=( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) >= 0; }
friend
auto operator>( const Derived& a, const Derived& b )
-> bool
{ return compare( a, b ) > 0; }
};
template< class Type >
struct Index_and_item
{
Index index;
Reference<Type> item;
};
template< class Iterator >
class Enumerator
: public Rel_ops_from_compare< Enumerator< Iterator > >
{
private:
Iterator it_;
Index index_;
public:
using Referent = typename remove_reference<
decltype( *declval<Iterator>() )
>::type;
friend
auto compare( const Enumerator& a, const Enumerator& b )
-> Index
{ return a.index_ - b.index_; }
auto operator->() const
-> Index_and_item< Referent >
{ return Index_and_item< Referent >{ index_, ref( *it_ )}; }
auto operator*() const
-> Index_and_item< Referent >
{ return Index_and_item< Referent >{ index_, ref( *it_ )}; }
Enumerator( const Iterator& it, const Index index )
: it_( it ), index_( index )
{}
auto operator++()
-> Enumerator&
{ ++it_; ++index_; return *this; }
auto operator++( int )
-> Enumerator
{
const Enumerator result = *this;
++*this;
return result;
}
auto operator--()
-> Enumerator&
{ --it_; --index_; return *this; }
auto operator--( int )
-> Enumerator
{
const Enumerator result = *this;
--*this;
return result;
}
};
template< class Collection >
struct Itertype_for_ { using T = typename Collection::iterator; };
template< class Collection >
struct Itertype_for_<const Collection> { using T = typename Collection::const_iterator; };
template< class Type, Size n >
struct Itertype_for_< Type[n] > { using T = Type*; };
template< class Type, Size n >
struct Itertype_for_< const Type[n] > { using T = const Type*; };
template< class Collection >
using Itertype_for = typename Itertype_for_< typename remove_reference< Collection >::type >::T;
template< class Collection >
class Enumerated
{
private:
Collection& c_;
public:
using Iter = Itertype_for< Collection >;
using Eter = Enumerator<Iter>;
auto begin() -> Eter { return Eter( std::begin( c_ ), 0 ); }
auto end() -> Eter { return Eter( std::end( c_ ), std::end( c_ ) - std::begin( c_ ) ); }
//auto cbegin() const -> decltype( c_.cbegin() ) { return c_.cbegin(); }
//auto cend() const -> decltype( c_.cend() ) { return c_.cend(); }
Enumerated( Collection& c )
: c_( c )
{}
};
template< class Collection >
auto enumerated( Collection& c )
-> Enumerated< Collection >
{ return Enumerated<Collection>( c ); }
} // namespace cppx
#include <iostream>
#include <vector>
using namespace std;
auto main() -> int
{
using cppx::enumerated;
cout << "Vector:" << endl;
vector<int> v = {100, 101, 102};
for( const auto e : enumerated( v ) )
{
cout << " " << e.item << " at " << e.index << endl;
}
cout << "Array:" << endl;
int a[] = {100, 101, 102};
for( const auto e : enumerated( a ) )
{
cout << " " << e.item << " at " << e.index << endl;
}
}
Declare your forData
to require a lambda which returns a boolean. When the lambda returns true
, break out of your for loop.
Then just use return true;
in your lambda for break;
and return false;
for continue;
And if you don't need break capability, as in your last example, just replacing continue
with return
suffices...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With