4

I have a function which needs to receive either an std::list or an std::vector of MyClass * objects and do a bunch of processing depending on what's inside. I don't want to duplicate the function body.

I only need to use these objects to iterate over them and do some read-only checks.

I had thought of passing .begin() and .end() iterators directly, but that doesn't seem pretty at all.

Is there a better way to get around this, currently I have the following solution (which creates another list out of the vector being passed, which is hardly ideal either).

void process(std::list<MyClass*> input)
{
    //A lot of processing
    BOOST_FOREACH(MyClass* itMyClass, input)
    {
        //some checks, creating new list based on the checks      
    }
    //A lot of processing
}
void process(std::vector<MyClass*> input)
{
    process(std::list<MyClass*>(input.begin(), input.end()));
}

EDIT:

It seems that many people are suggesting to go for begin() and end() after all, I've made it work in a way similar to the example below. Thanks for your help.

//This one is private
template <typename Iterator>
void process(Iterator begin, Iterator end)
{
    //A lot of processing
    for (; begin != end; ++begin) 
    {
        //some checks, creating new list based on the checks
    }
    //A lot of processing
}
void process(std::list<MyClass*> input)
{
    process(input.begin(), input.end());
}
void process(std::vector<MyClass*> input)
{
    process(input.begin(), input.end());
}
JFMR
  • 23,265
  • 4
  • 52
  • 76
Yu Ko
  • 43
  • 4
  • 3
    Use a template? – miradulo Mar 09 '19 at 20:20
  • https://stackoverflow.com/questions/7728478/c-template-class-function-with-arbitrary-container-type-how-to-define-it – Rietty Mar 09 '19 at 20:24
  • 1
    Even though iterators don't look pretty they are consistent with the standard library and very versatile. It is worth reconsidering your stance on them. – patatahooligan Mar 09 '19 at 21:00
  • 1
    I also vote for passing *begin* and *end* iterators directly. It is the best way to decouple your algorithm from the containers you want to process. The `STL` is a work of genius, even if it doesn't always look pretty. – Galik Mar 09 '19 at 22:51
  • 1
    Alternatively, you could pass in a begin and end iterator, rather than the container. (Which works if you are iterating, but not so much if you are mutating the container.) – Eljay Mar 09 '19 at 23:37
  • 1
    It seems that begin() and end() is the people's choice, and it was my first thought too. I believe I'll have two public overrides, one for vector, one for list, and both will pass begin() and end() iterators to private function which will have the logic. Thanks – Yu Ko Mar 09 '19 at 23:48

2 Answers2

4

You can use a function template for that:

template<class ListOrVector>
void process(ListOrVector const& input) {
    //your code
}

//You can also use a template template parameter
template<template<class My, class Alloc = std::allocator<My>> class ListOrVector>
void process(ListOrVector<MyClass*, Alloc> const& input) { ... }

Note that I take the ListOrVector by const reference (The const &). This will prevent a copy.

EDIT

I have fixed the second example. The classbefore the ListOrVector was missing and the allocator is std::allocator<My by default.

Kilian
  • 533
  • 4
  • 11
  • @YuKo You probably do not care about the allocator at all. So you could simplify it to: template – Kilian Mar 10 '19 at 11:11
0

Just like ordinary functions, function templates can also be overloaded, so that you can have the best of both worlds: an iterator-based algorithm for more flexibility and a container-based one for the ease of use.

This way, you can use the iterator-based overload for processing a subrange of a container and the container-based one for processing all the elements of a container.


process(first, last)

I would suggest first defining a function template process() that takes an iterator pair for the sequence of elements you want to process:

template<typename Iterator>
void process(Iterator begin, Iterator end) {
   for (auto it = begin; it != end; ++it) {
      // ...
   }
}

This is an iterator-based algorithm (i.e., it takes an iterator pair), and corresponds to the same approach the STL algorithms follow.


process(container)

Then, I would define another function template process() that overloads the first one. This overload takes a container and calls the iterator-based version of process() on the elements of the passed container:

template<typename Container>
void process(Container const& c) {
   process(std::begin(c), std::end(c));
}

This is a container-based algorithm, since it takes a container.


This way, you can use a container-based algorithm instead of an iterator-based one:

std::vector<int> vec{1, 2, 3};
std::list<int> lst{1, 2, 3};
std::array<int, 3> arr{1, 2, 3};
int carr[] = {1, 2, 3};

process(vec);
process(lst);
process(arr);
process(carr);

This algorithm is decoupled from the containers to process (e.g., it works even with C-style arrays, as you can see), yet you call it directly on a container instead of passing iterators.

You can always call the overload of process() that takes an iterator pair instead of a container when you don't want to process all the elements of a container, but just a subrange of the elements in it.

JFMR
  • 23,265
  • 4
  • 52
  • 76