Почему С++ не разрешает несколько типов в одном объявлении auto?

В стандарте С++ 2011 введено новое ключевое слово auto, которое может использоваться для определения переменных вместо типа, т.е.

auto p=make_pair(1,2.5);                   // pair<int,double>
auto i=std::begin(c), end=std::end(c);     // decltype(std::begin(c))

Во второй строке i и end имеют один и тот же тип, называемый auto. Стандарт не позволяет

auto i=std::begin(container), e=std::end(container), x=*i;

когда x будет другого типа. Мой вопрос: почему стандарт не позволяет эту последнюю строку? Это может быть разрешено путем интерпретации auto не как представляющего некоторый тип, подлежащий декодированию, а как указание на то, что тип любой объявленной переменной auto должен быть выведен из его назначенного значения. Есть ли веская причина для того, чтобы стандарт С++ 11 не выполнял этот подход?

На самом деле существует прецедент для этого, а именно в инструкции инициализации цепей for:

for(auto i=std::begin(c), end=std::end(c), x=*i;  i!=end;  ++i, x+=*i)
{ ... }

когда область переменных i, end и x ограничена циклом for. AFAIK, этого не может быть достигнуто в С++, если эти переменные не имеют общего типа. Правильно ли это? (уродливые трюки для размещения всех типов внутри struct)

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

Ответ 1

Я думаю, что это просто вопрос согласованности с объявлениями не auto.

Это:

auto n = 42, *p = &n;

эквивалентно:

int n = 42, *p = &n;

где типы int и int* выводятся из инициализаторов. В обоих случаях, даже если int и int* являются разными типами, им разрешено находиться в одном объявлении из-за их близкого синтаксического отношения. (По правилу "объявление следует за использованием", что объявления C и С++ почти следуют, вы определяете как n, так и *p как тип int.)

Было бы возможно разрешить несвязанные типы в одном объявлении:

auto n = 42, x = 1.5;

но вышеупомянутое должно быть эквивалентно двум отдельным объявлениям:

int n = 42; double x = 1.5;

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

Даже без auto вы можете определить int и int* в заголовке цикла for:

for (int n = 42, *p = &n; expr1; expr2) { /* ... / }

но вы не смогли бы объявить int и a double вместе. Добавление auto просто не изменило этого.

Вне контекста цикла for, как правило, гораздо лучше использовать отдельные объявления в любом случае. В большинстве случаев толчок многих различных объявлений в цикле for, возможно, является плохим. И для (возможно, редких) случаев, когда вам нужно много объявлений, вы можете просто поставить их выше цикла, что-то вроде этого:

auto i=std::begin(c), end=std::end(c),
for( x=*i;  i!=end;  ++i, x+=*i) {
    // ...
}

добавьте еще один набор { } вокруг всего, если вы хотите ограничить область. (В этом случае вы, вероятно, хотите, чтобы end был const).

Ответ 2

В соответствии с окончательной версией принятого предложения по этой функции N1737 возможная реализация multi-declarator auto выглядит следующим образом: (из раздела 6)

Мы считаем, что можно достичь как согласованной формы, так и последовательное поведение. Мы делаем это, вставляя (для целей экспозиция) промежуточное определение __Deduced_type и применение этот тип последовательно в расширении as-if:

  // Listing 12
 typedef int __Deduced_type; // exposition only
  __Deduced_type a = 1;
 // decltype(a) is int
 __Deduced_type b = 3.14; // decltype(b) is int
  __Deduced_type * c = new float; // error; decltype(c) would be int *

Мы не только достигаем согласованности формы и поведения с помощью такой согласованной формулировки, мы также рассматриваем более сложные ситуации. Например, когда ведущий декларатор включает ptr-operator:

 // Listing 13 
auto * a = new int(1), b = 3.14, * c = new float;

наша формулировка придает семантике as-if объявлено:

// Listing 14 
 typedef int __Deduced_type; // exposition only
 __Deduced_type * a = new int(1); // decltype(a) is int *
 __Deduced_type b = 3.14; // decltype(b) is int
 __Deduced_type * c = new float; // error; decltype(c) would be int *

Как показано в этой возможной реализации, изменение типа будет недействительным и, как таковое, приведет к ошибке.
Эта функция была реализована таким образом, потому что иначе она будет отличаться от других типов объявлений с несколькими переменными.
Я помню, что видел дискуссию о том, разрешать или не менять типы, но я не могу вспомнить, где. IIRC, они решили, что это будет сложнее реализовать, но другая причина может заключаться в том, что она была отброшена, потому что они не могли прийти к консенсусу о том, когда выбирать разные типы (новое поведение) или когда имплицитно конвертировать в выведенный тип первая объявленная переменная (старое поведение).

Ответ 3

Если ссылаться на рабочий проект С++ 2014, тогда стандарт допускает такой код. Вот пример из стандартной черновики

auto x = 5, *y = &x; // OK: auto is int

Я хотел бы добавить, что в вашем примере авто не может быть выведено, потому что тип итератора и тип значения итератора - это два разных типа.