Почему кортежи C ++ такие странные? - PullRequest
4 голосов
/ 08 июля 2019

Обычно я создаю пользовательский structs при группировании значений разных типов вместе.Обычно это нормально, и я лично считаю доступ к именованному члену более легким для чтения, но я хотел создать более универсальный API.Широко использовав кортежи в других языках, я хотел вернуть значения типа std::tuple, но обнаружил, что их использование в C ++ намного более уродливо, чем в других языках.

Какие инженерные решения были приняты для обеспечения доступа к элементам с использованием целочисленных значенийПараметр шаблона для get выглядит следующим образом?

#include <iostream>
#include <tuple>

using namespace std;

int main()
{
    auto t = make_tuple(1.0, "Two", 3);
    cout << "(" << get<0>(t) << ", " 
                << get<1>(t) << ", " 
                << get<2>(t) << ")\n";
}

Вместо чего-то простого, подобного следующему?

t.get(0)

или

get(t,0)

В чем преимущество?Я вижу только проблемы в этом:

  • Это выглядит очень странно при использовании такого параметра шаблона.Я знаю, что язык шаблонов является полным по Тьюрингу и все такое, но все же ...
  • Это затрудняет индексацию по генерируемым индексам времени выполнения (например, для небольшого индекса с конечным ранжированием я видел код, использующий операторы switch для каждоговозможно) или невозможно, если диапазон слишком велик.

Редактировать: Я принял ответ.Теперь, когда я подумал о том, что нужно знать языку, и когда это нужно знать, я вижу, что это имеет смысл.

Ответы [ 3 ]

8 голосов
/ 08 июля 2019

Второе, что вы сказали:

Это затрудняет индексацию по сгенерированным индексам времени выполнения (например, для небольшого индекса с конечным диапазоном, который я видел в коде, использующем операторы switch для каждой возможности) или невозможного, если диапазон слишком велик.

C ++ - это строго статический типизированный язык, который должен решить тип время компиляции

Так что функция как

template <typename ... Ts>
auto foo (std::tuple<Ts...> const & t, std::size_t index)
 { return get(t, index); }

недопустимо, поскольку возвращаемый тип зависит от значения времени выполнения index.

Принято решение: передать значение индекса как значение времени компиляции, как параметр шаблона.

Как вы знаете, я полагаю, в случае std::array все совершенно иначе: у вас есть get() (метод at(), или также operator[]), который получает значение индекса времени выполнения: в std::array тип значения не зависит от индекса.

3 голосов
/ 08 июля 2019
  • Это выглядит очень странно

Это слабый аргумент. Выглядит субъективно.

Список параметров функции просто не является опцией для значения, которое необходимо во время компиляции.

  • затрудняет индексирование по сгенерированным во время выполнения индексам

Сгенерированные индексы во время выполнения являются трудными независимо, потому что C ++ является статически типизированным языком без отражения во время выполнения (или даже отражения во время компиляции в этом отношении). Рассмотрим следующую программу:

std::tuple<std::vector<C>, int> tuple;
int index = get_at_runtime();
WHATTYPEISTHIS var = get(tuple, index);

Каким должен быть тип возврата get(tuple, index)? Какой тип переменной вы должны инициализировать? Он не может вернуть вектор, так как index может быть 1, и не может вернуть целое число, поскольку index может быть 0. Типы всех переменных известны во время компиляции в C ++.

Конечно, C ++ 17 ввел std::variant, что является потенциальной опцией в этом случае. Кортеж был представлен еще в C ++ 11, и это было невозможно.

Если вам нужна индексация во время выполнения кортежа, вы можете написать собственный шаблон функции get, который принимает кортеж и индекс времени выполнения и возвращает std::variant. Но использование варианта не так просто, как использование типа напрямую. Это стоимость введения типа времени выполнения в статически типизированный язык.

3 голосов
/ 08 июля 2019

«Технические решения» для требования аргумента шаблона в std::get<N> расположены намного глубже, чем вы думаете. Вы просматриваете разницу между статическими и динамическими системами. Я рекомендую прочитать https://en.wikipedia.org/wiki/Type_system,, но вот несколько ключевых моментов:

  • При статической типизации тип переменной / выражения должен быть известен во время компиляции. get(int) метод для std::tuple<int, std::string> не может существовать в этом случае, потому что аргумент get не может быть известен во время компиляции. С другой стороны, поскольку аргументы шаблона должны быть известны во время компиляции, использование их в этом контексте имеет смысл.

  • C ++ также имеет динамическую типизацию в форме полиморфных классов. Они используют информацию о типе времени выполнения (RTTI), которая сопровождается повышением производительности . Обычный вариант использования для std::tuple не требует динамической типизации и поэтому не допускает этого, но C ++ предлагает другие инструменты для такого случая.
    Например, хотя у вас не может быть std::vector, который содержит смесь int и std::string, вы можете полностью иметь std::vector<Widget*>, где IntWidget содержит int, а StringWidget содержит std::string, если оба получены из Widget. Учитывая, скажем,

    struct Widget {
       virtual ~Widget();
       virtual void print();
    };
    

    вы можете вызывать print для каждого элемента вектора, не зная его точного (динамического) типа.

...