Инициализация списка C++ допускает несколько определяемых пользователем преобразований

Я читал этот ответ, в котором есть следующий пример:

struct R {};
struct S { S(R); };
struct T { 
    T(const T &); //1 
    T(S);         //2
};
void f(T);
void g(R r) {
    f({r});
}

Ответ связан со старой версией [over.best.ics]/4, которая тогда выглядела как это:

Однако при рассмотрении аргумента конструктора или определяемой пользователем функции преобразования, которая является кандидатом [over.match.ctor] при вызове для копирования/перемещения временного объекта на втором этапе инициализации копирования класса, by [over.match.list] при передаче списка инициализаторов в качестве одного аргумента или когда список инициализаторов имеет ровно один элемент и преобразование в некоторый класс X или ссылка на (возможно, cv-квалифицированный) X рассматривается для первого параметра конструктора X или с помощью [over.match.copy], [over.match.conv] или [over.match.ref] во всех случаях учитываются только стандартные последовательности преобразования и последовательности преобразования с многоточием. .

В ответе говорится, что без выделенной части в приведенной выше цитате f({r}) будет неоднозначным, потому что он может использовать либо первый конструктор T (1), либо второй конструктор (2).

Однако, как бы я ни пытался, я не вижу, как первый конструктор (1) является вариантом. f({r}) приводит к инициализации списка копирования T из {r}. Если используется первый конструктор, стандарт разрешает преобразование из r в тип параметра конструктора. Однако одного преобразования недостаточно, так как нужно будет перейти R --> S (используя конструктор преобразования S), а затем S --> T (используя конструктор преобразования T (2)). И я не могу найти в стандарте ничего, что допускало бы более одного определяемого пользователем преобразования в приведении к инициализации списка.

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

Текущая версия процитированного абзаца требует, чтобы единственным элементом списка инициализаторов был сам список инициализаторов, что имело бы смысл в приведенном выше примере, если бы вместо f({r}) было f({{r}}). В этом случае объяснение будет правильным.

Спасибо.


person user42768    schedule 31.07.2018    source источник


Ответы (1)


Ваше наблюдение верно. Пример правильно построен даже без выделенной части, но причина немного в другом.

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

На самом деле [over.best.ics]/4 — это как раз то правило, которое запрещает более одной пользовательской конверсии. Предположим, что (1) вызывается, тогда мы должны скопировать-инициализировать временный объект типа T с r, который попадает в «или по [over.match.copy], [over.match.conv], или [over.match .ref] во всех случаях», поэтому пользовательские преобразования (r -> const T& и r -> S) запрещены. В результате мы не можем сформировать неявную последовательность преобразования для (1), поэтому (2) выигрывает.

Обратите внимание, что выделенная часть была удалена из-за проблемы 1758, и вернулся снова с ограничением "список инициализаторов имеет ровно один элемент, который сам является списком инициализаторов" из-за выпуск 2076.

person xskxzr    schedule 01.08.2018
comment
Спасибо за ваш ответ. Итак, по какой причине выделенная часть была добавлена ​​в первую очередь? Было ли это для таких случаев, как: struct S{}; struct T{ T(T&); T(S); }? Потому что теперь можно выбрать оба конструктора T? - person user42768; 01.08.2018
comment
Я не знаю. Я не могу найти официальный документ с указанием причины. - person xskxzr; 01.08.2018
comment
Я имел в виду пример, который я привел в своем первом комментарии: struct S{}; struct T{ T(T&); T(S); }. Без выделенной части в таком вызове, как void f(T); S s; f({s});, оба конструктора T были бы допустимы, верно? - person user42768; 01.08.2018
comment
Нет, T& нельзя инициализировать с помощью s... Если вы используете const T&, также нет проблем, поскольку T(S) является точным совпадением. - person xskxzr; 01.08.2018
comment
Вы правы в том, что неконстантная ссылка lvalue не привязывается к rvalue, я не знаю, почему я вообще сделал параметр ссылкой. Я попытаюсь найти другой пример, который может быть причиной выделенной части. Спасибо. - person user42768; 01.08.2018