views:

1904

answers:

9

I'm trying to get my head around tuples (thanks @litb), and the common suggestion for their use is for functions returning > 1 value.

This is something that I'd normally use a struct for , and I can't understand the advantages to tuples in this case - it seems an error-prone approach for the terminally lazy.

Borrowing an example, I'd use this

struct divide_result {
    int quotient;
    int remainder;
};

Using a tuple, you'd have

typedef boost::tuple<int, int> divide_result;

But without reading the code of the function you're calling (or the comments, if you're dumb enough to trust them) you have no idea which int is quotient and vice-versa. It seems rather like...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

...which wouldn't fill me with confidence.

So, what are the advantages of tuples over structs that compensate for the ambiguity?

+3  A: 

With tuples, you can use tie, which is sometimes quite useful: std::tr1::tie (quotient, remainder) = do_division ();. This is not so easy with structs. Second, when using template code, it's sometimes easier to rely on pairs than to add yet another typedef for the struct type.

And if the types are different, then a pair/tuple is really no worse than a struct. Think for example pair<int, bool> readFromFile(), where the int is the number of bytes read and bool is whether the eof has been hit. Adding a struct in this case seems like overkill for me, especially as there is no ambiguity here.

Anteru
Even if the types are different, the explicit naming a struct forces on you can still improve readability.
Max Howell
But how do you know it's not remainder/quotient in the "tie" line?
Roddy
i think we have the same problem with function parameters (althought we can name them, their names are only formally). a documentation documenting the returned tuple could say what position corresponds to what element.
Johannes Schaub - litb
@litb. True - which is why I also prefer passing *in* a struct rather than a long list of params. I also don't like that C++ allows param names in definition to be different from thos in declaration.
Roddy
Does it really improve readability to use a struct? that means I have to go check how the struct is defined. I may not care about that, I just need to get the int and the bool out from the object returned.
jalf
I said in cases where there is no ambiguity, the pair is no worse. In that cases the tie is rather clear. Never said that tie solves the problem with knowing which one is which :)
Anteru
+1  A: 

Prevents your code being littered with many struct definitions. It's easier for the person writing the code, and for other using it when you just document what each element in the tuple is, rather than writing your own struct/making people look up the struct definition.

Keegan Carruthers-Smith
In that case, perhaps they're best used in interfaces which are relatively private, e.g. for the return code from from a private member function.
ChrisW
*Where* would you document what each element in the tuple means? In the method that creates the tuple? In the code that stores\uses the tuple? what if several methods return the same tuple layout? Since you don't get a proper definition, there is no single place to put documentation, either.
Roy Peled
+2  A: 

Tuples will be easier to write - no need to create a new struct for every function that returns something. Documentation about what goes where will go to the function documentation, which will be needed anyway. To use the function one will need to read the function documentation in any case and the tuple will be explained there.

Vilx-
Prefer "easy-to-read" over "easy-to-write"....
Roddy
A: 

I agree with you 100% Roddy.

To return multiple values from a method, you have several options other than tuples, which one is best depends on your case:

  1. Creating a new struct. This is good when the multiple values you're returning are related, and it's appropriate to create a new abstraction. For example, I think "divide_result" is a good general abstraction, and passing this entity around makes your code much clearer than just passing a nameless tuple around. You could then create methods that operate on the this new type, convert it to other numeric types, etc.

  2. Using "Out" parameters. Pass several parameters by reference, and return multiple values by assigning to the each out parameter. This is appropriate when your method returns several unrelated pieces of information. Creating a new struct in this case would be overkill, and with Out parameters you emphasize this point, plus each item gets the name it deserves.

Tuples are Evil.

Roy Peled
Tuple are definitely not evil if you use them correctly. "Out" parameters are evil.
Zifre
if Tuple's are evil then the entire database industry is in trouble, since the entire concept of databases started with tuple relational calculas. http://en.wikipedia.org/wiki/Tuple_calculus
DouglasH
+3  A: 

Tuples are very useful in languages such as ML or Haskell.

In C++, their syntax makes them less elegant, but can be useful in the following situations:

  • you have a function that must return more than one argument, but the result is "local" to the caller and the callee; you don't want to define a structure just for this

  • you can use the tie function to do a very limited form of pattern matching "a la ML", which is more elegant than using a structure for the same purpose.

  • they come with predefined < operators, which can be a time saver.

+9  A: 

tuples

I think i agree with you that the issue with what position corresponds to what variable can introduce confusion. But i think there are two sides. One is the call-side and the other is the callee-side:

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

