Can I use std::pair, but rename .first and .second member names?

吃可爱长大的小学妹 提交于 2019-11-30 01:13:46

问题


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);

回答1:


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.




回答2:


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; }



回答3:


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>;



回答4:


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."




回答5:


You can use

#define _px first   
#define _py second


来源:https://stackoverflow.com/questions/32590764/can-i-use-stdpair-but-rename-first-and-second-member-names

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!