4

I work a lot with pairs of values: std::pair<int, int> my_pair. Sometimes I need to perform the same operation on both my_pair.first and my_pair.second.

My code would be much smoother if I could do my_pair[j] and loop over j=0,1. (I am avoiding using arrays because I don't want to bother with allocating memory, and I use pair extensively with other things).

Thus, I would like to define operator[] for std::pair<int, int>.

And I can't get it to work, (I'm not very good with templates and such)...

#include <utility>
#include <stdlib.h>

template <class T1> T1& std::pair<T1, T1>::operator[](const uint &indx) const
{
  if (indx == 0)
    return first;
  else
    return second;
};

int main()
{
// ....
return 0;
}

fails to compile. Other variations fail as well.

As far as I can tell, I am following the Stack Overflow operator overloading FAQ, but I guess I am missing something...

Community
  • 1
  • 1
cmo
  • 3,762
  • 4
  • 36
  • 64
  • The standard does not allow you to define functions in the `std` namespace. – Oliver Charlesworth Mar 02 '12 at 00:39
  • @Oli: It's worse, you can't just add function definition to a class without the function being declared. :) – Xeo Mar 02 '12 at 00:40
  • In my mind, a `pair` is for two things that "go together" but aren't the same type of object. They could both be the same data type but have different semantic meanings - for example, `int PartNumber` and `int numInStock`. So it feels odd to do the same exact operation to both elements of a `pair`. Is there a reason you're not using 2-element `vector`s? – japreiss Mar 02 '12 at 00:44
  • Two possible approaches: Define a new class that simply wraps `std::pair`, to which you can add the desired operator. Bleh. Better is to simply write a macro. If you want it so badly, a macro may be tolerable here. But I agree with japreiss, 2-element std::vectors might be good here. – Prashant Kumar Mar 02 '12 at 00:44
  • @japreiss Two element vectors are a significant waste of memory, aren't they? I will have many thousands of these... – cmo Mar 02 '12 at 00:47
  • No need for `std::vector` if you have a fixed number of elements. Use a regular C array here or `std::array` in C++11. – Xeo Mar 02 '12 at 00:48

3 Answers3

8
  1. you cannot overload operator[] as a non-member
  2. you cannot define a member function which has not been declared in the class definition
  3. you cannot modify the class definition of std::pair

Here's a non-member implementation:

/// @return the nth element in the pair. n must be 0 or 1.
template <class T>
const T& pair_at(const std::pair<T, T>& p, unsigned int n)
{
    assert(n == 0 || n == 1 && "Pair index must be 0 or 1!");
    return n == 0 ? p.first: p.second;
}

/// @return the nth element in the pair. n must be 0 or 1.
template <class T>
T& pair_at(std::pair<T, T>& p, unsigned int index)
{
    assert(index == 0 || index == 1 && "Pair index must be 0 or 1!");
    return index == 0 ? p.first: p.second;
}

// usage:
pair<int, int> my_pair(1, 2);
for (int j=0; j < 2; ++j)
    ++pair_at(my_pair, j);

Note that we need two versions: one for read-only pairs and one for mutable pairs.

Don't be afraid to use non-member functions liberally. As Stroustrup himself said, there is no need to model everything with an object or augment everything through inheritance. If you do want to use classes, prefer composition to inheritance.

You can also do something like this:

/// Applies func to p.first and p.second.
template <class T, class Func>
void for_each_pair(const std::pair<T, T>& p, Func func)
{
    func(p.first);
    func(p.second);
}

/// Applies func to p.first and p.second.
template <class T, class Func>
void for_each_pair(std::pair<T, T>& p, Func func)
{
    func(p.first);
    func(p.second);
}

// usage:
pair<int, int> my_pair(1, 2);
for_each_pair(my_pair, [&](int& x){
    ++x;
});

That isn't too unwieldy to use if you have C++11 lambdas and is at least a bit safer since it has no potential to access out of bounds.

stinky472
  • 6,737
  • 28
  • 27
  • Meh, exactly what I wanted to write. :( +1 – Xeo Mar 02 '12 at 00:46
  • *hits F5 until the 5 minute edit grace period is over...* - @Benjamin: But he forgot that other function. :) – Xeo Mar 02 '12 at 00:50
  • @stinky472 Can you explain the "performance hit" due to "runtime branching"? Or is it too complicated to write in a comment – cmo Mar 02 '12 at 00:51
  • 2
    Pff, branching overhead... inlining, loop analysis and constant propagation would like a word with you. :P – Xeo Mar 02 '12 at 00:54
  • @CycoMatto effectively a ternary operator involves some kind of branching (branching out to different parts of code depending on some condition), just like the if/else you have originally. Branching is actually rather expensive depending on a lot of factors like branch prediction. It's not always a trivial overhead, but I wouldn't worry about it yet if you are constantly finding yourself repeating the same code twice to apply operations to both elements of a pair: correctness first, and correctness is easy if you are doing only writing half the code. – stinky472 Mar 02 '12 at 00:56
  • @Xeo actually you are right with 'n' being passed a compile-time constant in most cases combined with inlining and loop unrolling. I was a bit too hasty with that comment. I'm going to omit the efficiency part out of the answer since that's speculative and it might lead to completely unnecessary, premature attempts at optimization. – stinky472 Mar 02 '12 at 00:58
3

You cannot add functions to an existing class like this. And you certainly can't do it to things in the std namespace.

So you should define your own wrapper class:

class MyPair {
private:
    std::pair<int,int> p;

public:
    int &operator[](int i) { return (i == 0) ? p[0] : p[1]; }
    // etc.
};
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
0

You probably should check out Boost.Fusion. You can apply algorithms to sequences(which std::pair is considered a sequence). So for example you can do for_each like this:

std::pair<int, int> my_pair;
for_each(my_pair, [] (int i)
{
    cout << i;
});

You can also access the index of the element like this:

int sum = at_c<0>(my_pair) + at_c<1>(my_pair);
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59