Шаблоны не всегда угадывают типы списков инициализаторов

#include <initializer_list>
#include <utility>

void foo(std::initializer_list<std::pair<int,int>>) {}
template <class T> void bar(T) {}

int main() {
    foo({{0,1}});  //This works
    foo({{0,1},{1,2}});  //This works
    bar({{0,1}});  //This warns
    bar({{0,1},{1,2}});  //This fails
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}}));  //This works
}

Это не компилируется в gcc 4.5.3, оно дает предупреждение для выделенной строки, в которой указано deducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’, и ошибка для выделенной строки, обозначающая no matching function for call to ‘bar(<brace-enclosed initializer list>)’. Почему gcc может вывести тип первого вызова в бар, но не второй, и есть ли способ исправить это, кроме долгого и уродливого литья?

Ответ 1

GCC в соответствии с С++ 11 не может выводить тип для первых двух вызовов bar. Он предупреждает, что он реализует расширение для С++ 11.

В стандарте говорится, что когда аргумент функции при вызове шаблона функции является { ... }, а параметр не является initializer_list<X> (необязательно ссылочным параметром), то тогда тип параметра не может быть выведен значением {...}. Если параметр такой initializer_list<X>, то элементы списка инициализаторов выводятся независимо путем сравнения с X, и каждый из вычетов элементов должен соответствовать.

template<typename T>
void f(initializer_list<T>);

int main() {
  f({1, 2}); // OK
  f({1, {2}}); // OK
  f({{1}, {2}}); // NOT OK
  f({1, 2.0}); // NOT OK
}

В этом примере первое в порядке, а второе - тоже ОК, потому что первый элемент дает тип int, а второй элемент сравнивает {2} с T - этот вывод не может привести к сглаживанию, так как он вывести что-либо, следовательно, второй вызов принимает T как int. Третий не может вывести T любым элементом, следовательно, НЕ ОК. Последний вызов дает противоречивые выводы для двух элементов.

Один из способов сделать эту работу - использовать такой тип, как тип параметра

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) {
  // ...
}

Я должен заметить, что делать std::initializer_list<U>({...}) опасно - лучше удалите те (...) вокруг фигурных скобок. В вашем случае это происходит случайно, но рассмотрите

std::initializer_list<int> v({1, 2, 3});
// oops, now 'v' contains dangling pointers - the backing data array is dead!

Причина в том, что ({1, 2, 3}) вызывает конструктор copy/move initializer_list<int>, передавая ему временный initializer_list<int>, связанный с {1, 2, 3}. Затем этот временный объект будет уничтожен и погибнет при завершении инициализации. Когда этот временный объект, связанный с этим списком, умирает, резервный массив, содержащий данные, также будет уничтожен (если движение будет отменено, оно будет жить до тех пор, пока "v" - это плохо, поскольку оно даже не будет вести себя плохой гарантированно!). Отпуская параны, v напрямую связан с этим списком, а данные массива базы данных уничтожаются только тогда, когда v уничтожается.

Ответ 2

Унифицированная инициализация зависит от того, какой тип инициализируется. {1} может означать много вещей. При применении к int он заполняет его 1. Когда применяется к std::vector<int>, это означает создание одноэлементного vector, с 1 в первом элементе. И так далее.

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

Например:

bar({{0,1}});

Вы ожидаете, что это будет тип std::initializer_list<std::pair<int,int>>. Но как мог компилятор это знать? bar первый параметр - это неограниченный шаблон; это может быть буквально любым типом. Корова могла ли компилятор предположить, что вы имели в виду этот конкретный тип?

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

По всем правам эта строка не должна компилироваться, согласно С++ 11. Он не может определить, каким типом вы должны были быть {...}, поэтому он должен был потерпеть неудачу. Это похоже на ошибку GCC или что-то в этом роде.