(Re) именованные члены std :: pair - PullRequest
25 голосов
/ 21 мая 2011

Вместо написания town->first я бы хотел написать town->name.Встроенные именованные средства доступа ( Переименование первого и второго итератора карты и Именованные члены std :: pair ) - лучшие решения, которые я нашел до сих пор.Моя проблема с именованными средствами доступа - потеря безопасности типа: pair<int,double> может означать struct { int index; double value; } или struct { int population; double avg_temp; }.Может кто-нибудь предложить простой подход, возможно, что-то похожее на черты?

Я часто хочу вернуть пару или кортеж из функции, и довольно утомительно вводить новый тип, такой как struct city { string name; int zipcode; } и его ctor каждыйвремя.Я очень рад узнать о boost и C ++ 0x, но мне нужно чистое решение C ++ 03 без boost.

Обновление

Вопрос Эндрюдски: да,(гипотетический) синтаксис, такой как pair<int=index, double=value>, который создаст отдельный тип из pair<int=population, double=avg_temp>, будет соответствовать вашим требованиям.Я даже не возражаю против того, чтобы реализовать собственный класс шаблона пара / кортеж ONCE и просто передать ему соответствующий аргумент шаблона «черты имени», когда мне нужен новый тип.Я понятия не имею, как будут выглядеть эти «черты имени».Может быть, это невозможно.

Ответы [ 9 ]

18 голосов
/ 22 мая 2011

Я не понимаю, как вы можете добиться большего успеха, чем

struct city { string name; int zipcode; };

Там нет ничего несущественного. Вам нужны типы двух членов, весь ваш вопрос основан на присвоении имен двум членам, и вы хотите, чтобы это был уникальный тип.

Вы знаете о синтаксисе инициализации агрегата, верно? Вам не нужен конструктор или деструктор, с компилятором все в порядке.

Пример: http://ideone.com/IPCuw


Безопасность типов требует, чтобы вы вводили новые типы, в противном случае pair<string, int> неоднозначен между (имя, почтовый индекс) и (население, температура).

В C ++ 03 для возврата нового кортежа требуется:

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

или написание вспомогательного конструктора:

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

чтобы получить

return city("name", zipcode);

С C ++ 0x, однако, вам будет разрешено писать

return { "name", zipcode };

и пользовательский конструктор не требуется.

5 голосов
/ 22 мая 2011

Хотя и не идеально, можно использовать тегированные данные:

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; }; всегда будет лучше.

РЕДАКТИРОВАТЬ: Возможно, шаблоныэто избыточное убийство, вы могли бы вместо этого использовать свободные функции в пространстве имен.Это все еще не решает проблемы безопасности типов, так как любые 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 голосов
/ 22 мая 2011

Я полагаю, что подробности о

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; } кажется совершенно нормальным.

3 голосов
/ 22 мая 2011

Поскольку 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;
}

Обратите внимание, что bimaps может прозрачно эмулировать 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, чтобы получить полную картину

2 голосов
/ 30 декабря 2015

Вам будет приятно узнать, что предложение по диапазонам также включает в себя нечто, называемое 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();
1 голос
/ 29 июня 2017

Я придумал макрос 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)                   \
    }                                                                                                \
/***/
1 голос
/ 02 января 2013

Вы можете использовать операторы указатель на член. Есть несколько альтернатив. Вот самое простое.

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.

0 голосов
/ 22 мая 2011

Я думаю, вы должны действительно ввести новые типы здесь. Я полностью на стороне stl с точки зрения наличия парного класса, но это точная причина, почему Java-люди утверждают, что не хотят иметь парный класс, и вы всегда должны вводить новые типы для ваших пироподобных типов.

Хорошая вещь в решении stl заключается в том, что вы можете использовать пару классов gerneric, но вы можете вводить новые типы / классы всякий раз, когда вы действительно хотите, чтобы члены именовались не так, как first / second. Вдобавок ко всему, введение новых классов дает вам возможность легко добавлять третий член, если это когда-либо понадобится.

0 голосов
/ 22 мая 2011

возможно, вы можете унаследовать свой собственный класс пары от пары и установить в своем конструкторе две ссылки, называемые name и zipcode (просто убедитесь, что реализованы все используемые вами конструкторы)

...