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

Инициализация двойной скобки

Какой конструктор следует вызывать в следующем коде и почему?

struct S
{
    int i;
    S() = default;
    S(void *) : i{1} { ; }
};

S s{{}};

Если я использую clang (из внешней линии), вызывается вторая.

Если второй конструктор закомментирован, то S{{}} все еще является допустимым выражением, но (я полагаю) в этом случае вызывается move-constructor из созданного по умолчанию экземпляра S{}.

Почему конструктор преобразования имеет приоритет над значением по умолчанию в самом первом случае?

Цель такой комбинации конструкторов S заключается в сохранении ее свойства std::is_trivially_default_constructible_v< S >, за исключением конечного набора случаев, когда он должен быть инициализирован определенным образом.

4b9b3361

Ответ 1

Если второй конструктор закомментирован, то S {{}} остается действительным выражением, но (я уверен) в этом случае вызывается конструктор move из построенного по умолчанию экземпляра S {}.

Собственно, это не то, что происходит. Порядок в [dcl.init.list]:

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

Как только вы удаляете конструктор S(void *), S становится агрегатом - он не имеет конструктора, предоставленного пользователем. S() = default не считается предоставленным пользователем, потому что причины. Агрегатная инициализация из {} закончится инициализацией значения i.


Почему конструктор преобразования имеет приоритет над значением по умолчанию в самом первом случае?

Оставаясь void*, продолжайте движение вниз по списку маркеров:

- В противном случае, если в списке инициализаторов нет элементов [...]
- В противном случае, если T является специализацией std:: initializer_list, [...]
- В противном случае, если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены и лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7).

[over.match.list] дает нам двухфазный процесс разрешения перегрузки:

- Изначально функции-кандидаты являются конструкторами-списками инициализаторов (8.6.4) класса T и Список аргументов состоит из списка инициализаторов как одного аргумента.
- Если не найдено никакого жизнеспособного конструктора списка инициализаторов, снова выполняется разрешение перегрузки, где Функции-кандидаты - все конструкторы класса T, а список аргументов состоит из элементов списка инициализаторов.

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

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

S(S const& );
S(S&& );
S(void *);

Последовательности преобразования определены в [over.ics.list]:

В противном случае, если параметр является неагрегатным классом X и разрешением перегрузки на 13.3.1.7, выбирается один лучший конструктор C из X для выполнения инициализации объекта типа X из списка инициализатора аргументов:
- Если C не является конструктором списка инициализаторов, а в списке инициализаций имеется один элемент типа cv U, [...] - В противном случае, неявная последовательность преобразования представляет собой пользовательскую последовательность преобразований со второй стандартной последовательностью преобразования с преобразованием идентичности.

и

В противном случае, если тип параметра не является классом: [...] - если в списке инициализаторов нет элементов, неявная последовательность преобразований - это преобразование идентичности.

Таким образом, конструкторы S(S&& ) и S(S const& ) являются как пользовательскими последовательностями преобразования, так и преобразованием идентичности. Но S(void *) - это просто преобразование идентичности.

Но, [over.best.ics] имеет это дополнительное правило:

Однако, если цель - - первый параметр конструктора или
- параметр неявного объекта определяемой пользователем функции преобразования
и конструктор или пользовательская функция преобразования - это кандидат от
- 13.3.1.3, когда [...]
- 13.3.1.4, 13.3.1.5 или 13.3.1.6 (во всех случаях), или
- вторая фаза 13.3.1.7, когда список инициализаторов имеет ровно один элемент, который сам является списком инициализаторов, а целью является первый параметр конструктора класса X, а преобразование - X или ссылку на (возможно, cv-квалификацию) X,

пользовательские последовательности преобразования не рассматриваются.

Это исключает из рассмотрения S(S const&) и S(S&& ) в качестве кандидатов - именно в этом случае - целью является первый параметр конструктора в результате второй фазы [over.match.list] и целевой как ссылка на возможную cv-квалификацию S, и такая последовательность преобразования будет определяться пользователем.

Следовательно, единственным оставшимся кандидатом является S(void *), поэтому он тривиально является лучшим жизнеспособным кандидатом.