Шаблоны не всегда предполагают типы списков инициализаторов - PullRequest
11 голосов
/ 09 октября 2011
#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 может определить тип первого вызова bar, а не второго, и есть ли способ исправить это, кроме длинного и некрасивого приведения?

Ответы [ 2 ]

19 голосов
/ 09 октября 2011

GCC в соответствии с C ++ 11 не может определить тип для первых двух вызовов bar. Он предупреждает, потому что он реализует расширение до C ++ 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}) вызывает конструктор копирования / перемещения initializer_list<int>, передавая ему временный initializer_list<int>, связанный с {1, 2, 3}. Этот временный объект будет уничтожен и умрет после завершения инициализации. Когда тот временный объект, который связан со списком, умирает, массив резервных копий, содержащий данные, также будет уничтожен (если перемещение исключено, оно будет жить так же долго, как «v»; это плохо, так как он даже не будет себя вести плохо гарантированно!). Опуская парены, v напрямую связывается со списком, а данные резервного массива уничтожаются только при уничтожении v.

5 голосов
/ 09 октября 2011

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

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

Например:

bar({{0,1}});

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

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

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

...