В чем разница, если таковая имеется, между инициализацией {x} и '= {x}?

Я пытаюсь понять семантическую разницу между этими двумя способами инициализации:

Foo foo{x};
Foo foo = {x};

Мне интересно узнать разницу в следующих случаях:

  • x имеет тип Foo.
  • Foo имеет конструктор, который принимает аргумент того же типа, что и для x.
  • x не имеет типа Foo, но доступен конструктор преобразования.
  • x не имеет тип Foo, но доступен конструктор преобразования explicit.

В различие я имею в виду, в каждом случае:

  • Концептуально, какие конструкторы вызываются?
  • Какие вызовы конструкторов обычно оптимизируются компилятором?
  • Разрешено ли неявное преобразование?

Ответы

Ответ 1

Foo foo{x};    // 1

Foo foo = {x}; // 2

1 - инициализация прямого списка. 2 - инициализация списка копий.

Предполагая, что Foo является типом класса, тогда в большинстве случаев они делают то же самое, концептуально или иначе, за исключением того, что если явный конструктор выбран во время разрешения перегрузки, то # 2 плохо сформирован. В частности, в отличие от инициализации копирования, инициализация списка экземпляров концептуально не создает временную.

Единственное исключение - это когда x имеет тип Foo или тип, полученный из него. В этом случае # 1 эквивалентно Foo foo(x); (т.е. Прямая инициализация), а # 2 эквивалентно Foo foo = x; (т.е. Копирование-инициализация). Тонкая разница заключается в том, что разрешение перегрузки для # 2 в этом случае рассматривает только неявные конструкторы, а не рассматривает все конструкторы, а затем становится плохо сформированным, если выбран явный конструктор. * Это исключение добавляется разрешением CWG issue 1467, который был принят в ноябре прошлого года.


* Вам нужно написать какой-то довольно извращенный код, чтобы это имело значение. Например:

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

int main() { 
    X x1;
    X x2 = {x1}; // #1
    X x3 = x1;   // #2
}

Pre-CWG1467, строка # 1 плохо сформирована, потому что разрешение перегрузки выбирает X(X&), что составляет explicit. Post-CWG1467, конструктор explicit X(X&) не рассматривается, поэтому используется X(const X&). Обратите внимание, что строка # 2 всегда хорошо сформирована и использует X(const X&).