Есть ли удобный способ обернуть std :: pair как новый тип?

Часто я использую std :: pair для определения логических группировок двух связанных величин в качестве аргументов функции / возвращаемых значений. Некоторые примеры: строка / столбец, тег / значение и т. Д.

Часто мне действительно нужно использовать свой собственный класс, а не просто использовать std :: pair. Довольно легко увидеть, когда что-то начинает ломаться - когда код засоряется make_pair, во-первых, и во-вторых, очень трудно вспомнить, что есть что - std::pair<int, int> передает меньше смысла, чем тип Position.

Что вы нашли, это лучший способ обернуть функциональность std :: pair в тип, который передает реальный смысл?

Вот некоторые вещи, которые я рассмотрел:

typedef std::pair<int, int> Position;

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

struct Position : public std::pair<int, int>
{
    typedef std::pair<int, int> Base;
    Position() : Base() {}
    Position(const Position &x) : Base(x) {}
    Position(int a, int b) : Base(a, b) {}

    int &row() { return first; }
    const int &row() const { return first; }

    int &col() { return second; }
    const int &col() const { return second; }
};

Это лучше, поскольку мы можем получить доступ к переменным через достаточно информативное имя. Проблема здесь в том, что вы все еще можете получить доступ к первому и второму, поэтому утечка абстракции не вызывает затруднений. Кроме того, доступ к простым переменным через функции раздражает синтаксис.

Следующий очевидный шаг - сделать наследование закрытым:

struct Position : private std::pair<int, int>
{
    typedef std::pair<int, int> Base;
    Position() {}
    Position(const Position &x) : Base(x) {}
    Position(int a, int b) : Base(a, b) {}

    int &row() { return first; }
    const int &row() const { return first; }

    int &col() { return second; }
    const int &col() const { return second; }

    bool operator<(const Position &x) const { return Base(*this) < Base(x); }
    // other forwarding operators as needed...
};

Итак, теперь, по крайней мере, мы избавились от доступа к первому и второму, но теперь возникает новая проблема. Когда мы хотим сохранить тип в std :: set, у нас теперь нет доступа к перегрузке оператора ‹, поскольку у нас нет доступа к first и second. Это означает, что мы должны определить функцию пересылки для каждой перегрузки оператора, которую мы хотим. Для меня это обычно ==,! = И ‹, но могут быть и другие, которые мне нужны. Да, я знаю, что мне, вероятно, не следует перегружать оператор ‹просто для того, чтобы вставить его в ассоциативный контейнер, но это делает все чертовски простым ... И определение этих операторов для каждого нового типа - это боль, и мы ВСЕ ЕЩЕ должны получить доступ через функции . Мы можем это исправить:

struct Position
{
    Position() {}
    Position(const Position &x) : row(x.row), col(x.col) {}
    Position(int row, int col) : row(row), col(col) {}

    int row, col;
};
bool operator<(const Position &a, const Position &b)
{
    return a.row < b.row || (!(b.row < a.row) && a.col < b.col);
}
// more overloads as needed

Итак, теперь у нас есть простой доступ к переменным, но теперь определение перегруженных операторов становится еще более сложной задачей, потому что вместо того, чтобы просто пересылать их реализации пары, мы фактически должны каждый раз заново реализовывать их ...

Есть ли какие-то решения, которые я упустил из виду, которые упрощают задачу без недостатков? Если нет, что бы вы предпочли?


person Greg Rogers    schedule 14.10.2008    source источник


Ответы (8)


Это то, что было сделано Boost.Tuple за.

Но вам, вероятно, сейчас следует использовать std :: tuple ...

person Ferruccio    schedule 14.10.2008
comment
Я мог бы, возможно, сделать перечисление для аргумента get и обернуть его таким же образом, но похоже, что это слишком сильно загрязнит область пространства имен, особенно если бы мне были нужны похожие типы. - person Greg Rogers; 14.10.2008
comment
Что, если вы поместите такие объекты в их собственное пространство имен? Держите загрязнения под контролем. - person Ferruccio; 14.10.2008
comment
Я полагаю, но создание отдельных пространств имен только для отдельных типов приведет к загрязнению кода, использующего их. - person Greg Rogers; 14.10.2008

