Templated Function that works for iterators over source pointers, as well as iterators over unique_ptrs

Let's say I have a template function that takes a range of constants (or better, start and end-iterators) of some sort of pointer collection. This function internally creates an STL container with pointers for reorganizing items.

Now I want to use this function again for unique_ptr collections. I somehow need to change the template parameters or introduce a new shell or overload ... but how? Is there any magic to the C ++ 11 template, STL helper or helper helper? Following the code example:

#include <string> #include <iostream> #include <vector> #include <algorithm> #include <memory> // Element Class class Foo { }; // Take a range of elements, sort them internally by their addresses and print them in order template <typename FooIterator> void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos) { // Sort them std::vector<const Foo*> elements(beginFoos, endFoos); std::sort(elements.begin(), elements.end()); // Print them for(const auto& e : elements) std::cout << e << std::endl; } int main() { std::vector<Foo*> raw_foos; std::vector<std::unique_ptr<Foo>> unique_foos; // Fill them for(int i=0; i<10; i++) { std::unique_ptr<Foo> foo(new Foo()); raw_foos.push_back(foo.get()); unique_foos.push_back(std::move(foo)); } print_sorted_addresses(raw_foos.cbegin(), raw_foos.cend()); //print_sorted_Foos(unique_foos.cbegin(), unique_foos.cend()); // ERROR return 0; } 

The offender, apparently, is the uneven behavior of the source pointers and smart pointers (in particular, unique_ptr ) to convert them as source pointers. This can either be bypassed through the à la std::addressof(*p) dereferencing cycle, but it only has well-defined behavior if p is not nullptr . To reduce any runtime checks, I played with conditional patterns and came up with the following:

 template<typename Ptr> using RawPtr = typename std::pointer_traits<Ptr>::element_type*; // raw pointers like int**, const char*, ... template<typename Ptr> typename std::enable_if<std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr ptr) { return ptr; } // smart pointers like unique_ptr, shared_ptr, ... template<typename Ptr> typename std::enable_if<!std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr& ptr) { return ptr.get(); } 

This can be used in the @tclamb iterator or in boost :: transform_iterator, as in @Praetorian's answer. But it still seems strange to use a specific get () - a member of the smart-pointer implementation instead of the * -interface operator, which makes the pointer a pointer.

+6
source share
3 answers

Here is a general approach that wraps a pointer iterator. When dereferencing, it looks for the stored iterator (gives a pointer (smart-)) and again plays out (gives a link to pointee), and then returns the pointee address (via std::addressof() ). The rest of the implementation is just an iterator pattern.

 template<typename Iterator, typename Address = decltype(std::addressof(**std::declval<Iterator>())) > class address_iterator : public std::iterator<std::input_iterator_tag, Address> { public: address_iterator(Iterator i) : i_{std::move(i)} {}; Address operator*() const { auto&& ptr = *i_; return i_ == nullptr ? nullptr : std::addressof(*ptr); }; Address operator->() const { return operator*(); } address_iterator& operator++() { ++i_; return *this; }; address_iterator operator++(int) { auto old = *this; operator++(); return old; } bool operator==(address_iterator const& other) const { return i_ == other.i_; } private: Iterator i_; }; template<typename I, typename A> bool operator!=(address_iterator<I, A> const& lhs, address_iterator<I, A> const& rhs) { return !(lhs == rhs); } template<typename Iterator> address_iterator<Iterator> make_address_iterator(Iterator i) { return i; } 

Live example on Coliru (with std::random_shuffle() thrown for fun). :)

+3
source

The problem with your code when working with unique_ptr is the following line:

 std::vector<const Foo*> elements(beginFoos, endFoos); 

The vector constructor will try to copy unique_ptr s, which is not allowed; and you are interested in what unique_ptr means. Thus, you need an additional dereferencing level to get a reference to the managed entity. This can be achieved using Boost.IndirectIterator .

Using boost::indirect_iterator , you get Foo const& , which can then be converted to Foo const * by wrapping it in Boost.TransformIterator and passing std::addressof as a unary predicate to boost::transform_iterator .

 template <typename FooIterator> void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos) { std::vector<Foo const *> elements( boost::make_transform_iterator(boost::make_indirect_iterator(beginFoos), std::addressof<Foo>), boost::make_transform_iterator(boost::make_indirect_iterator(endFoos), std::addressof<Foo>)); std::sort(elements.begin(), elements.end()); for(const auto& e : elements) std::cout << e << std::endl; } 

Live demo

+1
source

My 2 coins

 Foo* get(Foo* const& p) { return p; } Foo* get(std::unique_ptr<Foo> const& up) { return up.get(); } // Take a range of elements, sort them internally by their addresses and print // them in order template <typename ConstIt> void print_sorted_addresses(const ConstIt& cbegin, const ConstIt& cend) { using deref_type = decltype(*cbegin); using raw_ptr_type = decltype(get(*cbegin)); std::vector<raw_ptr_type> v; v.reserve(cend - cbegin); std::transform(cbegin, cend, std::back_inserter(v), [] (const deref_type& p) { return get(p); }); std::sort(v.begin(), v.end()); for(const auto& p : v) std::cout << p << '\n'; } 
0
source

Source: https://habr.com/ru/post/972663/


All Articles