问题
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