(Re)named std::pair members

I don't see how you can possibly do better than

struct city { string name; int zipcode; };

There's nothing non-essential there. You need the types of the two members, your whole question is predicated around giving names to the two members, and you want it to be a unique type.

You do know about aggregate initialization syntax, right? You don't need a constructor or destructor, the compiler-provided ones are just fine.

Example: http://ideone.com/IPCuw


Type safety requires that you introduce new types, otherwise pair<string, int> is ambiguous between (name, zipcode) and (population, temp).

In C++03, returning a new tuple requires either:

city retval = { "name", zipcode };
return retval;

or writing a convenience constructor:

city::city( std::string newName, int newZip ) : name(newName), zipcode(newZip) {}

to get

return city("name", zipcode);

With C++0x, however, you will be allowed to write

return { "name", zipcode };

and no user-defined constructor is necessary.


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

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