views:

748

answers:

9

In C++, whenever a function creates many (hundreds or thousands of) values, I used to have the caller pass an array that my function then fills with the output values:

void computeValues(int input, std::vector<int>& output);

So, the function will fill the vector output with the values it computes. But this is not really good C++ style, as I'm realizing now.

The following function signature is better because it doesn't commit to using a std::vector, but could use any container:

void computeValues(int input, std::insert_iterator<int> outputInserter);

Now, the caller can call with some inserter:

std::vector<int> values; // or could use deque, list, map, ...
computeValues(input, std::back_inserter(values));

Again, we don't commit to using std::vector specifically, which is nice, because the user might just need the values in a std::set etc. (Should I pass the iterator by value or by reference?)

My question is: Is the insert_iterator the right or standard way to do it? Or is there something even better?

EDIT: I edited the question to make it clear that I'm not talking about returning two or three values, but rather hundreds or thousands. (Imagine you have return all the files you find in a certain directory, or all the edges in a graph etc.)

+1  A: 

One other option is boost::tuple: http://www.boost.org/doc/libs/1_38_0/libs/tuple/doc/tuple_users_guide.html

int x, y;
boost::tie(x,y) = bar();
CTT
boost::tie is nice, but see my update: It's really about hundreds of values or more.
+1  A: 
  • A stadard container works for homogenous objects 9which you can return).
  • The standard library way is to abstract an algorithm from the container and use iterators to bridge the gap between.
  • If you need to pass more than a single type think of structs/classes.

My question is: Is the insert_iterator the right or standard way to do it?

Yes. Otherwise, if you are not going to have at least as many elements in your container as there will be computed values. This is not always possible, specially, if you want to write to a stream. So, you are good.

dirkgently
+2  A: 

If you ever have a group of objects, chances are you have at least a few methods that work on that group of objects (otherwise, what are you doing with them?)

If that's the case, it would make sense to have those methods in a class that contain both said objects and methods.

If that makes sense and you have such a class, return it.

I virtually never find myself thinking that I wish I could return more than one value. By the fact that a method should only do one small thing, your parameters and return values tend to have a relationship, and so are more often than not deserving of a class that contains them, so returning more than one value is rarely interesting (Maybe I wished for it 5 times in 20 years--each time I refactored instead, came up with a better result and realized my first attempt was sub-standard.)

Bill K
+7  A: 

Response to Edit: Well, if you need to return hundreds and thousands if values, a tuple of course would not be the way to go. Best pick the solution with the iterator then, but it's best not use any specific iterator type.


If you use iterators, you should use them as generic as possible. In your function you have used an insert iterator like insert_iterator< vector<int> >. You lost any genericity. Do it like this:

template<typename OutputIterator>
void computeValues(int input, OutputIterator output) {
    ...
}

Whatever you give it, it will work now. But it will not work if you have different types in the return set. You can use a tuple then. Also available as std::tuple in the next C++ Standard:

boost::tuple<int, bool, char> computeValues(int input) { 
    ....
}

If the amount of values is variadic and the type of the values is from a fixed set, like (int, bool, char), you can look into a container of boost::variant. This however implies changes only on the call-side. You can keep the iterator style of above:

std::vector< boost::variant<int, bool, char> > data;
computeValues(42, std::back_inserter(data));
Johannes Schaub - litb
+1  A: 

Your example with insert_iterator won't work, because insert_iterator is a template requiring a container for a parameter. You could declare it

void computeValues(int input, std::insert_iterator<vector<int> > outputInserter);

or

template<class Container>
void computeValues(int input, std::insert_iterator<Container> outputInserter);

The first will tie you back to a vector<int> implementation, without any obvious advantages over your initial code. The second is less restrictive, but implementing as a template will give you other constraints that might make it a less desirable choice.

Mark Ransom
+6  A: 

You could return a smart pointer to a vector. That should work and no copy of the vector will be made.

If you don't want to keep the smart pointer for the rest of your program, you could simply create a vector before calling the function, and swap both vectors.

Benoît
+1  A: 

I'd use something like

std::auto_ptr<std::vector<int> > computeValues(int input);
{
   std::auto_ptr<std::vector<int> > r(new std::vector<int>);
   r->push_back(...) // Hundreds of these
   return r;
}

No copying overhead in the return or risk of leaking (if you use auto_ptr correctly in the caller).

timday
+2  A: 

Actually, your old method of passing in the vector has a lot to recommend it -- it's efficient, reliable, and easy to understand. The disadvantages are real but don't apply equally in all cases. Are people really going to want the data in an std::set or list? Are they really going to want to use the long list of numbers without bothering to assign it to a variable first (one of the reasons to return something via 'return' rather than a parameter)? Being generic is nice, but there is a cost in your programming time that may not be redeemed.

A: 

I'd say your new solution is more general, and better style. I'm not sure I'd worry too much about style in c++, more about usability and efficiency.

If you're returning a lot of items, and know the size, using a vector would allow you to reserve the memory in one allocation, which may or may not be worth it.

tfinniga