5
\$\begingroup\$

I wrote a simple iterator wrapper that can be used in e.g. std::find and std::copy and iterator pair constructors to search on and extract members from structs contained in an iterable range. It can probably be made to work with member functions accepting (copyable) arguments, but simple argumentless getters work fine as it is now.

I made std::find useful by providing an underlying_iterator function that returns the normal iterator to the underlying element the wrapper currently points to.

This is useful to to e.g. get a list of names from a list of struct person { std::string name; std::string id; };, or to find a person with a specific name without writing a lambda or other functor to do this.

You can check out the implementation below (most of it is boilerplate for the arithmetic operators and comparison functions, some of which might be better off as free functions).

One of the issues I'm struggling with is making this useful for std::sort and the if the autos in operator* actually do the right thing or not. I believe the former is rendered impossible by the singular interface of iterators.

#include <algorithm>
#include <iostream>
#include <limits>
#include <iterator>
#include <vector>

template<typename ValueType, typename PointerToMemberType, bool IsMemberFunctionPointer = std::is_member_function_pointer<PointerToMemberType>::value>
struct result_of_pointer_to_member_dereference
{
    using type = decltype(std::declval<ValueType>().*std::declval<PointerToMemberType>());
};
template<typename ValueType, typename PointerToMemberType>
struct result_of_pointer_to_member_dereference<ValueType, PointerToMemberType, true>
{
    using type = decltype((std::declval<ValueType>().*std::declval<PointerToMemberType>())());
};
template<typename... ArgTypes>
using result_of_pointer_to_member_dereference_t = typename result_of_pointer_to_member_dereference<ArgTypes...>::type;

template<typename Iterator, typename PointerToMemberType, bool = std::is_member_function_pointer<PointerToMemberType>::value>
struct dereference_pointer_to_member_helper
{
    static decltype(auto) dereference(Iterator iterator, PointerToMemberType ptm)
    {
        return *iterator.*ptm;
    }
};
template<typename Iterator, typename PointerToMemberType>
struct dereference_pointer_to_member_helper<Iterator, PointerToMemberType, true>
{
    static decltype(auto) dereference(Iterator iterator, PointerToMemberType ptm)
    {
        return (*iterator.*ptm)();
    }
};

template<typename Iterator, typename PointerToMemberType>
class get_member_iterator
{
    using underlying_iterator_type = Iterator;
    using underlying_value_type = typename std::iterator_traits<Iterator>::value_type;

public:
    using iterator_category = typename underlying_iterator_type::iterator_category;
    using iterator_value_type = result_of_pointer_to_member_dereference_t<underlying_value_type, PointerToMemberType>;
    using value_type = std::remove_reference_t<iterator_value_type>;
    using difference_type = typename underlying_iterator_type::difference_type;
    using pointer = std::add_pointer_t<value_type>;
    using reference = std::add_lvalue_reference_t<value_type>;

    get_member_iterator(underlying_iterator_type it, PointerToMemberType ptm)
      : it(it)
      , pointer_to_member(ptm)
    {}

    decltype(auto) operator*()
    {
        return dereference_pointer_to_member_helper<underlying_iterator_type, PointerToMemberType>::dereference(it, pointer_to_member);
    }

    get_member_iterator& operator++()
    {
        ++it;
        return *this;
    }

    get_member_iterator operator++(int)
    {
        return {++it, pointer_to_member};
    }

    get_member_iterator operator+(int increment)
    {
        return {it + increment, pointer_to_member};
    }

    get_member_iterator& operator+=(int increment)
    {
        it += increment;
        return *this;
    }

    get_member_iterator& operator--()
    {
        --it;
        return *this;
    }

    get_member_iterator operator--(int)
    {
        return {--it, pointer_to_member};
    }

    get_member_iterator operator-(int decrement)
    {
        return {it + decrement, pointer_to_member};
    }

    get_member_iterator& operator-=(int decrement)
    {
        it -= decrement;
        return *this;
    }

