views:

510

answers:

8

As most programmers I admire and try to follow the principles of Literate programming, but in C++ I routinely find myself using std::pair, for a gazillion common tasks. But std::pair is, IMHO, a vile enemy of literate programming...

My point is when I come back to code I've written a day or two ago, and I see manipulations of a std::pair (typically as an iterator) I wonder to myself "what did iter->first and iter->second mean???".

I'm guessing others have the same doubts when looking at their std::pair code, so I was wondering, has anyone come up with some good solutions to recover literacy when using std::pair?

+14  A: 

std::pair is a good way to make a "local" and essentially anonymous type with essentially anonymous columns; if you're using a certain pair over so large a lexical space that you need to name the type and columns, I'd use a plain struct instead.

Alex Martelli
I was starting to write an answer suggesting a wrapper class to give the accessors a name, but yours is much simpler. Unless the OP needs the capabilities of an STL container.
DevSolar
This solution is quite reasonable, but DevSolar's idea would be useful when working with std::map, subclassing the iterator might be a nice solution
Robert Gould
For the kind of use cases for std::pair, I usually find the struct too much effort. The simplest struct (ie. just two members) is an Aggregate and so requires aggregate initialization or member by member initialization - both of which require a local temporary object. Alternatively you can add a constructor or write a make_pair equivalent, both of which complicate the situation IMHO.
Richard Corden
+1  A: 

As Alex mentioned, std::pair is very convenient but when it gets confusing create a structure and use it in the same way, have a look at std::pair code, it's not that complex.

stefanB
+4  A: 
typedef std::pair<bool, int> IsPresent_Value;
typedef std::pair<double, int> Price_Quantity;

...you get the point.

rlbond
Can you explain how this helps? Sure where you declare the pair it's obvious, but if you're in the body of a functor with nothing but "value_type" as the parameter, you have no idea what "first" and "second" are.
Richard Corden
A modern IDE should tell you a variable's type when you hover over it. Or have an option to flip to its declaration.
rlbond
+2  A: 

You can create two pairs of getters (const and non) that will merely return a reference to first and second, but will be much more readable. For instance:

string& GetField(pair& p) { return p.first; }
int& GetValue(pair& p) { return p.second; }

Will let you get the field and value members from a given pair without having to remember which member holds what.

If you expect to use this a lot, you could also create a macro that will generate those getters for you, given the names and types: MAKE_PAIR_GETTERS(Field, string, Value, int) or so. Making the getters straightforward will probably allow the compiler to optimize them away, so they'll add no overhead at runtime; and using the macro will make it a snap to create those getters for whatever use you make of pairs.

eran
+1  A: 

Recently I've found myself using boost::tuple as a replacement for std::pair. You can define enumerators for each member and so it's obvious what each member is:

typedef boost::tuple<int, int> KeyValueTuple;
enum {
  KEY
  , VALUE
};

void foo (KeyValueTuple & p) {
    p.get<KEY> () = 0;
    p.get<VALUE> () = 0;
}

void bar (int key, int value)
{
  foo (boost:tie (key, value));
}

BTW, comments welcome on if there is a hidden cost to using this approach.

EDIT: Remove names from global scope.

Just a quick comment regarding global namespace. In general I would use:

struct KeyValueTraits
{
  typedef boost::tuple<int, int> Type;
  enum {
    KEY
    , VALUE
  };
};

void foo (KeyValueTuple::Type & p) {
    p.get<KeyValueTuple::KEY> () = 0;
    p.get<KeyValueTuple::VALUE> () = 0;
}

It does look to be the case that boost::fusion does tie the identity and value closer together.

Richard Corden
Urgh. KEY and VALUE are now in global namespace. While possibly more readable, I don't think this is any less error-prone.
Roddy
@Roddy: Your point about KEY, VALUE in the global namespace is valid, and I've updated my answer to reflect an alternative. However, can you explain why you feel this is not less error-prone?
Richard Corden
@Richard: "0" and "1" are pretty definitive. p.get<0>() = 0; is ugly but unambiguous. However if i have (for example) const int KEY = 1, followed by your snippet, I can subsequently write p.get<KEY>() = x; without realising that I'm using the wrong "KEY".
Roddy
@Roddy: Its possible to create lots of examples where hiding one name with anohter results in "things going wrong". However, I'm very impressed with boost::fusion, and as soon as I can work out why make_map isn't compiling I'll probably move my tuple examples to use fusion maps.
Richard Corden
That's a good way to more human-friendly meaning to a tuple. I like it.
Robert Gould
+2  A: 

You could use boost tuples, but they don't really alter the underlying issue: Do your really want to access each part of the pair/tuple with a small integral type, or do you want more 'literate' code. See this question I posted a while back.

However, boost::optional is a useful tool which I've found replaces quite a few of the cases where pairs/tuples are touted as ther answer.

Roddy
BTW, as a direct result of your answer and link to question, I'm currently investigating if I can easily move to using fusion rather than my current solution.
Richard Corden
Also a good question! It's basically the generalized version of my question :)
Robert Gould
+1  A: 

I don't like std::pair as used in std::map either, map entries should have had members key and value.
I even used boost::MIC to avoid this. However, boost::MIC also comes with a cost.

Also, returning a std::pair results in less than readable code:

if (cntnr.insert(newEntry).second) { ... }

???

I also found that std::pair is commonly used by the lazy programmers who needed 2 values but didn't think why these values where needed together.

stefaanv
+3  A: 

How about this:

struct MyPair : public std::pair < int, std::string >
{
    const int& keyInt() { return first; }
    void keyInt( const int& keyInt ) { first = keyInt; }
    const std::string& valueString() { return second; }
    void valueString( const std::string& valueString ) { second = valueString; }
};

It's a bit verbose, however using this in your code might make things a little easier to read, eg:

std::vector < MyPair > listPairs;

std::vector < MyPair >::iterator iterPair( listPairs.begin() );
if ( iterPair->keyInt() == 123 )
    iterPair->valueString( "hello" );

Other than this, I can't see any silver bullet that's going to make things much clearer.

Alan
well besides giving parts names like customerId() and customerName(), the solution is viable; just need to change keyInt with customerId
Robert Gould
Robert Gould