Instead of writing town->first
I would like to write town->name
. Inline named accessors (Renaming first and second of a map iterator and Name
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.
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
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.
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);
}
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.
Probably not worth the extra memory, but if you want to retain the advantage of std::pair
or std::tuple
/ maintain compatibility with an unchangeable API, you can inherit from them and then define references to their members.
Apart from the memory cost, this comes with the giant caveat that STL types generally don't have virtual destructors and can thus cause memory leaks if you attempt to de-allocate using a pointer to the base type:
#include <tuple>
struct PairShadow : public std::pair<int, double> {
using std::pair<int, double>::pair;
PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
int & x = this->first;
double & y = this->second;
};
struct TupleShadow: public std::tuple<int, double> {
using std::tuple<int, double>::tuple;
PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
int & x = std::get<0>(*this);
double & y = std::get<1>(*this);
};
auto main() -> int {
auto twinShadow = PairShadow(1,3.0);
auto tupleShadow = TupleShadow(1,3.0);
return tupleShadow.y;
}
Alternatively, and perhaps preferrably as mentioned by @holyblackcat you could also just define an overload to std::get<int>(PairShadow)
for your struct in many cases as long as you aren't trying to conform to an unchangable API which specifically requires a std::pair
or std::tuple