在基于 lambda 的 foreach 循环中模拟 `continue;`, `break;`
Simulating `continue;`, `break;` in lambda-based foreach loops
我是"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)
{
// ...
});
}
我认为对于更复杂的数据结构来说,这是一个很好的抽象,因为它允许用户直观地循环使用附加参数的内容。并且编译器优化应该保证性能等同于传统的for(...)
循环。
不幸的是,像这样使用 lambda 显然会阻止使用有时有用的 continue;
和 break;
语句。
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
有什么方法可以模拟 continue;
和 break;
语句而不损失任何性能并且不降低语法的便利性?
声明您的 forData
需要一个 returns 布尔值的 lambda。当 lambda returns true
时,跳出你的 for 循环。
然后只需在您的 lambda 中使用 return true;
表示 break;
并使用 return false;
表示 continue;
如果您不需要中断功能,如上一个示例所示,只需将 continue
替换为 return
就足够了...
用产生迭代器的成员函数begin
和end
替换成员函数forData
,然后替换
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
和
for( auto& data : sds )
{
if(data.isInvalid()) continue;
}
但是,如果出于某些未公开的原因您更愿意使用 forData
成员函数,那么您可以通过滥用异常来实现伪 continue
和 break
。例如,Python 的 for
循环是基于异常的。 forData
驱动程序代码将忽略继续异常,并通过停止迭代来处理中断异常。
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;
}
}
}
另一种方法是要求 lambda return 一个表示 "break" 或 "continue".
的值
最自然的做法是为此使用枚举类型。
在我看来,return 值方法的主要问题是它劫持了 lambda 结果值,例如然后它不能(非常容易地)用于产生循环累积的结果,或类似的东西。
不过我不会这样做。我宁愿使用基于范围的 for
循环,正如本答案开头所建议的那样。但如果你这样做了,而且你关心效率,那么请记住,首先要做的是衡量。
附录:添加一个类似于 Python 的枚举函数。
您可以实现一个类似于 Python 的 枚举函数 来生成集合的逻辑视图,以便该集合看起来是(值, index) 对,非常适合在基于范围的 for
循环中使用:
cout << "Vector:" << endl;
vector<int> v = {100, 101, 102};
for( const auto e : enumerated( v ) )
{
cout << " " << e.item << " at " << e.index << endl;
}
以下代码(最低限度,只是为了这个答案拼凑而成)展示了一种方法:
#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;
}
}
我是"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)
{
// ...
});
}
我认为对于更复杂的数据结构来说,这是一个很好的抽象,因为它允许用户直观地循环使用附加参数的内容。并且编译器优化应该保证性能等同于传统的for(...)
循环。
不幸的是,像这样使用 lambda 显然会阻止使用有时有用的 continue;
和 break;
语句。
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
有什么方法可以模拟 continue;
和 break;
语句而不损失任何性能并且不降低语法的便利性?
声明您的 forData
需要一个 returns 布尔值的 lambda。当 lambda returns true
时,跳出你的 for 循环。
然后只需在您的 lambda 中使用 return true;
表示 break;
并使用 return false;
表示 continue;
如果您不需要中断功能,如上一个示例所示,只需将 continue
替换为 return
就足够了...
用产生迭代器的成员函数begin
和end
替换成员函数forData
,然后替换
sds.forData([](auto idx, auto& data)
{
// Not valid!
if(data.isInvalid()) continue;
});
和
for( auto& data : sds )
{
if(data.isInvalid()) continue;
}
但是,如果出于某些未公开的原因您更愿意使用 forData
成员函数,那么您可以通过滥用异常来实现伪 continue
和 break
。例如,Python 的 for
循环是基于异常的。 forData
驱动程序代码将忽略继续异常,并通过停止迭代来处理中断异常。
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;
}
}
}
另一种方法是要求 lambda return 一个表示 "break" 或 "continue".
的值最自然的做法是为此使用枚举类型。
在我看来,return 值方法的主要问题是它劫持了 lambda 结果值,例如然后它不能(非常容易地)用于产生循环累积的结果,或类似的东西。
不过我不会这样做。我宁愿使用基于范围的 for
循环,正如本答案开头所建议的那样。但如果你这样做了,而且你关心效率,那么请记住,首先要做的是衡量。
附录:添加一个类似于 Python 的枚举函数。
您可以实现一个类似于 Python 的 枚举函数 来生成集合的逻辑视图,以便该集合看起来是(值, index) 对,非常适合在基于范围的 for
循环中使用:
cout << "Vector:" << endl;
vector<int> v = {100, 101, 102};
for( const auto e : enumerated( v ) )
{
cout << " " << e.item << " at " << e.index << endl;
}
以下代码(最低限度,只是为了这个答案拼凑而成)展示了一种方法:
#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;
}
}