Каковы хорошие варианты использования кортежей в С++ 11?
Каковы хорошие варианты использования кортежей в С++ 11? Например, у меня есть функция, которая определяет локальную структуру следующим образом:
template<typename T, typename CmpF, typename LessF>
void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less)
{
struct SI
{
int l, r, w;
SI() {}
SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {}
} stack[40];
// etc
Я рассматривал возможность замены структуры SI
на std::tuple<int,int,int>
, которая является гораздо более коротким объявлением с удобными конструкторами и операторами, уже предопределенными, но со следующими недостатками:
- Элементы Tuple скрыты в неясных, определенных реализацией структурах. Несмотря на то, что Visual Studio интерпретирует и показывает их содержимое красиво, я все еще не могу установить условные точки останова, которые зависят от значения элементов кортежа.
- Доступ к отдельным полям кортежа (
get<0>(some_tuple)
) гораздо более подробный, чем доступ к элементам структуры (s.l
).
- Доступ к полям по имени гораздо более информативен (и короче!), чем числовым индексом.
Последние две точки несколько рассматриваются функцией tie
. Учитывая эти недостатки, что будет хорошим вариантом использования для кортежей?
ОБНОВЛЕНИЕ. Оказывается, что отладчик VS2010 SP1 не может отображать содержимое следующего массива std::tuple<int, int, int> stack[40]
, но он отлично работает, когда он кодируется с помощью структуры. Таким образом, решение в основном не вызывает затруднений: если вам когда-либо придется проверять свои значения, используйте struct [esp. важно с такими отладчиками, как GDB].
Ответы
Ответ 1
Ну, imho, самая важная часть - это общий код. Написание генерического кода, который работает на всех типах структур, намного сложнее, чем писать дженерики, которые работают с кортежами. Например, функция std::tie
, которую вы упомянули, будет очень трудно сделать для структур.
это позволяет делать такие вещи:
- Параметры функции сохранения для отложенного выполнения (например, этот вопрос)
- Возвратить несколько параметров без громоздкой (un) упаковки с помощью
std::tie
- Объединить (не одинаковые) наборы данных (например, от параллельного выполнения), это можно сделать просто как
std::tuple_cat
.
Дело в том, что это не останавливается на этих целях, люди могут расширяться в этом списке и писать общие функции, основанные на кортежах, которые гораздо сложнее делать с структурами. Кто знает, может быть, завтра кто-то найдет блестящее применение для сериализации.
Ответ 2
Это простой способ вернуть несколько значений из функции;
std::tuple<int,int> fun();
Значения результата можно использовать элегантно следующим образом:
int a;
int b;
std::tie(a,b)=fun();
Ответ 3
Я думаю, что большинство из них для tuple
происходит от std::tie
:
bool MyStruct::operator<(MyStruct const &o) const
{
return std::tie(a, b, c) < std::tie(o.a, o.b, o.c);
}
Наряду со многими другими примерами в ответах здесь. Однако я считаю этот пример наиболее полезным, поскольку он сэкономит много сил, как он был на С++ 03.
Ответ 4
Вы когда-нибудь использовали std::pair
? Многие из мест, которые вы использовали бы std::tuple
, схожи, но не ограничиваются точно двумя значениями.
Недостатки, которые вы перечисляете для кортежей, также применимы к std:: pair, иногда вам нужен более выразительный тип с лучшими именами для его членов, чем first
и second
, но иногда вам это не нужно. То же самое относится к кортежам.
Ответ 5
Я думаю, что нет ничего хорошего для кортежей за пределами деталей реализации некоторой общей функции библиотеки.
(возможно) сохранение при вводе не компенсирует потери в самодокументирующихся свойствах полученного кода.
Подстановка кортежей для структур, которые просто убирают значащее имя для поля, заменяя имя поля "числом" (точно так же, как непродуманное понятие пары std::).
Возвращение нескольких значений с помощью кортежей гораздо менее самодокументируется, чем альтернативы - возврат именованных типов или использование названных ссылок. Без этого самодокументирования легко путать порядок возвращаемых значений, если они взаимно конвертируемы.
Ответ 6
Реальные случаи использования - это ситуации, когда у вас есть неузнаваемые элементы - вариативные шаблоны и лямбда-функции. В обеих ситуациях вы можете иметь неназванные элементы с неизвестными типами, и таким образом единственным способом их хранения является структура с неназванными элементами: std:: tuple. В любой другой ситуации у вас есть известное количество именных элементов с известными типами и, таким образом, может использовать обычную структуру, которая является лучшим ответом в 99% случаев.
Например, вы не должны использовать std:: tuple, чтобы иметь "множественные возвращения" от обычных функций или шаблонов с фиксированным числом общих входов. Используйте для этого реальную структуру. Реальный объект FAR более "общий", чем std:: tuple cookie-cutter, потому что вы можете дать реальный объект буквально любому интерфейсу. Это также даст вам гораздо больше безопасности и гибкости в публичных библиотеках.
Просто сравните эти 2 функции-члены класса:
std::tuple<double, double, double> GetLocation() const; // x, y, z
GeoCoordinate GetLocation() const;
С реальным объектом "геокоордината" я могу предоставить оператор bool(), который возвращает false, если родительский объект не имел местоположения. Через своих API-интерфейсов пользователи могут получить места x, y, z. Но вот большая вещь - если я решила сделать GeoCoordinate 4D, добавив поле времени через 6 месяцев, текущий код пользователя не сломается. Я не могу сделать это с помощью версии std:: tuple.
Ответ 7
Взаимодействие с другими языками программирования, использующими кортежи, и возврат нескольких значений без необходимости вызова вызывающих абонентов каких-либо дополнительных типов. Это первые два, которые приходят мне на ум.
Ответ 8
Я не могу комментировать ответ mirk, поэтому мне придется дать отдельный ответ:
Я думаю, что кортежи были добавлены к стандарту и для программирования функционального стиля. Например, в то время как код типа
void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3)
{
// whatever
}
вездесущ в традиционном С++, потому что это единственный способ получить несколько объектов, возвращаемых функцией, это мерзость для функционального программирования. Теперь вы можете написать
tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input)
{
// whatever
return tuple<MyClass, MyClass, MyClass>(output1, output2, output3);
}
Таким образом, есть возможность избежать побочных эффектов и изменчивости, чтобы обеспечить конвейерную обработку и, в то же время, сохранить смысловую силу вашей функции.