Подтвердить что ты не робот

Ошибка неявного преобразования из списка инициализаторов

Рассмотрим фрагмент:

#include <unordered_map>

void foo(const std::unordered_map<int,int> &) {}

int main()
{
        foo({});
}

Это не с GCC 4.9.2 с сообщением:

map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’

Тестирование с использованием других реализаций компилятора/библиотеки:

  • GCC < 4.9 принимает это без жалоб,
  • Clang 3.5 с libstdС++ терпит неудачу с похожим сообщением,
  • Clang 3.5 с libС++ принимает это,
  • ICC 15.что-то принимает это (не уверен, какую стандартную библиотеку он использует).

Еще несколько озадачивающих пунктов:

  • замена std::unordered_map на std::map заставляет ошибку уйти,
  • замена foo({}) на foo foo({{}}) также заставляет ошибку уйти.

Кроме того, замена {} непустым списком инициализаторов работает как ожидалось во всех случаях.

Итак, мои основные вопросы:

  • кто здесь? Правильно ли сформирован код?
  • Что делает синтаксис с двойными фигурными фигурными скобками foo({{}}), чтобы сделать ошибку?

EDIT зафиксировал пару опечаток.

4b9b3361

Ответ 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.

Ответ 2

Инициализация списка для ссылок определяется следующим образом: [dcl.init.list]/3:

В противном случае, если T является ссылочным типом, временное присвоение типа ссылающийся на T, является инициализированным списком копий или инициализируется прямым списком, в зависимости от типа инициализации для ссылки, и ссылка привязана к этому временному.

Таким образом, ваш код выходит из строя, потому что

std::unordered_map<int,int> m = {};

не удается. Инициализация списка для этого случая покрывается этой пулей из [dcl.init.list]/3:

В противном случае, если в списке инициализаторов нет элементов, а T - класс type с конструктором по умолчанию, объект инициализируется значением.

Таким образом, конструктор по умолчанию объекта будет вызван 1.
Теперь к решающим бит: В С++ 11 unordered_map имел этот конструктор по умолчанию 2:

explicit unordered_map(size_type n = /* some value */ ,
                       const hasher& hf = hasher(),
                       const key_equal& eql = key_equal(),
                       const allocator_type& a = allocator_type());

Очевидно, что вызов этого конструктора explicit через инициализацию списка копий плохо сформирован [over.match.list]:

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

Так как С++ 14 unordered_map объявляет конструктор по умолчанию, который не явный:

unordered_map();

Таким образом, реализация стандартной библиотеки С++ 14 должна скомпилировать это без проблем. Предположительно libС++ уже обновлен, но libstdС++ отстает.


1) [dcl.init]/7:

Для инициализации объекта типа T означает:
- если T является (возможно, cv-qualit) тип класса (раздел 9) с предоставленным пользователем конструктор (12.1), тогда конструктор по умолчанию для T вызывается [...]

2) [class.ctor]/4:

Конструктор по умолчанию для класса X является конструктором класса X, который можно вызвать без аргумента.