25

A common design problem I run into, is that I bundle two variables together and then lose the ability to reference them in a meaningful way.

std::pair<int,int> cords;
cord.first = 0; //is .first the x or y coordinate?
cord.second = 0; //is .second the x or y coordinate?

I've considered writing basic structs instead, but then I lose a lot of the benefits that come along with std::pair:

  • make_pair
  • non-member overloaded operators
  • swap
  • get
  • etc.

Is there a way to rename or provide an alternative identifier for the first and second data members?

I was hoping to leverage all of the the functions that accept std::pair,
but still be able to use them in the following way:

std::pair<int,int> cords;  
//special magic to get an alternative name of access for each data member.

//.first and .second each have an alternative name.
cords.x = 1;
assert(cords.x == cords.first);
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • I don't think you can. Maybe make a class that wraps std::pair? – PC Luddite Sep 15 '15 at 16:13
  • 1
    Probably overkill for the task as defined, but [BOOST_FUSION_DEFINE_STRUCT](http://www.boost.org/doc/libs/1_59_0/libs/fusion/doc/html/fusion/adapted/define_struct.html) allows you to generate a struct that has comparison operators, swap, etc automatically generated, but with custom field names/types. – Mankarse Sep 15 '15 at 16:16
  • 2
    How about `struct cords : std::pair { int &x = this->first; int &y = this->second; };` ? – Slava Sep 15 '15 at 16:17
  • @Slava I have often read that we should not inherit from STL containers because they do not have virtual destructors. I think you're right in assuming that std::pair can be an exception to this rule. Hm, Does std::pair even qualify to be called an STL container? – Trevor Hickey Sep 15 '15 at 20:14
  • @TrevorHickey I just wanted to let you know I've updated my answer with new functionality that might bring you closer to what you want to do. – NathanOliver Sep 26 '18 at 12:48

5 Answers5

18

One way you could get around this is to use std::tie. You can tie() the return into variables that you have named so that you have good names.

int x_pos, y_pos;

std::tie(x_pos, y_pos) = function_that_returns_pair_of_cords();

// now we can use x_pos and y_pos instead of pair_name.first and pair_name.second

Another benefit with this is if you ever change the function to return a tuple tie() will also work with that.


With C++17 we now have structured bindings which allow you to declare and bind multiple variables to the return of the function. This work with arrays, tuple/pair like objects and struct/classes (as long as they meet some requirments). Using structured bindings in this case lets use convert the above example into

auto [x_pos, y_pos] = function_that_returns_pair_of_cords();

You can also do

auto& [x_pos, y_pos] = cords;

and now x_pos is a reference to cords.first and y_pos is a reference to cords.second.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
7

You can just make free functions:

int& get_x(std::pair<int, int>& p) { return p.first; }
int& get_y(std::pair<int, int>& p) { return p.second; }
int const& get_x(std::pair<int, int> const& p) { return p.first; }
int const& get_y(std::pair<int, int> const& p) { return p.second; }
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
6

Eric Niebler's tagged might help here. The basic idea is that you create getters like this:

struct x_tag {
    template<class Derived, class Type, std::size_t N>
    struct getter {
        Type& x() & { 
            return std::get<N>(static_cast<Derived&>(*this)); 
        }
        Type&& x() && { 
            return std::get<N>(static_cast<Derived&&>(*this)); 
        }
        const Type& x() const & { 
            return std::get<N>(static_cast<const Derived&>(*this)); 
        }
        const Type&& x() const && { 
            return std::get<N>(static_cast<const Derived&&>(*this)); 
        }
    };
};

And you can similarly implement y_tag (just change the member function names to y()). Then:

template<class, class, class...> struct collect;
template<class Derived, std::size_t... Ns, class... Tags>
struct collect<Derived, std::index_sequence<Ns...>, Tags...>
      : Tags::template getter<Derived, std::tuple_element_t<Ns, Derived>, Ns>...{};

template<class Base, class... Tags>
struct tagged : Base, collect<tagged<Base, Tags...>, 
                              std::index_sequence_for<Tags...>, Tags...> {
    using Base::Base;
    // extra polish for swap and converting from other tagged's.
};

namespace std
{
    template<typename Base, typename...Tags>
    struct tuple_size<tagged<Base, Tags...>>
      : tuple_size<Base>
    {};

    template<size_t N, typename Base, typename...Tags>
    struct tuple_element<N, tagged<Base, Tags...>>
      : tuple_element<N, Base>
    {};
}

Then

using coord_t = tagged<std::pair<int, int>, x_tag, y_tag>;
T.C.
  • 133,968
  • 17
  • 288
  • 421
1

You cannot rename std::pair's members, but you could create an equivalent class that has named variables. Instead of templates, you can fall back on #defines. You can have this declaration:

DefinePair(Dimensions, int, Height, int, Width);
Dimensions dimensions(3, 4);
cout << dimensions.mHeight;

which isn't the same as std::pair but gives you the ease of declaration that people want from std::pair while retaining the naming.

There are lots of ways to structure this -- you could inherit from std::pair and then expose the named variables as references to the first and second, if you need to plug into something that takes pairs. But the simplest implementation is something like this:

#define DefinePair(StructName, FirstType, FirstName, SecondType, SecondName)    \
    struct StructName { \
        FirstType m##FirstName; \
        SecondType m##SecondName; \
        StructName(FirstType FirstName, SecondType SecondName) \
            : m##FirstName(FirstName), \
            m##SecondName(SecondName) \
        {} \
    };

It would be nice if we could do this with C++ templates, but I know of no way to do it. It would require some sort of new keyword like "template " where identifier would mean "this template parameter is going to be used to name variables, types, or methods inside the template."

srm
  • 3,062
  • 16
  • 30
-3

You can use

#define _px first   
#define _py second
polkovnikov.ph
  • 6,256
  • 6
  • 44
  • 79
newbie
  • 19
  • A hack like that is a recipe for extremely hard to debug bugs in the future when it mysteriously replaces tokens, especially in other files. – Gamrix Jun 22 '22 at 22:31