(Re)named std::pair members

2019-01-22 12:57发布

Instead of writing town->first I would like to write town->name. Inline named accessors (Renaming first and second of a map iterator and Named std::pair members) are the best solutions I have found so far. My problem with named accessors is the loss of type safety: pair<int,double> may refer to struct { int index; double value; } or to struct { int population; double avg_temp; }. Can anyone propose a simple approach, perhaps something similar to traits?

I often want to return a pair or a tuple from a function and it is quite tiring to introduce a new type like struct city { string name; int zipcode; } and its ctor every time. I am thrilled to learn about boost and C++0x but I need a pure C++03 solution without boost.

Update

Re andrewdski's question: yes, a (hypothetical) syntax like pair<int=index, double=value> which would create a distinct type from pair<int=population, double=avg_temp> would meet your requirement. I do not even mind having to implement a custom pair/tuple template class ONCE and just passing a 'name traits' template argument to it approprietly when I need a new type. I have no idea how that 'name traits' would look like. Maybe it's impossible.

9条回答
我命由我不由天
2楼-- · 2019-01-22 13:12

Although not perfect, it is possible to use tagged data:

template <typename tag_type, typename pair_type>
typename tag_type::type& get(pair_type& p);

typedef std::pair<std::string /*name*/, int /*zipcode*/> city;
struct name { typedef std::string type; };
struct zipcode { typedef int type; };

template <>
std::string& get<name, city>(city& city)
{
   return city.first;
}

template <>
int& get<zipcode, city>(city& city)
{
   return city.second;
}

int main()
{
   city c("new york", 10001);
   std::string n = get<name>(c);
   int z = get<zipcode>(c);
}

But as Ben Voigt says: struct city { string name; int zipcode; }; would pretty much always be better.

EDIT: Templates probably are an overkill, you could use free functions in a namespace instead. This still does not solve type safety issues, as any std::pair<T1, T2> are the same type as any other std::pair<T1, T2>:

namespace city
{
   typedef std::pair<std::string /*name*/, int /*zipcode*/> type;

   std::string& name(type& city)
   {
      return city.first;
   }

   int& zipcode(type& city)
   {
      return city.second;
   }
}

int main()
{
   city::type c("new york", 10001);
   std::string n = city::name(c);
   int z = city::zipcode(c);
}
查看更多
一夜七次
3楼-- · 2019-01-22 13:20

I think you should really introduce new types here. I am totally on stl's side in terms of having a pair class, but this is the exact reason why java people argue they don't want to have a pair class and you should always introduce new types for your piar-like types.

The good thing about the stl solution is that you can use the gerneric class pair, but you can introduce new types / classes whenever you really want members to be named in a different way than first/second. On top of that introducing new classes gives you the freedom of easily adding a thrid member if it should ever become necessary.

查看更多
太酷不给撩
4楼-- · 2019-01-22 13:23

I guess elaborating on

struct City : public std::pair<string, int> {
  string& name() { return first; }
  const string& name() const { return first; }
  int& zip() { return second; }
  int zip() const { return second; }
};

is the closest you get to what youre looking for, althrough struct City { string name; int zipcode; } seems perfectly fine.

查看更多
贼婆χ
5楼-- · 2019-01-22 13:23

You'll be happy to know that the ranges proposal also includes a something called tagged_pair, so that your:

struct city { string name; int zipcode; };

can also be written as:

using city = tagged_pair<tag::name(std::string), tag::zipcode(int)>;

city c{"Chicago", 60654};
std::cout << c.name() << " is at zipcode " << c.zipcode() << '\n';

Of course this can also be used in the return type directly as normal:

tagged_pair<tag::min(int), tag::max(int)> get_range() {
     return {0, 100};
}

auto score_range = get_range();
std::cout << "From " << score_range.min() << " to " << score_range.max();
查看更多
做自己的国王
6楼-- · 2019-01-22 13:29

Since std::pair is commonly used for storing entries in std::map containers, you might want to look at tagged elements in Boost Bimap.

Synopsis:

#include <boost/bimap/bimap.hpp>
#include <string>
#include <iostream>

struct name {}; // Tag for the default 'first' member
struct zipcode {}; // Tag for the default 'second' member

int main()
{
    using namespace boost::bimaps;
    typedef bimap <tagged<std::string, name>, tagged<int, zipcode> > Cities;
    typedef Cities::value_type registration;

    Cities cities;
    cities.insert(registration("Amsterdam", 20));
    cities.insert(registration("Rotterdam", 10));

    // ...
    std::string cityName;
    std::cin >> cityName;

    Cities::map_by<name>::const_iterator id_iter = cities.by<name>().find(cityName);
    if( id_iter != cities.by<name>().end() )
    {
        std::cout << "name: " << id_iter->get<name>() << std::endl
                  << "zip: " << id_iter->get<zipcode>()   << std::endl;
    }

    return 0;
}

Note that bimaps can transparently emulate std::map or other associative container types without performance cost; They just are more flexible. In this particular example, the definition would most likely best be changed into something like:

typedef bimap <tagged<std::string, name>, multiset_of<tagged<int, zipcode> > > Cities;
typedef Cities::value_type registration;

Cities cities;
cities.insert(registration("Amsterdam", 20));
cities.insert(registration("Rotterdam", 10));
cities.insert(registration("Rotterdam", 11));

I invite you to wander around the documentation for Boost Bimap to get the full picture

查看更多
手持菜刀,她持情操
7楼-- · 2019-01-22 13:30

perhaps you can inherit your own pair class from pair and set two references called name and zipcode in your constructor ( just be sure to implement all constructors you will use )

查看更多
登录 后发表回答