Почему использование унифицированного синтаксиса инициализатора приводит к поведению, отличному от «старого» стиля ()? - PullRequest
7 голосов
/ 26 мая 2019

Я получаю разные результаты, если пытаюсь использовать единый инициализатор для std::set.

Пример:

int main()
{
    std::array a {1,2,3,4};
    std::set<int> s1 {a.begin(), a.end()};
    std::set      s2 {a.begin(), a.end()};
    std::set      s3 (a.begin(), a.end());

    for(auto& i: s1) { std::cout << i << "\n"; }
    std::cout << "####" << std::endl;
    for(auto& i: s2) { std::cout << i << "\n"; }
    std::cout << "####" << std::endl;
    for(auto& i: s3) { std::cout << i << "\n"; }
}

Результат:

1   
2   
3   
4   
####
0x7ffecf9d12e0
0x7ffecf9d12f0
####
1   
2   
3   
4  

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

1 Ответ

7 голосов
/ 26 мая 2019

Краткий ответ

Для s2 используется синтаксис фигурной скобки, а {a.begin(), a.end()} считается initializer_list из std::array<int>::iterator с. Следовательно, s2 - это набор итераторов.

Для s3 используется синтаксис скобок, и выбирается конструктор итератора. s3 является набором int с и инициализируется из диапазона [a.begin(), a.end()).

Длинный ответ

За [set.overview] , у нас есть два руководства по выводам, относящиеся здесь:

template<class InputIterator,
         class Compare = less<typename iterator_traits<InputIterator>::value_type>,
         class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
  set(InputIterator, InputIterator,
      Compare = Compare(), Allocator = Allocator())
    -> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;

и

template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
  set(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
    -> set<Key, Compare, Allocator>;                                                

За [over.match.class.deduct] / 1 :

Формируется набор функций и шаблонов функций, содержащий:

  • [...]

  • (1.4) Для каждой инструкции по удержанию , функции или шаблона функции со следующими свойствами:

    • Параметры шаблона, если таковые имеются, и параметры функции соответствуют руководству по дедукции .

    • Тип возврата: simple-template-id из руководства по удержанию .

В этом случае синтезированные функции и шаблоны функций для вышеупомянутых руководств по выводам, соответственно,

template<class InputIterator,
         class Compare = less<typename iterator_traits<InputIterator>::value_type>,
         class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
auto __func1(InputIterator, InputIterator,
            Compare = Compare(), Allocator = Allocator())
  -> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;

и

template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
auto __func2(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
  -> set<Key, Compare, Allocator>; 

(Я использовал двойные подчеркивания, чтобы показать, что эти имена синтезированы и не доступны иным образом.)


За [over.match.class.deduct] / 2 :

Инициализация и разрешение перегрузки выполняются, как описано в [dcl.init] и [over.match.ctor], [over.match.copy] или [over.match.list] (в зависимости от типа инициализации выполняется) для объекта гипотетического типа класса, где выбранные функции и шаблоны функций считаются конструкторы этого типа класса с целью формирования перегрузки установить, и инициализатор предоставляется контекстом, в котором класс вычет аргумента шаблона был выполнен. Каждый такой понятийный конструктор считается явным, если функция или функция шаблон был сгенерирован из конструктора или deduction-guide , который был объявлен explicit. Все такие условные конструкторы считаются быть публичными членами гипотетического типа класса.

Гипотетический тип класса выглядит следующим образом:

class __hypothetical {
public:
  // ...

  // #1
  template<class InputIterator,
           class Compare = less<typename iterator_traits<InputIterator>::value_type>,
           class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
  __hypothetical(InputIterator, InputIterator,
                 Compare = Compare(), Allocator = Allocator())
    -> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;

  // #2
  template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
  __hypothetical(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
    -> set<Key, Compare, Allocator>;

  // ...
};

Для декларации s2,

std::set s2 {a.begin(), a.end()};

Разрешение перегрузки выполняется как в

__hypothetical __hyp{a.begin(), a.end()}; // braces

Таким образом, [over.match.list] входит.

[...] разрешение перегрузки выбирает конструктор в два этапа:

  • Первоначально функции-кандидаты являются конструкторами списка инициализаторов ([dcl.init.list]) класса T и списка аргументов. состоит из списка инициализаторов в качестве одного аргумента.

  • [...]

Конструктор # 2 является конструктором списка инициализаторов . Вывод аргумента шаблона функции дает

Key = std::array<int>::iterator

Таким образом, выведенный тип s2 равен

std::set<std::array<int>::iterator>

Объявление s2 эквивалентно

std::set<std::array<int>::iterator> s2 {a.begin(), a.end()};

Следовательно, s2 - это набор итераторов, состоящий из двух элементов: a.begin() и a.end(). В вашем случае std::array<int>::iterator, вероятно, int*, а a.begin() и a.end() сериализуются как 0x7ffecf9d12e0 и 0x7ffecf9d12f0, соответственно.


Для s3 разрешение перегрузки выполняется как в

__hypothetical __hyp(a.begin(), a.end()); // parentheses

Это прямая инициализация и относится к области действия [pver.match.ctor] . Конструктор initializer_list не имеет значения, и вместо него выбирается Конструктор # 1. Вывод аргумента шаблона функции дает

InputIterator = std::array<int>::iterator

Таким образом, выведенный тип s3 равен

set<iterator_traits<std::array<int>::iterator>::value_type>

Что такое set<int>. Следовательно, объявление s3 эквивалентно

std::set<int> s3 (a.begin(), a.end());

s3 - это набор int с, который инициализируется из диапазона [a.begin(), a.end()) & mdash; четыре элемента 1, 2, 3, 4, что объясняет вывод.

...