Коллега указал мне на два возможных решения:

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

Использование макроса для генерации кода, необходимого для различных операторов. Таким образом, мне не нужно было бы явно писать что-либо на уровне определения, просто сделайте что-то вроде DEFINE_PAIR_TYPE(Position, int, int, row, col);. Это, вероятно, ближе всего к тому, что я ищу, но все же это кажется злом по сравнению с некоторыми решениями, представленными другими.

person Greg Rogers    schedule 14.10.2008

Также имеется библиотека Boost :: Operators. для автоматической генерации кода оператора. Это похоже на библиотеку SGI, которая Мартин Йорк предложил, но может быть более портативным.

person Head Geek    schedule 15.10.2008

Вы по-прежнему можете повторно использовать функцию pair, переадресовав ей:

bool operator< ( const Position &a, const Position &b ) 
{
    return
        std::make_pair( a.row, a.col ) < std::make_pair( b.row, b.col );
}

Хотя вы все равно делаете это для всех необходимых вам операций ...

person xtofl    schedule 14.10.2008

Вы можете использовать некоторые стандартные служебные шаблоны, которые помогают определять операторы отношения.

#include ‹utility›

http://www.sgi.com/tech/stl/operators.html

Требования к типам

Требование для оператора! = Состоит в том, что x == y является допустимым выражением
Требование для оператора> состоит в том, чтобы y ‹x было допустимым выражением
Требование для оператора‹ = состоит в том, что y ‹x является допустимым выражение
Требование для оператора> = состоит в том, чтобы x ‹y было допустимым выражением

Таким образом, в основном он автоматически генерирует другие операторы, которые дают ‹и == все, что вам нужно сделать, это включить ‹utility›

person Martin York    schedule 14.10.2008
comment
Проблема в том, что мне пришлось бы добавить в код using пространство имен std :: rel_ops ... Возможно, это не так уж и плохо, но я бы хотел избежать этого. Это также означает, что мне все еще нужно определить operator ‹и operator == - person Greg Rogers; 17.10.2008
comment
Если вы используете стандартную библиотеку C ++, отдайте предпочтение документации по стандартной библиотеке C ++. - person Lightness Races in Orbit; 22.02.2011

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

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

struct MyStruct
{
    std::string var1;
    std::string var2;
    bool var3;

    struct less : std::binary_function<struct MyStruct, struct MyStruct, bool>
    {
        bool operator() (const struct MyStruct& s1, const struct MyStruct& s2) const
            { if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; }
    };
};
typedef std::set<struct MyStruct, MyStruct::less> MySet;

или поместите их в определение класса

bool operator==(const MyStruct& rhs) const 
    { return var1 == rhs.var1 && var2 == rhs.var2 && var3 == rhs.var3; };
bool operator<(const MyStruct& a2) const  
    { if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; };

Лучшая причина заключается в том, что это легко понять, их можно легко вставить в определение класса, и их легко расширить, если вы обнаружите, что позже вам понадобятся дополнительные переменные. Я бы никогда не стал пытаться перегрузить std :: pair, когда есть гораздо более простое решение.

person gbjbaanb    schedule 15.10.2008

Не используйте это.

Я ненавижу std :: pair именно по этой причине. Вы никогда не знаете, что есть что, и поскольку доступ к первому и второму является публичным, вы также не можете обеспечить выполнение контрактов.

Но ведь это дело вкуса.

person Tobias    schedule 09.06.2009

К сожалению, strong typedefs не поможет его в C ++ 0x ему присвоена классификация Не готов к C ++ 0x, но открыт для повторной отправки в будущем.

person Motti    schedule 28.10.2008