我可以使用std::pair,但是重命名.first和.second成员名吗?

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

本文关键字:second first 成员 重命名 可以使 std pair 我可以      更新时间:2023-10-16

我遇到的一个常见的设计问题是,我将两个变量捆绑在一起,然后失去了以有意义的方式引用它们的能力。

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?

我考虑过写基本结构体,但这样我就失去了std::pair带来的很多好处:

  • make_pair
  • 非成员重载操作符
  • 得到
  • 等。

是否有一种方法来重命名或提供firstsecond数据成员的替代标识符?

我希望利用所有接受std::pair的功能,
但仍然能够以以下方式使用它们:

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

解决这个问题的一种方法是使用std::tie。您可以将返回值tie()设置为已命名的变量,这样您就有了好的名称。

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

这样做的另一个好处是,如果您更改函数以返回元组,tie()也可以使用。


在c++ 17中,我们现在有了结构化绑定,允许你声明和绑定多个变量到函数的返回值。这适用于数组、元组/对类对象和结构/类(只要它们满足某些要求)。在本例中使用结构化绑定,让我们将上面的示例转换为
auto [x_pos, y_pos] = function_that_returns_pair_of_cords();

你也可以做

auto& [x_pos, y_pos] = cords;

,现在x_pos是对cords.first的引用,y_pos是对cords.second的引用

你可以创建自由函数:

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

Eric Niebler的tagged可能会有所帮助。基本思想是创建像这样的getter:

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

您可以类似地实现y_tag(只需将成员函数名称更改为y())。然后:

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>
    {};
}
然后

using coord_t = tagged<std::pair<int, int>, x_tag, y_tag>;

您不能重命名std::pair的成员,但是您可以创建一个具有命名变量的等价类。您可以使用#define代替模板。你可以这样声明:

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

,它与std::pair不同,但在保留命名的同时,为您提供了人们希望从std::pair声明的便利性。

有很多方法来构造它——你可以继承std::pair,然后公开命名变量作为对第一个和第二个的引用,如果你需要插入一些接受成对的东西。但最简单的实现是这样的:

#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) 
        {} 
    };

如果我们能用c++模板做到这一点就好了,但我知道没有办法做到。它需要一些新的关键字,比如"template",其中的标识符表示"这个模板参数将用于命名模板内的变量、类型或方法"。

可以使用

#define _px first   
#define _py second