可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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.
回答2:
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:
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
回答4:
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:
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:
You can use pointer-to-member operators. There are a few alternatives. Here is the most straightforward.
typedef std::map< zipcode_t, std::string > zipmap_t;
static zipcode_t const (zipmap_t::value_type::*const zipcode)
= &zipmap_t::value_type::first;
static std::string (zipmap_t::value_type::*const zipname)
= &zipmap_t::value_type::second;
// Usage
zipmap_t::value_type my_map_value;
std::string &name = my_map_value.*zipname;
You can put the accessors for one pseudo-type into a dedicated namespace
to separate them from other things. Then it would look like my_map_value.*zip::name
. But, unless you really need to use a pair
, it's probably easier to just define a new struct
.
回答7:
I came up with a Utility_pair
macro that can be used like this:
Utility_pair(ValidityDateRange,
time_t, startDay,
time_t, endDay
);
Then, whenever you need to access the fields of ValidityDateRange
, you can do it like this:
ValidityDateRange r = getTheRangeFromSomewhere();
auto start = r.startDay(); // get the start day
r.endDay() = aNewDay(); // set the end day
r.startDay(aNewDay1()) // set the start and end day in one go.
.endDay(aNewDay2());
This is the implementation:
#include <utility>
#define Utility_pair_member_(pairName, ordinality, type, name) \
const type &name() const { return ordinality; } \
type &name() { return ordinality; } \
pairName &name(const type &m) { ordinality = m; return *this; } \
/***/
#define Utility_pair(pairName, firstMemberType, firstMemberName, secondMemberType, secondMemberName) \
struct pairName: std::pair<firstMemberType, secondMemberType> { \
Utility_pair_member_(pairName, first, firstMemberType, firstMemberName) \
Utility_pair_member_(pairName, second, secondMemberType, secondMemberName) \
} \
/***/
回答8:
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.
回答9:
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 )