Что такое std:: векторные руководства по дедукции в C++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 руководства по дедукции в C++17?

  • почему и когда нам нужна векторная дедукция?

  • Здесь x a std::vector<int> или std::vector<std::vector<int>>?

3 ответов


каковы std::vector руководства по дедукции в C++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. Стандарт, похоже, определяет явное руководство по дедукции из итератора пара:

enter image description here

поведение, встречающееся в g++ 8, кажется правильным независимо от того, как (цитирую Rakete1111)

  • разрешение перегрузки предпочитает конструктор с std::initializer_list С рамно список инициализатора

  • другие конструкторы считаются только после того, как все std::initializer_list конструкторы были опробованы в список-инициализации

std:vector<std::vector<int>::iterator> поэтому правильный результат при использовании инициализации списка. видео

при строительстве x С std::vector x(v.begin(), v.end()), int вместо этого будет выведено. видео


Здесь 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 as std::vector<int>::iterator, поэтому мы перезапускаем процесс разрешения перегрузки, чтобы выбрать конструктор для std::vector<std::vector<int>::iterator> list-инициализируется двумя итераторами.

это, вероятно, не желаемый результат - мы, вероятно, хотели vector<int>. Решение Там простое: используйте ()s:

std::vector x(v.begin(), v.end()); // uses explicit deduction guide

теперь мы не делаем инициализацию списка, поэтому initializer_list кандидат не является реальным кандидатом. В результате мы выводим vector<int> через руководство по дедукции (единственный жизнеспособный кандидат) и в конечном итоге вызывает конструктор итераторов-пар. Это имеет побочное преимущество фактически делать комментарий правильный.


это одно из многих мест, где инициализации с {} делает что-то дико отличается от инициализации с (). Некоторые утверждают, что {} является равномерной инициализацией-какие примеры, как это, кажется, противоречат. Мое эмпирическое правило: используйте {} когда вам конкретно, сознательно нужно поведение, которое {} обеспечивает. () иначе.


каковы std::vector руководства по дедукции в C++17?

вычет аргумента шаблона класса указывает: "для создания экземпляра шаблона класса должен быть известен каждый аргумент шаблона, но не каждый аргумент шаблона должен быть указан."

и это локализовано для std:vector, Я имею в виду std:vector это просто класс. Ничего особенного.

здесь std::vector руководство deducation от ref:

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>;

если вы не знакомы с синтаксисом, пожалуйста, прочитайте каковы руководства по вычету шаблонов в C++17?

почему и когда нам нужна векторная дедукция?

вам нужны руководства, когда вывод типа из аргументов не основан на типе одного из этих аргументов.

это x a vector<int> или vector<vector<int>>?

ни!

это Ан:

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

это аргумент по умолчанию, что позволяет передать в распределитель. Дефолт означает, что вам не нужны два проводника.