AttributeIterator erstellen



  • Hi,

    ich suche die kürzeste Form einen AttributeIterator zu erstellen, der in etwa so funktioniert:

    struct Object
    {
        int x;
        int y;
    };
    
    vector<Object> objects{{1, 2}, {3, 4}, {5, 6}, {7, 8}};
    
    auto itBegin = make_attribute_iterator(objects.begin(), &Object::x);
    auto itEnd = make_attribute_iterator(objects.end(), &Object::x);
    
    std::for_each(itBegin, itEnd, [](int x) {cout << x << ' ';});
    

    Erwartete Ausgabe:
    1 3 5 7

    for_each ist natürlich nur ein Beispiel für irgendeine iterator-erwartende Funktion.

    Meine ersten Ideen führten in die Richtung von boost::transform_iterator. Der lässt sich aber nicht komfortabel mit einem Lambda initialisieren, sondern braucht ein Funktionsobjekt, das man aber auch wieder nicht direkt mit einem Lambda initialisieren kann (jedenfalls nicht mit automatischer Typdeduzierung, da es ja vermutlich begründeterweise make_function nicht gibt).

    Man könnte sich selbst ein make_attribute_iterator schreiben, aber eigentlich würde ich lieber auf Bewährtes zurückgreifen und damit möglichst kurzen Code schreiben. Ein Funktionstemplate für make_attribute_iterator wäre natürlich auch eine Idee.

    Gibt es eine gute, kurze Lösung für mein Problem?



  • Naja, boost und kurz schließt sich meist gegenseitig aus.
    Ich habe mir dafür ein Makro angelegt, womit ich das schön kurz schreiben kann:

    foreach(x,attributeRange(objects,x))cout << x << ' ';
    

    Wenn du dir also für Makros nicht zu schade bist...

    Wenn doch, ist der kürzeste Weg, dies vernünftig zu machen, also so:

    vector<int> xs;
    foreach(o,objects)xs.push_back(o.x);
    


  • Warum den so umständlich? (Mir ist aktuell nicht klar, was für ein Vorteil das hat.)

    Warum nicht mit ranged-based-loop?

    std::vector<Object> v = { { 1, 2 }, { 2, 3 }, { 4, 5 } };
    
    for (auto o : v) {
      std::cout << o.x;
    }
    


  • Naja, in einem tatsächlichen Anwendungsfall würde man das auch nicht ausgeben, sondern an eine weitere Algorithmusfunktion verfüttern oder ähnliches.



  • for_each ist natürlich nur ein Beispiel für irgendeine iterator-erwartende Funktion.

    ;sebi:
    Makro geht natürlich auch. Die kürzeste Nicht-Makro-Variante interessiert mich hier jedoch eher. 🙂

    Das kürzeste mit boost ist zurzeit:

    std::function<int(const Object&)> xExtractor(
    				[&](const Object& info) {return &info.x; });
    
    auto styleBegin = boost::make_transform_iterator(objects.begin(), xExtractor);
    auto styleEnd = boost::make_transform_iterator(objects.end(), xExtractor);
    
    algo(styleBegin, styleEnd);
    

    Geht ja fast noch... ist aber hart an der Schmerzgrenze, weil ich das schon wieder lieber in eine Funktion auslagern würde, da es zu viele Zeilen hat. Eigentlich suche ich nach einer Lösung, die ich guten Gewissens direkt in irgendwelche Funktionen schreiben kann.



  • Eisflamme schrieb:

    std::function<int(const Object&)> xExtractor(
    				[&](const Object& info) {return &info.x; });
    ...
    
    auto extractor = bind(&Object::x, placeholders::_1);
    


  • Generell nice, aber das const fliegt auf die Weise raus und es kompiliert nicht. cref drumherum scheint nicht zu helfen 😞



  • Eisflamme schrieb:

    Generell nice, aber das const fliegt auf die Weise raus und es kompiliert nicht. cref drumherum scheint nicht zu helfen 😞

    Hmm.
    Was soll ich sagen ausser dem wenig hilfreichen: bei mir kompiliert es doch! 🙂
    (Z.19 u. 20 ohne std::function ).
    Allerdings weiss ich nicht genau, welches const "rausfliegt"?

    #include <iostream>
    #include <vector>
    #include <functional>
    #include <algorithm>
    
    #include <boost/iterator/transform_iterator.hpp>
    
    struct Object
    {
        int x;
        int y;
    };
    
    using namespace std;
    
    int main() {
      const vector<Object> objects{{1, 2}, {3, 4}, {5, 6}, {7, 8}};
    
      auto extractor = bind(&Object::x, placeholders::_1);
      //auto extractor = [](const Object& o){ return o.x; };
      auto first = boost::make_transform_iterator(begin(objects), extractor);
      auto last  = boost::make_transform_iterator(end(objects), extractor);
    
      for_each(first, last, [](int x) {cout << x << ' ';});
    
    }
    

  • Mod

    Oder, wenn man es auch für constant expressions funktionieren haben will:

    #include <iterator>
    #include <tuple>
    
    template <typename Extractor, typename Iterator>
    struct transform_iterator
    {
    	using iterator_type = Iterator;
    	using extractor_type = Extractor;
    
    	using reference = std::result_of_t<
    	                    extractor_type const&(typename std::iterator_traits<iterator_type>::reference)
    	                  >;
    	using value_type = std::decay_t<reference>;
    	using pointer = value_type*;
    	using difference_type = typename std::iterator_traits<Iterator>::difference_type;
    	using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
    
    private:
    
    	std::tuple<Extractor, iterator_type> _data;
    	constexpr decltype(auto) _it()       {return std::get<iterator_type>(_data);}
    	constexpr decltype(auto) _ex() const {return std::get<extractor_type>(_data);}
    
    public:
    
    	constexpr auto extractor() const {return _ex();}
    	constexpr auto base() const {return std::get<iterator_type>(_data);}
    
    	constexpr explicit transform_iterator(iterator_type it, extractor_type const& ex = {}) : _data(ex, it) {}
    
    	template <typename Q, typename Iter2>
    	constexpr transform_iterator(transform_iterator<Q, Iter2> const& other)
    		: _data(other._data) {}
    
    	constexpr reference operator*() const {return _ex()(*base());}
    
    	constexpr pointer operator->() const {return &operator*();}
    
    	constexpr transform_iterator& operator++() {
    		++_it();
    		return *this;
    	}
    
    	constexpr transform_iterator operator++(int) {
    		auto t = *this;
    		++_it();
    		return t;
    	}
    
    	constexpr transform_iterator& operator--() {
    		--_it();
    		return *this;
    	}
    
    	constexpr transform_iterator operator--(int) {
    		transform_iterator t = *this;
    		--_it();
    		return t;
    	}
    
    	constexpr transform_iterator operator+(difference_type n) const
    	{ return transform_iterator(base() + n); }
    
    	constexpr transform_iterator& operator+=(difference_type n) {
    		_it() += n;
    		return *this;
    	}
    
    	constexpr transform_iterator operator-(difference_type n) const
    	{ return transform_iterator(base() - n); }
    
    	constexpr transform_iterator& operator-=(difference_type n) {
    		_it() -= n;
    		return *this;
    	}
    
    	constexpr reference operator[](difference_type n) const
    	{ return *(*this + n); }
    };
    
    template <typename P, typename I>
    constexpr bool operator==(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return lhs.base() == rhs.base(); }
    
    template <typename P, typename I>
    constexpr bool operator <(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return rhs.base() < lhs.base(); }
    
    template<typename P, typename I>
    constexpr bool operator!=(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return !(lhs == rhs); }
    
    template <typename P, typename I>
    constexpr bool operator >(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return rhs < lhs; }
    
    template <typename P, typename I>
    constexpr bool operator<=(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return !(rhs < lhs); }
    
    template <typename P, typename I>
    constexpr bool operator>=(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return !(lhs < rhs); }
    
    template <typename P, typename I>
    constexpr auto operator-(transform_iterator<P,I> const& lhs, transform_iterator<P,I> const& rhs)
    { return rhs.base() - lhs.base(); }
    
    template <typename P, typename I>
    constexpr auto operator+(typename transform_iterator<P,I>::difference_type n, transform_iterator<P,I> const& lhs)
    { return transform_iterator<P,I>(lhs.base() - n); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator==(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return lhs.base() == rhs.base(); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator <(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return rhs.base() < lhs.base(); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator!=(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return !(lhs == rhs); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator >(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return rhs < lhs; }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator<=(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return !(rhs < lhs); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr bool operator>=(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return !(lhs < rhs); }
    
    template <typename P1, typename I1, typename P2, typename I2>
    constexpr decltype(auto) operator-(transform_iterator<P1,I1> const& lhs, transform_iterator<P2,I2> const& rhs)
    { return rhs.base() - lhs.base(); }
    
    template <typename E, typename I>
    constexpr auto make_transform_iterator(I i, E const& e) {
    	return transform_iterator<E,I>(i,e);
    }
    
    template <typename P>
    struct member_subobject_extractor {
    	P p;
    	template <typename T>
    	constexpr decltype(auto) operator()(T&& t) const {
    		return std::forward<T>(t).*p;
    	}
    };
    template <typename B>
    struct base_subobject_extractor {
    	template <typename T>
    	constexpr std::conditional_t<std::is_const<T>{}, const B, B>& operator()(T& t) const {
    		return t;
    	}
    	template <typename T>
    	constexpr std::conditional_t<std::is_const<T>{}, const B, B>&& operator()(T&& t) const {
    		return std::move(t);
    	}
    };
    
    template <typename P>
    constexpr auto make_subobject_extractor(P p) {
    	return member_subobject_extractor<P>{p};
    }
    
    template <typename P, typename I>
    constexpr auto make_subobject_iterator(I i, P p) {
    	return make_transform_iterator(i, make_subobject_extractor(p));
    }
    
    #include <iostream>
    
    int main() {
    	struct S {int i; int const j=i;} arr[3] {{1},{2},{3}};
    	auto it = make_subobject_iterator(std::begin(arr), &S::j);
    	decltype(it) end(std::end(arr));
    	while (it != end)
    		std::cout << *it++ << ", ";
    }
    


  • Hi,

    danke, das sieht super aus!

    In welchen Anwendungsfällen bringt mir constexpr hier einen Performance- (oder sonstigen!?) Vorteil?


  • Mod

    Eisflamme schrieb:

    In welchen Anwendungsfällen bringt mir constexpr hier einen Performance- (oder sonstigen!?) Vorteil?

    Eigentlich war das constexpr nicht für deine Anwendungsfälle gedacht. Es ist eher ein obligatorischer Teil einer modernen Implementierung, weil es keinen Grund gibt, sie nicht auch für konstante Ausdrücke verfügbar zu machen.

    Außerdem kann der Compiler kann in entsprechenden Szenarien ggf. zu constant folding motiviert werden.


Log in to reply