Ответ 1
Синтаксис косвенной инициализации с использованием списка с расширенным кодом, который использует ваш код, называется копированием-инициализацией списка.
Процедура разрешения перегрузки, определяющая лучший жизнеспособный конструктор для этого случая, описана в следующем разделе Стандарта С++:
§ 13.3.1.7 Инициализация с помощью инициализации списка
[over.match.list]
Когда объекты неагрегатного типа класса
T
инициализируются по списку (8.5.4), разрешение перегрузки выбирает конструктор в две фазы:- Изначально функции-кандидаты являются конструкторами-списками инициализаторов (8.5.4) класса
T
, а список аргументов состоит из списка инициализаторов как один аргумент.- Если не найден жизнеспособный конструктор списка инициализаторов, разрешение перегрузки выполняется снова, где кандидатные функции являются всеми конструкторами класса
T
, а список аргументов состоит из элементов списка инициализаторов.Если в списке инициализаторов нет элементов, а
T
имеет конструктор по умолчанию, первая фаза опущена. В инициализации списка копий, если выбран явный конструктор, инициализация плохо сформирована. [Примечание. Это отличается от других ситуаций (13.3.1.3, 13.3.1.4), где рассматриваются только конструкторы преобразования для инициализации копирования. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. - конец примечания].
В соответствии с этим конструктор-инициализатор-список (тот, который можно вызывать с единственным аргументом, соответствующим параметру конструктора типа std::initializer_list<T>
), обычно предпочтительнее других конструкторов , но не если доступен конструктор по умолчанию, а список бит-init, используемый для инициализации списка , пуст.
Что здесь важно, набор конструкторов стандартных контейнеров библиотеки изменился между С++ 11 и С++ 14 из-за LWG номер 2193. В случае std::unordered_map
, для нашего анализа нас интересует следующая разница:
С++ 11:
explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
С++ 14:
unordered_map();
explicit unordered_map(size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
Другими словами, существует другой конструктор по умолчанию (тот, который можно вызывать без аргументов) в зависимости от стандартного языка (С++ 11/С++ 14) и, что имеет решающее значение, конструктор по умолчанию в С++ 14 теперь выполнен не explicit
.
Это изменение было введено, чтобы можно было сказать:
std::unordered_map<int,int> m = {};
или
std::unordered_map<int,int> foo()
{
return {};
}
которые семантически эквивалентны вашему коду (передача {}
в качестве аргумента вызова функции для инициализации std::unordered_map<int,int>
).
То есть, в случае библиотеки, совместимой с С++ 11, ошибка ожидается, поскольку выбранный конструктор по умолчанию explicit
, поэтому код плохо сформирован:
explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
В случае библиотеки, совместимой с С++ 14, ошибка не ожидается, так как выбранный (по умолчанию) конструктор не explicit
, а код хорошо сформирован:
unordered_map();
Таким образом, различное поведение, с которым вы сталкиваетесь, связано исключительно с версией libstdС++ и libС++, которые вы используете с различными параметрами компиляторов/компиляторов.
Замена
std::unordered_map
наstd::map
приводит к ошибке. Почему?
Я подозреваю это только потому, что std::map
в используемой версии libstdС++ уже был обновлен для С++ 14.
Замена
foo({})
наfoo({{}})
также заставляет проблему уйти. Почему?
Потому что теперь это инициализация списка-копий {{}}
с непустым braced-init-list (т.е. он содержит один элемент внутри, инициализированный пустым файлом braced-init- list {}
), поэтому применяется правило из первой фазы раздела 13.3.1.7 [over.match.list]/p1 (цитированное ранее), которое предпочитает конструктор-инициализатор-список для других. Этот конструктор не explicit
, следовательно, вызов корректно сформирован.
Замена
{}
непустым списком инициализаций работает как ожидалось во всех случаях. Почему?
То же, что и выше, разрешение перегрузки заканчивается первой фазой § 13.3.1.7 [over.match.list]/p1.