C++ LINQ-like iterator operations

后端 未结 8 973
执念已碎
执念已碎 2021-02-13 13:40

Having been tainted by Linq, I\'m reluctant to give it up. However, for some things I just need to use C++.

The real strength of linq as a linq-consumer (i.e. to me) li

8条回答
  •  悲&欢浪女
    2021-02-13 14:03

    I don't have concrete experience with LINQ, but the Boost.Iterator library seems to approach what you're referring to.

    The idea is to have functions (IIUC, in LINQ, they take the form of extension methods, but that's not fundamental), taking an iterator and a function, combining them to create a new iterator.

    LINQ "Where" maps to make_filter_iterator:

    std::vector vec = ...;
    // An iterator skipping values less than "2":
    boost::make_filter_iterator(_1 > 2, vec.begin())
    

    LINQ "Select" maps to make_transform_iterator:

    using namespace boost::lambda;
    //An iterator over strings of length corresponding to the value
    //of each element in "vec"
    //For example, 2 yields "**", 3 "***" and so on.
    boost::make_transform_iterator(construct('*', _1), vec.begin())
    

    And they can be composed:

    //An iterator over strings of length corresponding to the value of each element
    // in "vec", excluding those less than 2
    std::vector vec = ...;
    boost::make_transform_iterator(construct('*', _1), 
        boost::make_filter_iterator(_1 > 2, vec.begin())
    )
    

    However, there are a few annoying things with this:

    • The type returned by make_xxx_iterator(some_functor, some_other_iterator) is xxx_iterator
    • The type of a functor created using boost::bind, lambda, or phoenix quickly becomes unmanageably large and cumbersome to write.

    That's why I avoided in the code above to assign the result of make_xxx_iterator to a variable. C++0x "auto" feature will be pretty welcome there.

    But still, a C++ iterator can't live "alone": they have to come in pairs to be useful. So, even with "auto", it's still a mouthful:

    auto begin = make_transform_iterator(construct('*', _1), 
        make_filter_iterator(_1 > 2, vec.begin())
    );
    auto end = make_transform_iterator(construct('*', _1), 
        make_filter_iterator(_1 > 2, vec.end())
    );
    

    Avoiding the use of lambda makes things verbose, but manageable:

    struct MakeStringOf{
        MakeStringOf(char C) : m_C(C){}
        char m_C;
    
        std::string operator()(int i){return std::string(m_C, i);}
    };
    
    struct IsGreaterThan{
        IsGreaterThan(int I) : m_I(I){}
        int m_I;
    
        bool operator()(int i){return i > m_I;}
    };
    
    typedef boost::filter_iterator<
       IsGreaterThan, 
       std::vector::iterator
    > filtered;
    
    typedef boost::transform_iterator<
       MakeStringOf, 
       filtered
    > filtered_and_transformed;
    
    filtered_and_transformed begin(
        MakeStringOf('*'), 
        filtered(IsGreaterThan(2), vec.begin())
    );
    
    filtered_and_transformed end(
        MakeStringOf('*'), 
        filtered(IsGreaterThan(2), vec.end())
    );
    

    The (not-yet)Boost.RangeEx library is promising in this respect, in that it allows to combine the two iterators in a single range. Something like:

    auto filtered_and_transformed = make_transform_range(
        make_filter_range(vec, _1 > 2),
        construct('*', _1)
    );
    

提交回复
热议问题