Почему "ctor() = default" изменяет поведение при наличии других конструкторов?

Почему

struct wrapper
{
    explicit wrapper(void *);
    wrapper() = default;
    int v;
};

int main() { return wrapper().v; }  // You should run this in Debug mode

return 0xCCCCCCCC, тогда как

struct wrapper { wrapper() = default; int v; };
int main() { return wrapper().v; }

и

struct wrapper { int v; };
int main() { return wrapper().v; }

оба возвращают 0?

Ответ 1

Во время инициализации значения, если T - это тип класса без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объект инициализируется нулем (§8.5/8.2). Это действительно так с wrapper.

Ваш первый пример соответствует третьему случаю для нулевой инициализации (§8.5/6.1, акцент мой)

- если T является скалярным типом (3.9), объект инициализируется значением, полученным преобразованием целочисленного литерала   0 (ноль) до T;

     

- , если T является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический элемент данных и каждый под-объект базового класса инициализируются нулями, а заполнение инициализируется нулевыми битами;

     

- если T является (возможно, cv-квалифицированным) типом объединения, объекты первого нестатического именованного элемента данных инициализируются нулями и заполнение инициализируется нулевыми битами;

     

- если T - тип массива, каждый элемент инициализируется нулем

     

- если T является ссылочным типом, инициализация не выполняется

Итак, в вашем первом примере v должен быть инициализирован нулем. Это выглядит как ошибка.

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

Ответ 2

Это похоже на ошибку в MSVC. Во всех трех случаях wrapper не имеет пользовательского конструктора по умолчанию, поэтому инициализация с помощью wrapper() вызывает:

(Все цитаты из n3690)

(8.5/11) Объект, инициализатор которого представляет собой пустой набор круглых скобок, т.е.(), должен инициализироваться значением.

(благодаря dyp), это приведет к нулевой инициализации int v

Инициализация затем ссылается на правило:

(8.5/8), если T является (возможно, cv-квалифицированным) типом класса без предоставленного пользователем или удаляемого конструктора по умолчанию, тогда объект инициализируется нулем и проверяются семантические ограничения для инициализации по умолчанию.

Правила инициализации нуля:

(8.5/6), если T является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический член данных и каждый подобъект базового класса инициализируются нулями и заполнение инициализируется нулевыми битами

int v, являющийся элементом данных wrapper, инициализируется нулем в соответствии с:

(8.5/6), если T является скалярным типом (3.9), объект инициализируется значением, полученным преобразованием целочисленного литерала 0 (ноль) в T

Это не поведение, которое вы наблюдаете.