dudefromouterspace
dudefromouterspace

Reputation: 35

C++ template constructor for iterators

I want to create different types of containers, and I want to sort them with my SortedContainer class. (I don't want to use functions like std::sort() now).

std::deque<int> d; d.push_back(2); d.push_back(1);
SortedContainer<int> sc1(d.begin(), d.end());

In the SortedContainer class, I want to create a copy constructor, that handles the iterators of the containers (d.begin() and d.end()).

But if I create another type of container of STL, I want to do the same.

std::vector<int> v; v.push_back(2); d.push_back(1);
SortedContainer<int> sc2(v.begin(), v.end());

In this case, I'm using std::vector instead of std::deque. So basically, it would be a template constructor.

template <class T>
class SortedContainer
{
   //...
public:
 SortedContainer(template_iterator begin, template_iterator end)
 {
   //...
 }
};

I know if I added class template_iterator as a template parameter, it would work well.

SortedContainer<int, std::deque::const_iterator> sc1(d.begin(), d.end());

But I don't want to tell the containers' type when instantiating the SortedContainer class. I want to find it out when passing container.begin() and container.end() to its constructor.

I have already tried using std::function and some other kind of methods, but none of them worked.

Upvotes: 3

Views: 2412

Answers (3)

Vittorio Romeo
Vittorio Romeo

Reputation: 93264

You can create a make_sorted_container function that does the deduction for you:

template <typename TContainer>
auto make_sorted_container(TContainer&& c)
{
    using element_type = std::decay_t<decltype(*std::begin(c))>;
    using iterator_type = decltype(std::begin(c));

    return SortedContainer<element_type, iterator_type>(std::begin(c), std::end(c));
}

It can be used as follows:

std::vector<int> v;
auto sv = make_sorted_container(v);

static_assert(std::is_same<
    decltype(sv), 
    SortedContainer<int, typename std::vector<int>::iterator>
>{});

wandbox example


In C++17, class template deduction will allow template arguments to be deduced by constructors. In this case you need a deduction guide:

template <class T, class TItr>
class SortedContainer
{
   //...
public:
    SortedContainer(TItr b, TItr e)            
    {
        //...
    }
};

// Deduction guide:
template <class TItr>
SortedContainer(TItr b, TItr e)
    -> SortedContainer<std::decay_t<decltype(*b)>, TItr>;

Can be used like this:

std::vector<int> v;
SortedContainer sv(v.begin(), v.end());

wandbox example

Upvotes: 5

Mark B
Mark B

Reputation: 96233

I'm going to assume that the internal sorted representation of your sorted container does not depend on the type of the container used to initialize it. Then all you need to do is template the constructor:

template <typename Iter>
SortedContainer(Iter first, Iter last)
{
  //...
}

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180415

Instead of adding the template to the class you can make the function a template.

template <class T, class template_iterator>
class SortedContainer
{
   //...
public:
    SortedContainer(template_iterator begin, template_iterator end)
    {
        //...
    }
};

Becomes

template <class T>
class SortedContainer
{
   //...
public:
    template <typename Iterator>
    SortedContainer(Iterator begin, Iterator end)
    {
        //...
    }
};

So now the function will except any type but you use the name Iterator to self document that the type is expected to be an iterator. Do note that since this is a constructed SOP says that it should be constrained with SFINAE so it is not overly broad.

Upvotes: 4

Related Questions