Что такое std::vector руководства по дедукции в С++ 17?
Я читал о руководствах по вычитанию для std::vector
с помощью cppreference.
Пример:
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4};
std::vector x{v.begin(), v.end()}; // uses explicit deduction guide
}
Итак, у меня есть некоторые вопросы по этому поводу:
-
Что такое std::vector
руководства по дедукции в С++ 17?
-
Почему и когда нам нужен векторный вывод?
-
Здесь Is x
a std::vector<int>
или std::vector<std::vector<int>>
?
Ответы
Ответ 1
Что такое std::vector
руководства по дедукции в С++ 17?
Пользовательское руководство по вычету позволяет пользователям решать, как вывод аргумента шаблона шаблона выводит аргументы для класса шаблона из его аргументов конструктора. В этом случае кажется, что std::vector
имеет явное руководство, которое должно сделать конструкцию из пары итераторов более интуитивной.
Почему и когда нам нужен векторный вывод?
Мы не "нуждаемся" в нем, но он полезен в общем коде и в коде, который очень очевиден (т.е. код, явно указывающий аргументы шаблона, не полезен для читателя).
Является x
a vector<int>
или vector<vector<int>>
?
Вот хороший трюк, чтобы быстро разобраться в этом - напишите объявление функции шаблона без определения и попытайтесь вызвать его. Компилятор распечатает тип переданных аргументов. Вот что печатает g++ 8:
template <typename>
void foo();
// ...
foo(x);
ошибка: нет соответствующей функции для вызова foo(std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> > ...
Как видно из сообщения об ошибке, x
выводится std::vector<std::vector<int>::iterator>
.
Почему?
std::vector
руководства по выходу доступны на cppreference.org. Стандарт, как представляется, определяет явное руководство по вычитанию из пары итераторов:
![введите описание изображения здесь]()
Поведение, встречающееся в g++ 8, кажется правильным, поскольку (цитируя Rakete1111)
-
разрешение перегрузки предпочитает конструктор с std::initializer_list
с расширенным списком инициализаторов
-
другие конструкторы рассматриваются только после того, как все конструкторы std::initializer_list
были опробованы в list-initialization
std:vector<std::vector<int>::iterator>
, следовательно, является правильным результатом при использовании инициализации списка. живой пример
При построении x
с std::vector x(v.begin(), v.end())
вместо этого будет выведено int
. живой пример
Ответ 2
Здесь Is x
a std::vector<int>
или std::vector<std::vector<int>>
?
Другие ответы здесь касаются других вопросов, но я хотел бы рассмотреть этот вопрос немного более основательно. Когда мы делаем вывод аргумента шаблона класса, синтезируем кучу шаблонов функций от конструкторов, а затем еще несколько из руководства по дедукции и выполнить разрешение перегрузки для определения правильных параметров шаблона.
Существует немало конструкторов для std::vector<T,A>
, но большинство из них не упоминает T
, что сделает T
не выводимый контекст и, следовательно, не жизнеспособный вариант в этой перегрузке. Если мы предварительно подрезаем набор, чтобы использовать только те, которые могут быть жизнеспособными:
template <class T> vector<T> __f(size_t, T const& ); // #2
template <class T> vector<T> __f(vector<T> const& ); // #5
template <class T> vector<T> __f(vector<T>&& ); // #6, NB this is an rvalue ref
template <class T> vector<T> __f(initializer_list<T> ); // #8
И затем также это руководство по дедукции, которое я также упрощу, сбросив распределитель:
template <class InputIt>
vector<typename std::iterator_traits<InputIt>::value_type> __f(InputIt, InputIt );
Это наши 5 кандидатов, и мы перегружаемся, как будто [dcl.init], вызов через __f({v.begin(), v.end()})
. Поскольку это инициализация списка, мы начинаем с кандидатов initializer_list
, и только если их нет, переходим ли мы к другому кандидатов. В этом случае существует кандидат initializer_list
, который является жизнеспособным (# 8), поэтому мы выбираем его, даже не рассматривая какие-либо другие. Этот кандидат выводит T
как std::vector<int>::iterator
, поэтому мы перезапускаем процесс разрешения перегрузки, чтобы выбрать конструктор для std::vector<std::vector<int>::iterator>
list-initialized с двумя итераторами.
Это, вероятно, не желаемый результат - мы, вероятно, хотели vector<int>
. Решение просто: используйте ()
s:
std::vector x(v.begin(), v.end()); // uses explicit deduction guide
Теперь мы не выполняем инициализацию списка, поэтому кандидат initializer_list
не является жизнеспособным кандидатом. В результате мы выводим vector<int>
через руководство дедукции (единственный жизнеспособный кандидат) и в конечном итоге вызываем его конструктор итератор-пар. Это имеет побочное преимущество, фактически делая комментарий правильным.
Это одно из многих мест, где инициализация с помощью {}
делает что-то дико отличающееся от инициализации с помощью ()
. Некоторые утверждают, что {}
- это равномерная инициализация, - подобные примеры, похоже, противостоят. Мое эмпирическое правило: используйте {}
, когда вы специально сознательно нуждаетесь в поведении, которое предоставляет {}
. ()
в противном случае.
Ответ 3
Что такое std::vector
руководства по дедукции в С++ 17?
Вывод аргумента шаблона шаблона указывает: "Чтобы создать экземпляр шаблона класса, каждый аргумент шаблона должен быть известен, но не каждый аргумент шаблона должен быть указан."
И что локализован для std:vector
, я имею в виду, что std:vector
- это просто класс. Ничего особенного в этом.
Вот руководство по дедукции std::vector
из ссылки:
template< class InputIt,
class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
vector(InputIt, InputIt, Alloc = Alloc())
-> vector<typename std::iterator_traits<InputIt>::value_type, Alloc>;
Если вы не знакомы с синтаксисом, прочитайте Что представляют собой руководства по вычитанию шаблонов в С++ 17?
Почему и когда нам нужен векторный вывод?
Вам нужны справочники, когда вывод типа из аргументов не основан на типе одного из этих аргументов.
Является x a vector<int>
или vector<vector<int>>
?
Ни!
Это an:
std::vector<std::vector<int>::iterator>
Принудительная простая ошибка компиляции (например, назначая число x
) будет раскрывать ее тип):
error: no match for 'operator=' (operand types are 'std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, std::allocator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > > >' and 'int')
PS:
Зачем нам нужна эта инициализация Alloc = Alloc() в этом руководстве?
Это аргумент по умолчанию, который позволяет передать в распределитель. По умолчанию вы не нуждаетесь в двух направляющих.