Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated Function that works for iterators over raw pointers as well as iterators over unique_ptrs

Let's say I have a template function that takes a const range (or better, begin- and end-iterators) of some kind of pointer-collections. This function internally constructs a STL-container with pointers to reorganize the elements.

Now I want to reuse this function for unique_ptr-collections as well. I somehow need to modify the template parameters or introduce a new wrapper or overload... but how? Is there any C++11 template magic, STL helper or boost helper? Following an example code:

#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 culprit seems to be the non-uniform behavior of raw pointers and smart pointers (unique_ptr in particular) for converting them both to raw pointers. This can either be circumvented via a dereferencing-cycle à la std::addressof(*p), but this only has well-defined behavior if p is not nullptr. To reduce any runtime-checks I played with conditional templates 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 could be used in @tclamb's iterator, or in boost::transform_iterator as in @Praetorian's answer. But it still feels strange to build upon the specific get()-member of a smart-pointer implementation instead of the operator*-interface what makes a pointer a pointer.

like image 308
Thomas B. Avatar asked Jul 22 '14 17:07

Thomas B.


2 Answers

Here's a generic approach that wraps the pointer iterator. On dereference, it dereferences the stored iterator (yielding the (smart-)pointer), and dereferences again (yielding a reference to the pointee), and then returns the pointee's address (via std::addressof()). The rest of the implementation is just iterator boilerplate.

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 a std::random_shuffle() thrown in for fun). :)

like image 72
tclamb Avatar answered Nov 14 '22 17:11

tclamb


The problem with your code when dealing with unique_ptr is this line:

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

The vector constructor is going to try and copy the unique_ptrs, which is not allowed; and you're interested in what the unique_ptr points to anyway. So you need an extra level of dereferencing to yield a reference to the managed object. This can be achieved using Boost.IndirectIterator.

Using boost::indirect_iterator will yield Foo const&, which can then be converted to Foo const * by wrapping it in Boost.TransformIterator, and passing std::addressof as the 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

like image 1
Praetorian Avatar answered Nov 14 '22 17:11

Praetorian