    difference_type operator-(const get_member_iterator& other)
    {
        return it - other.it;
    }

    bool operator!=(const get_member_iterator& other) const
    {
        return !(*this == other);
    }
    bool operator==(const get_member_iterator& other) const
    {
        return pointer_to_member == other.pointer_to_member && it == other.it;
    }
    bool operator<(const get_member_iterator& other) const
    {
        return it < other.it;
    }
    bool operator>(const get_member_iterator& other) const
    {
        return it > other.it;
    }


    bool operator>=(const get_member_iterator& other) const
    {
        return it >= other.it;
    }


    void swap(get_member_iterator& other)
    {
        using namespace std;
        swap(it, other.it);
        swap(pointer_to_member, other.pointer_to_member);
    }

    const underlying_iterator_type& underlying_iterator() const
    {
        return it;
    }

    underlying_iterator_type& underlying_iterator()
    {
        return it;
    }

private:
    underlying_iterator_type it;
    PointerToMemberType pointer_to_member;
};

//-- Helpers that are obsoleted by C++17 constructor template argument deduction --
template<typename Iterator, typename PointerToMember>
auto create_get_member_iterator(Iterator it, PointerToMember pointer_to_member )
{
    return get_member_iterator<Iterator, PointerToMember>(it, pointer_to_member);
}

template<typename Container, typename PointerToMember>
auto get_member_begin(const Container& container, PointerToMember pointer_to_member)
{
    return create_get_member_iterator(begin(container), pointer_to_member);
}

template<typename Container, typename PointerToMember>
auto get_member_end(const Container& container, PointerToMember pointer_to_member )
{
    return create_get_member_iterator(end(container), pointer_to_member);
}

//-- User code --
struct A
{
    int i;
    short s;

    short getS() const { return s; }

    void swap(A other)
    {
        std::swap(i, other.i);
        std::swap(s, other.s);
    }
};

std::vector<int> getIntsFromA(const std::vector<A>& as)
{
    return {get_member_begin(as, &A::i), get_member_end(as, &A::i)};
}

std::vector<short> getShortsFromA(const std::vector<A>& as)
{
    return {get_member_begin(as, &A::getS), get_member_end(as, &A::getS)};
}

int main()
{
    std::vector<A> as
    {
        { 0, 2 },
        { 42, 1}
    };

    for( auto i : getIntsFromA(as))
        std::cout << i << '\n';

    for( auto s : getShortsFromA(as))
        std::cout << s << '\n'; 

    auto result = std::find(get_member_begin(as, &A::i), get_member_end(as, &A::i), 42).underlying_iterator();
    if(result != as.end())
        std::cout << "Found element 42: (" << result->i << ", " << result->s << ").\n";

    std::vector<int> is{ get_member_begin(as, &A::getS), get_member_end(as, &A::getS)};
    for(int i : is)
        std::cout << i << '\n';

    // Does not (and will never?) work.
    //std::sort(get_member_begin(as, &A::s), get_member_end(as, &A::s));
    //std::cout << "sorted:\n";
    //for(short s : getShortsFromA(as))
    //    std::cout << s << '\n';
}

Live on Coliru.

\$\endgroup\$
  • \$\begingroup\$ I changed the auto return types to decltype(auto) to get perfect forwarding on return types. \$\endgroup\$ – rubenvb Aug 18 '17 at 12:21
1
\$\begingroup\$

It's a bit weird to see the solution built around a pointer to a member function. As templates are involved, I would find it much more idiomatic to accept any callable object (function pointer, struct with operator(), lambda, std::function, etc..).

Boost provides Transform iterator that does exactly this.

\$\endgroup\$
  • \$\begingroup\$ It may not be as generic as a full blown transform iterator, but it is more succinct (in the case for which I wrote it). This also more clearly expresses intent, which is "hidden"/layered in the functor which adds subjectively more syntactical overhead than my approach. So yes you definitely have a point, but it's somewhat tangential to my intent. \$\endgroup\$ – rubenvb Aug 16 '17 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.