Подтвердить что ты не робот

Что такое 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>>?

4b9b3361

Ответ 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() в этом руководстве?

Это аргумент по умолчанию, который позволяет передать в распределитель. По умолчанию вы не нуждаетесь в двух направляющих.