I think it's crystal clear what we got, but it can become confusing if you have to return more values at once. Once the caller's programmer has looked up the documentation of div, he will know what position is what, and can write effective code. As a rule of thumb, i would say not to return more than 4 values at once. For anything beyond, prefer a struct.

output parameters

Output parameters can be used too, of course:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

Now i think that illustrates how tuples are better than output parameters. We have mixed the input of div with its output, while not gaining any advantage. Worse, we leave the reader of that code in doubt on what could be the actual return value of div be. There are wonderful examples when output parameters are useful. In my opinion, you should use them only when you've got no other way, because the return value is already taken and can't be changed to either a tuple or struct. operator>> is a good example on where you use output parameters, because the return value is already reserved for the stream, so you can chain operator>> calls. If you've not to do with operators, and the context is not crystal clear, i recommend you to use pointers, to signal at the call side that the object is actually used as an output parameter, in addition to comments where appropriate.

returning a struct

The third option is to use a struct:

div_result d = div(10, 3);

I think that definitely wins the award for clearness. But note you have still to access the result within that struct, and the result is not "laid bare" on the table, as it was the case for the output parameters and the tuple used with tie.

I think a major point these days is to make everything as generic as possible. So, say you have got a function that can print out tuples. You can just do

cout << div(10, 3);

And have your result displayed. I think that tuples, on the other side, clearly win for their versatile nature. Doing that with div_result, you need to overload operator<<, or need to output each member separately.

Johannes Schaub - litb
It's much easier to write the caller code with the out params approach- intellisense can help you.Also, by convention we define an empty macro called "OUT" and put that in front of out parameters.
Roy Peled
intellisense is a good point i think. but i'm using an IDE which hasn't got that, and the point that function parameters should ideally be for input, and the return value for output (and not unused, as in that example) still holds i think
Johannes Schaub - litb
Even if you don't have intellisense, it's much easier for you to do manually what the intellisense does- looking at the method declaration :-) I do agree it's visually more appealing to have input on one side and output on the other, but it's just a matter of being consistent and getting used to it.
Roy Peled
The *intent* of the "tie" is crystal clear, but there's no compile-time checking that you have items in the correct order. So every call to div() is a potential error spot. My approach is "The code says what you mean, and comments explain why". With tuples, the code doesn't say enough.
Roddy
Yeah i'm afraid. i see that issue too. I think one should use tuples only when you can remember that order. Sure you have to look at least once into its header for its documentation (as always), but then you know the order. for div, the order can be easily remembered. For stuff like [...]
Johannes Schaub - litb
in std::map, where .first is the key, and .second is the data, things are not really all that clear anymore i think, but once you get the hang of it, you could remember too. so you should probably use a struct once you get like 4 or more members in those tuples.
Johannes Schaub - litb
+2  A: 

I tend to use tuples in conjunction with typedefs to at least partially alleviate the 'nameless tuple' problem. For instance if I had a grid structure then:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

Then I use the named type as :

grid_index find(const grid& g, int value);

This is a somewhat contrived example but I think most of the time it hits a happy medium between readability, explicitness, and ease of use.

Or in your example:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);
+4  A: 

Another option is to use a Boost Fusion map (code untested):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

You can access the results relatively intuitively:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

There are other advantages too, such as the ability to iterate over the fields of the map, etc etc. See the doco for more information.

Alastair
That's some high-powered meta'ness. But where does it actually win over a simple struct?
kizzx2
+2  A: 

One feature of tuples that you don't have with structs is in their initialization. Consider something like the following:

struct A
{
  int a;
  int b;
};

Unless you write a make_tuple equivalent or constructor then to use this structure as an input parameter you first have to create a temporary object:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

Not too bad, however, take the case where maintenance adds a new member to our struct for whatever reason:

struct A
{
  int a;
  int b;
  int c;
};

The rules of aggregate initialization actually mean that our code will continue to compile without change. We therefore have to search for all usages of this struct and updating them, without any help from the compiler.

Contrast this with a tuple:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

The compiler cannot initailize "Tuple" with the result of make_tuple, and so generates the error that allows you to specify the correct values for the third parameter.

Finally, the other advantage of tuples is that they allow you to write code which iterates over each value. This is simply not possible using a struct.

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}
Richard Corden
I think the more items are in a tuple, the better the reasons are for having it as a struct! I agree 100% with you about aggregate initialization, which is why I almost never use it! Structs with constructors (and boost:optional) work well for me.
Roddy
Re: enumerating over tuple members - it's a neat trick, but not one I've ever considered useful.
Roddy