(Re) с именем std:: pair members
Вместо записи town->first
я хотел бы написать town->name
. Inline named accessors (Переименование первого и второго итератора карт и Именованные элементы std:: pair) - лучшие решения, которые я нашел до сих пор. Моя проблема с именованными аксессуарами - это потеря безопасности типа:
pair<int,double>
может относиться к struct { int index; double value; }
или к struct { int population; double avg_temp; }
. Может ли кто-нибудь предложить простой подход, возможно, нечто похожее на черты?
Я часто хочу вернуть пару или кортеж из функции, и довольно утомительно вводить новый тип типа struct city { string name; int zipcode; }
и его ctor каждый раз. Я очень рад узнать о boost и С++ 0x, но мне нужно чистое решение С++ 03 без повышения.
Обновление
Re andrewdski question: yes, (гипотетический) синтаксис вроде pair<int=index, double=value>
, который создавал бы отдельный тип из pair<int=population, double=avg_temp>
, соответствовал бы вашему требованию. Я даже не возражаю, чтобы реализовать собственный шаблон шаблона пары/кортежа ONCE и просто передать ему аргумент шаблона "свойства признаков", когда мне нужен новый тип. Я понятия не имею, как выглядят "черты имени". Возможно, это невозможно.
Ответы
Ответ 1
Я не вижу, как вы можете сделать лучше, чем
struct city { string name; int zipcode; };
Там нет ничего существенного. Вам нужны типы двух участников, весь ваш вопрос основан на предоставлении имен двум членам, и вы хотите, чтобы он был уникальным типом.
Вы знаете об объединении синтаксиса инициализации, правильно? Вам не нужен конструктор или деструктор, предоставленные компилятором просто отлично.
Пример: http://ideone.com/IPCuw
Тип безопасности требует, чтобы вы вводили новые типы, иначе pair<string, int>
неоднозначно между (именем, zipcode) и (совокупность, темп).
В С++ 03 возврат нового кортежа требует либо:
city retval = { "name", zipcode };
return retval;
или написать конструктор удобства:
city::city( std::string newName, int newZip ) : name(newName), zipcode(newZip) {}
чтобы получить
return city("name", zipcode);
С С++ 0x, однако, вам будет разрешено писать
return { "name", zipcode };
и не требуется конструктор, определенный пользователем.
Ответ 2
Хотя это и не идеально, можно использовать тегированные данные:
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);
}
Но как говорит Бен Фойгт: struct city { string name; int zipcode; };
всегда будет лучше.
EDIT: Шаблоны, вероятно, являются излишним, вместо этого вы можете использовать свободные функции в пространстве имен. Это все еще не решает проблемы безопасности типа, поскольку любой std::pair<T1, T2>
является тем же самым типом, что и любой другой 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
Так как std::pair
обычно используется для хранения записей в контейнерах std::map
, вы можете посмотреть тегированные элементы в Boost Bimap.
Описание:
#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;
}
Обратите внимание, что бимапы могут прозрачно эмулировать std::map
или другие типы ассоциативных контейнеров без затрат на производительность; Они просто более гибкие. В этом конкретном примере определение, скорее всего, лучше всего будет изменено на нечто вроде:
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));
Я приглашаю вас бродить по документации для Boost Bimap, чтобы получить полную картину
Ответ 4
Я думаю, что разработка
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; }
};
является самым близким к тому, что вы ищете, а вот struct City { string name; int zipcode; }
выглядит отлично.
Ответ 5
Вы можете использовать операторы-указатели-член. Есть несколько альтернатив. Вот самое простое.
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;
Вы можете поместить аксессоров для одного псевдо-типа в выделенный namespace
, чтобы отделить их от других вещей. Тогда это будет выглядеть как my_map_value.*zip::name
. Но, если вам действительно не нужно использовать pair
, возможно, проще просто определить новый struct
.
Ответ 6
Думаю, вам действительно нужно вводить новые типы здесь. Я полностью согласен с тем, что у меня есть парный класс, но это точная причина, почему люди из java утверждают, что они не хотят иметь парный класс, и вы всегда должны вводить новые типы для ваших типов, подобных пиарам.
Хорошо, что решение stl состоит в том, что вы можете использовать пару класса gerneric, но вы можете вводить новые типы/классы всякий раз, когда вы действительно хотите, чтобы члены были названы по-другому, чем первая/вторая. Помимо того, что введение новых классов дает вам свободу легко добавлять элементарный член, если он когда-нибудь станет необходимым.
Ответ 7
Вы с удовольствием узнаете, что предложение диапазонов также включает в себя что-то под названием tagged_pair
, так что ваш:
struct city { string name; int zipcode; };
также может быть записана как:
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';
Конечно, это также можно использовать в обратном типе как обычно:
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();
Ответ 8
Я придумал макрос Utility_pair
, который можно использовать следующим образом:
Utility_pair(ValidityDateRange,
time_t, startDay,
time_t, endDay
);
Затем, когда вам нужно получить доступ к полям ValidityDateRange
, вы можете сделать это следующим образом:
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());
Это реализация:
#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) \
} \
/***/
Ответ 9
возможно, вы можете наследовать свой собственный парный класс из пары и установить в свой конструктор две ссылки, называемые именем и zipcode (просто убедитесь, что все конструкторы будут использоваться)