Как вложенный список-инициализация передает свои аргументы? - PullRequest
2 голосов
/ 26 мая 2020

При инициализации вектора пар

  std::vector<std::pair<int, std::string>> foo{{1.0, "one"}, {2.0, "two"}};

как я должен интерпретировать построение foo? Насколько я понимаю,

  1. Конструктор вызывается с синтаксисом инициализации в фигурных скобках, поэтому перегрузка vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() ); является настоятельно предпочтительной и выбирается
  2. Параметр шаблона std::initializer_list<T> выводится как std::pair<int, std::string>
  3. Каждый элемент foo - это std::pair. Однако std::pair не принимает перегрузку std::initializer_list.

Я не уверен в шаге 3. Я знаю, что внутренние фигурные скобки нельзя интерпретировать как std::initializer_list, поскольку они неоднородны. Какой механизм в стандарте фактически создает каждый элемент foo? Я подозреваю, что ответ как-то связан с пересылкой аргументов во внутренних фигурных скобках в перегрузку template< class U1, class U2 pair( U1&& x, U2&& y );, но я не знаю, как это называется.

EDIT :

Я полагаю, что более простой способ задать тот же вопрос был бы: Когда кто-то делает

std::map<int, std::string> m = { // nested list-initialization
           {1, "a"},
           {2, {'a', 'b', 'c'} },
           {3, s1}

, как показано в примере cppreference , где в стандарте говорится, что {1, "a"}, {2, {'a', 'b', 'c'} } и {3, s1} каждый перенаправляется конструктору для std::pair<int, std::string>?

Ответы [ 2 ]

1 голос
/ 27 мая 2020

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

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

Первое правило: если есть конструкторы с одним параметром, равным некоторому initializer_list<T>, то в первом раунде разрешения перегрузки будут использоваться только такие конструкторы. считается ( over.match.list ).

Второе правило: для каждого initializer_list<T> кандидата (их может быть больше одного в классе, с разными T в каждом ) проверяется, что каждый инициализатор может быть преобразован в T, и только те кандидаты остаются там, где это работает ( over.ics.list ).

Это второе правило в основном , где берется препятствие для списков инициализаторов без типа и возобновляется анализ наизнанку.

После того, как при разрешении перегрузки решено, что следует использовать конкретный конструктор initializer_list<T>, используется инициализация копирования для инициализации элементов типа T списка инициализаторов .

1 голос
/ 27 мая 2020

Вы путаете два разных понятия:

1) списки инициализаторов

initializer_list<T>: , который в основном используется для инициализации коллекций . В этом случае все члены должны быть одного типа. (не применимо для std::pair)

Пример:

std::vector<int> vec {1, 2, 3, 4, 5};

2) Единая инициализация

Uniform initialization: фигурные скобки используются для создания и инициализации некоторых объектов, таких как структуры, классы (с соответствующим конструктором) и базовые c типы (int, char, et c.).

.

Пример:

struct X { int x; std::string s;}; 
X x{1, "Hi"}; // Not an initializer_list here.

Отметив, что для инициализации std::pair с помощью инициализатора фигурных скобок вам понадобится конструктор, который принимает два элемента, т.е. первый и второй элементы, а не std::initializer_list<T>. Например, на моей машине с установленным VS2015 этот конструктор выглядит так:

template<class _Other1,
    class _Other2,
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value
                    && is_constructible<_Ty2, _Other2>::value>,
    enable_if_t<is_convertible<_Other1, _Ty1>::value
            && is_convertible<_Other2, _Ty2>::value, int> = 0>
    constexpr pair(_Other1&& _Val1, _Other2&& _Val2) // -----> Here the constructor takes 2 input params
        _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
            && is_nothrow_constructible<_Ty2, _Other2>::value))
    : first(_STD forward<_Other1>(_Val1)), // ----> initialize the first 
            second(_STD forward<_Other2>(_Val2)) // ----> initialize the second
    {   // construct from moved values
    }
...