问题 在基于lambda的foreach循环中模拟`continue;`,`break;`


我是“基于lambda的foreach循环”的粉丝:

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(...) 循环。

不幸的是,使用像这样的lambdas显然会阻止使用有时有用的 continue; 和 break; 声明。

sds.forData([](auto idx, auto& data)
{
    // Not valid!
    if(data.isInvalid()) continue;
});

有没有办法模拟 continue; 和 break; 声明没有任何性能损失,并没有使语法不方便?


12856
2018-03-06 11:37


起源

关于“我认为它对于更复杂的数据结构是一个很好的抽象,因为它允许用户直观地使用其他参数循环其内容。”, 迭代内外 是一系列很好的帖子,解释了两种迭代方式之间的区别,以及它们之间的权衡。由于主题,它有点长。 - Luc Danton
取决于你想要做什么。我将迭代器暴露给用户。使他们 const_iterator如果它是不可变的。 - Alex


答案:


替换成员函数 forData 与成员函数 begin 和 end,生成迭代器,然后替换

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 然后,驱动程序代码将忽略continue-exception,并通过停止迭代来遵守break-exception。

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返回一个“break”或“continue”的值。

最自然的是为此使用枚举类型。

正如我所看到的,返回值方法的主要问题是它劫持了lambda结果值,例如:它不能(很容易)用于产生循环累积的结果,或类似的东西。


不过,我不会这样做。我宁愿使用基于范围的 for 循环,如本答案开头所建议的那样。但是如果你这样做,并且你关注效率,那么请记住,首先要做的就是测量。


附录:添加类似Python的枚举函数。

你可以实现类似Python的 枚举功能 生成集合的逻辑视图,以便集合看起来是(值,索引)对的集合,非常适合在基于范围的范围内使用 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;
    }
}

9
2018-03-06 11:49



当然,例外方法可能会对性能产生负面影响。在迭代器方法上+1 - Brandon


宣布你的 forData 要求lambda返回一个布尔值。当lambda返回时 true,打破你的循环。

然后就用吧 return true; 在你的lambda中 break; 和 return false; 对于 continue;

如果您不需要中断功能,就像在上一个示例中那样,只需要替换 continue 同 return 足够...


7
2018-03-06 11:42



我用一个带有自记录值名称的枚举类型替换布尔值。另请注意,这会阻止将lambda返回用于其他目的,例如返回由循环累积的值。 - Cheers and hth. - Alf