Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ API for returning sequences in a generic way

If I am writing a library and I have a function that needs to return a sequence of values, I could do something like:

std::vector<int> get_sequence();

However, this requires the library user to use the std::vector<> container rather than allowing them to use whatever container they want to use. In addition, it can add an extra copy of the returned array (depending on whether the compiler could optimize this or not) that might have a negative impact on performance.

You could theoretically enable the use of arbitrary containers (and avoid the unnecessary extra copying) by making a templated function that takes a start and an end iter:

template<class T_iter> void get_sequence(T_iter begin, T_iter end);

The function would then store the sequence values in the range given by the iterators. But the problem with this is that it requires you to know the size of the sequence so you have enough elements between begin and end to store all of the values in the sequence.

I thought about an interface such as:

template<T_insertIter> get_sequence(T_insertIter inserter);

which requires that the T_insertIter be an insert iterator (e.g. created with std::back_inserter(my_vector)), but this seems way too easy to misuse since the compiler would happily accept a non-insert iterator but would behave incorrectly at run-time.

So is there a best practice for designing generic interfaces that return sequences of arbitrary length?

like image 924
jonner Avatar asked Sep 18 '08 22:09

jonner


2 Answers

Have get_sequence return a (custom) forward_iterator class that generates the sequence on-demand. (It could also be a more advanced iterator type like bidirectional_iterator if that's practical for your sequence.)

Then the user can copy the sequence into whatever container type they want. Or, they can just loop directly on your iterator and skip the container entirely.

You will need some sort of end iterator. Without knowing exactly how you're generating the sequence, it's hard to say exactly how you should implement that. One way would be for your iterator class to have a static member function that returned an end iterator, like:

static const my_itr& end() { static const my_itr e(...); return e; };

where ... represents whatever parameters you need to create the end iterator (which might use a private constructor). Then your loop would look like:

for (my_itr i = get_sequence(); i != my_itr::end(); ++i) { ... }

Here's a trivial example of a forward iterator class that generates a sequence of consecutive integers. Obviously, this could easily be turned into a bidirectional or random access iterator, but I wanted to keep the example small.

#include <iterator>

class integer_sequence_itr
  : public std::iterator<std::forward_iterator_tag, int>
{
 private:
  int i;

 public:
  explicit integer_sequence_itr(int start) : i(start) {};

  const int& operator*()  const { return i; };
  const int* operator->() const { return &i; };

  integer_sequence_itr& operator++() { ++i; return *this; };
  integer_sequence_itr  operator++(int)
    { integer_sequence_itr copy(*this); ++i; return copy; };

  inline bool operator==(const integer_sequence_itr& rhs) const
    { return i == rhs.i; };

  inline bool operator!=(const integer_sequence_itr& rhs) const
    { return i != rhs.i; };
}; // end integer_sequence_itr

//Example:  Print the integers from 1 to 10.
#include <iostream>

int main()
{
  const integer_sequence_itr stop(11);

  for (integer_sequence_itr i(1); i != stop; ++i)
    std::cout << *i << std::endl;

  return 0;
} // end main
like image 156
cjm Avatar answered Nov 05 '22 09:11

cjm


Err... Just my two cents, but:

void get_sequence(std::vector<int> & p_aInt);

This would remove the potential return by copy problem. Now, if you really want to avoid imposing a container, you could try something like:

template <typename T>
void get_sequence(T & p_aInt)
{
    p_aInt.push_back(25) ; // Or whatever you need to add
}

This would compile only for vectors, lists and deque (and similar containers). Should you want a larget set of possible containers, the code would be:

template <typename T>
void get_sequence(T & p_aInt)
{
    p_aInt.insert(p_aInt.end(), 25) ; // Or whatever you need to add
}

But as said by other posts, you should accept to limit your interface to one kind of containers only.

like image 3
paercebal Avatar answered Nov 05 '22 11:11

paercebal