Какие языковые правила позволяют C++11 сделать вывод, что это initializer_list пар?

В С++ 11 кажется допустимым инициализировать std::map<std::string, int> следующим образом:

std::map<std::string, int> myMap = {
     { "One",   1 },
     { "Two",   2 },
     { "Three", 3 }
};

Интуитивно это имеет смысл — инициализатор, заключенный в фигурные скобки, представляет собой список пар строк, а std::map<std::string, int>::value_type — это std::pair<std::string, int> (возможно, с некоторыми уточнениями const.

Однако я не уверен, что понимаю, как здесь работает набор текста. Если мы исключим здесь объявление переменной и оставим только инициализатор, заключенный в фигурные скобки, компилятор не узнает, что он просматривает std::initializer_list<std::pair<std::string, int>>, потому что он не будет знать, что пары в фигурных скобках представляют std::pairs. Поэтому кажется, что компилятор каким-то образом откладывает действие по присвоению типа инициализатору, заключенному в фигурные скобки, до тех пор, пока он не получит достаточно информации о типе из конструктора std::map, чтобы понять, что вложенные фигурные скобки предназначены для пар. Я не помню, чтобы что-то подобное происходило в C++03; насколько мне известно, тип выражения никогда не зависел от его контекста.

Какие языковые правила позволяют правильно компилировать этот код и компилятору определять, какой тип использовать для списка инициализаторов? Я надеюсь на ответы с конкретными ссылками на спецификацию С++ 11, так как очень интересно, что это работает!

Спасибо!


person templatetypedef    schedule 13.07.2014    source источник
comment
Списки, заключенные в фигурные скобки, в C++03 уже были очень специфичны для контекста инициализатора. Здесь нет существенной разницы с вложенным агрегатом C++03 (возможно, std::pair<std::string, int> myArray[]), инициализированным с помощью фигурных скобок, за исключением того, что тип не назван напрямую, он выводится из доступных конструкторов.   -  person Ben Voigt    schedule 13.07.2014
comment
Кстати, операторы неявного преобразования (член operator T()) — это еще одно место, где контекст определяет тип выражения (и используется для разрешения перегрузки).   -  person Ben Voigt    schedule 13.07.2014
comment
braced-init-list не является выражением и не имеет типа. Нет вывода, только разрешение перегрузки среди конструкторов списка инициализаторов (из которых есть только один)   -  person Cubbi    schedule 13.07.2014


Ответы (2)


В выражении

std::map<std::string, int> myMap = {
     { "One",   1 },
     { "Two",   2 },
     { "Three", 3 }
};

справа у вас есть список инициализации в фигурных скобках, где каждый элемент также является списком инициализации в скобках. Первое, что происходит, это то, что рассматривается конструктор списка инициализаторов std::map.

map(initializer_list<value_type>,
    const Compare& = Compare(),
    const Allocator& = Allocator());

map<K, V>::value_type — это typedef для pair<const K, V>, в данном случае pair<const string, int>. Внутренние скобочные списки инициализации могут быть успешно преобразованы в map::value_type, потому что std::pair имеет конструктор, который принимает ссылки на два составляющих его типа, а std::string имеет конструктор неявного преобразования, который принимает char const *.

Таким образом, конструктор списка инициализаторов std::map жизнеспособен, и построение может происходить из вложенных списков инициализации в фигурных скобках.

Соответствующий стандартный язык представлен в §13.3.1.7/1 [over.match.list].

Когда объекты неагрегатного типа класса T инициализируются списком (8.5.4), разрешение перегрузки выбирает конструктор в два этапа:
— первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класс T и список аргументов состоят из списка инициализаторов как одного аргумента.
— Если жизнеспособный конструктор списка инициализаторов не найден, разрешение перегрузки выполняется снова, где функциями-кандидатами являются все конструкторы класса T а список аргументов состоит из элементов списка инициализаторов.

Первая пуля — это то, что приводит к выбору конструктора initializer_list из map для внешнего списка инициализации скобок, а вторая пуля приводит к выбору правильного конструктора pair для внутренних списков инициализации скобок.

person Praetorian    schedule 13.07.2014

Это инициализация списка. Правила находятся в §8.5.4[dcl.init.list]/p3 стандарта:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • Если в списке инициализаторов нет элементов и T является типом класса с конструктором по умолчанию, объект инициализируется значением.
  • В противном случае, если T является агрегатом, выполняется инициализация агрегата (8.5.1). [пример опущен]
  • В противном случае, если T является специализацией std::initializer_list<E>, объект initializer_list создается, как описано ниже, и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5).
  • В противном случае, если T является типом класса, рассматриваются конструкторы. Перечисляются применимые конструкторы, и лучший из них выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. ниже), программа некорректна.
  • [пример и остальные правила опущены]

Обратите внимание, что разрешение перегрузки в этих случаях предпочтет конструкторы std::initializer_list (§13.3.1.7 [over.match.list]).

Таким образом, когда компилятор видит список в фигурных скобках, используемый для инициализации объекта неагрегированного типа класса, отличного от std::initializer_list, он выполнит разрешение перегрузки для выбора соответствующего конструктора, предпочитая конструктор initializer_list, если он существует (как это происходит для std::map).

person T.C.    schedule 13.07.2014