Руководства по выводу, initializer_list и процесс вывода типа - PullRequest
0 голосов
/ 12 ноября 2018

Рассмотрим следующий код :

#include <initializer_list>
#include <utility>

template<class T>
struct test
{
    test(const std::pair<T, T> &)
    {}
};

template<class T>
test(std::initializer_list<T>) -> test<T>;

int main()
{
    test t{{1, 2}};
}

Я хотел бы понять, почему этот трюк с initializer_list компилируется. Похоже, что сначала {1, 2} обрабатывается как initializer_list, но затем повторно интерпретируется как инициализация списка pair.

Что именно здесь происходит, шаг за шагом?

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

С вашим руководством по выводу мы получаем то, что эквивалентно:

test<int> t{{1, 2}};

Это работает из-за инициализации списка, раздел dcl.init.listp3.7 , который говорит:

В противном случае, если T является типом класса, учитываются конструкторы. перечислены соответствующие конструкторы, и выбран лучший через разрешение перегрузки ([over.match], [over.match.list]). Если сужающее преобразование (см. ниже) требуется для преобразования любого из аргументы, программа плохо сформирована. [Пример:

struct S {
  S(std::initializer_list<double>); // #1
  S(std::initializer_list<int>);    // #2
  S();                              // #3
  // ...
};
S s1 = { 1.0, 2.0, 3.0 };           // invoke #1
S s2 = { 1, 2, 3 };                 // invoke #2
S s3 = { };                         // invoke #3

- конец примера] [Пример:

struct Map {
  Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};

- конец примера] [Пример:

struct S {
  // no initializer-list constructors
  S(int, double, double);           // #1
  S();                              // #2
  // ...
};
S s1 = { 1, 2, 3.0 };               // OK: invoke #1
S s2 { 1.0, 2, 3 };                 // error: narrowing
S s3 { };                           // OK: invoke #2

- конец примера]

В противном случае у нас есть не выводимый контекст

0 голосов
/ 12 ноября 2018

Компилируется, потому что так работают руководства по выводу шаблонов классов.

Направляющие вычета являются гипотетическими конструкторами типа. Они на самом деле не существуют. Их единственная цель - определить, как вывести параметры шаблона класса.

Как только вычет сделан, фактический код C ++ вступает во владение с определенным экземпляром test. Поэтому вместо test t{{1, 2}}; компилятор ведет себя так, как если бы вы сказали test<int> t{{1, 2}};.

test<int> имеет конструктор, который принимает pair<int, int>, который может соответствовать значениям в списке braced-init-list, так что это то, что вызывается.

Такого рода действия были сделаны, чтобы агрегаты могли участвовать в выводе аргументов шаблона класса. Агрегаты не имеют пользовательских конструкторов, поэтому, если руководства по выводам были ограничены только реальными конструкторами, агрегаты не могли работать.

Итак, у нас есть руководство по выводу шаблона класса для std::array:

template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;

Это позволяет std::array arr = {2, 4, 6, 7}; работать. Он выводит как аргумент шаблона, так и длину из руководства, но поскольку руководство не является конструктором, array остается агрегатом.

...