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

Явный конструктор копирования и равномерная инициализация

Явные конструкторы копирования запрещают что-то вроде Foo foo = bar; и применяют использование копии как Foo foo(bar);. Кроме того, явные конструкторы копирования также запрещают возвращать объекты по значению из функции. Однако я попытался заменить инициализацию копии фигурными скобками, например

struct Foo
{
    Foo() = default;
    explicit Foo(const Foo&) = default;
};

int main()
{
    Foo bar;
    Foo foo{bar}; // error here
}

и я получаю ошибку (g++ 5.2)

error: нет соответствующей функции для вызова в Foo:: Foo (Foo &) '

или (clang++)

ошибка: избыточные элементы в инициализаторе структуры

Удаление explicit делает компилируемый код под g++, clang++ все еще терпит неудачу с той же ошибкой (спасибо @Steephen). Что здесь происходит? Является ли равномерная инициализация рассмотренной как конструктор списка инициализаторов (который превосходит все остальные)? Но если это так, почему компиляция программы, когда конструктор копирования не явный?

4b9b3361

Ответ 1

Вы столкнулись с ситуацией, которая была решена с помощью разрешения Основной вопрос 1467 сразу после завершения С++ 14.

Пусть сначала отметим, что класс foo является агрегатом. Ваш код выполняет инициализацию прямого списка для foo. Правила для инициализации списка приведены в [8.5.4p3].

В С++ 14 (цитируя из N4140, рабочий проект, ближайший к опубликованному стандарту), вышеприведенный параграф начинался с:

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

  • Если T является агрегатом, выполняется агрегатная инициализация (8.5.1).

[...]

Итак, если ваш класс является агрегатом, компилятор пытается выполнить агрегатную инициализацию, которая не выполняется.

Это было признано проблемой и зафиксировано в рабочем проекте. Цитируя из текущей версии N4527, вышеупомянутый пункт теперь начинается с:

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

  • Если T - тип класса, а в списке инициализаций есть один элемент типа cv U, где U есть T или класс, полученный из T, объект инициализируется из этого элемента (путем инициализации копирования для инициализация списка копий или прямая инициализация для прямой список инициализации).
  • В противном случае, если T - это массив символов, а в списке инициализаций есть один элемент, который является соответствующим образом типизированным строковым литералом (8.5.2), инициализация выполняется, как описано в этом разделе.
  • В противном случае, если T является агрегатом, выполняется агрегатная инициализация (8.5.1).

[...]

Ваш пример теперь относится к случаю, описанному первой точкой маркера, и foo инициализируется прямым списком с использованием конструктора копии по умолчанию (независимо от того, является ли он explicit, поскольку он имеет прямую инициализацию).

То есть... если компилятор реализует разрешение в отчете о дефектах.

  • GCC 5.2.0 (и 6.0.0 trunk), похоже, делает это, но, похоже, имеет ошибку, связанную с этим explicit.
  • Clang 3.6.0 не делает, но 3.8.0 trunk делает, и делает это правильно (explicit не имеет значения).
  • MSVC 14 делает, но IntelliSense в среде IDE не работает (squiggles под bar - похоже, что компилятор EDG, используемый IntelliSense, также не обновлялся).

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

  • CWG 2137 указывает, что первая пуля в приведенном выше параграфе слишком сильно зашла, применив это исключение ко всем типам классов примечания к выпуску содержат соответствующий пример). Начало пули теперь гласит:
    • Если T - это совокупный класс и [...]
  • Разрешение CWG 1518, содержащееся в документе P0398R0 указывает, что классы, объявляющие конструктор explicit (даже по умолчанию), больше не являются агрегатами.

Это не меняет того факта, что после того, как все изменения будут реализованы, пример в вопросе предназначен для работы с или без explicit; просто стоит знать, что основные механизмы, которые делают его работу, немного изменились.

Обратите внимание, что все эти изменения являются разрешениями отчетов о дефектах, поэтому они должны применяться, когда компиляторы также находятся в режимах С++ 14 и С++ 11.