почему функция явного преобразования возвращаемого типа производного класса не является кандидатом в контексте прямой инициализации

Рассмотрим следующий пример:

#include <iostream>
struct A{
    A() = default;
    A(A const&){}
};
struct B:A{};
struct C{
    explicit operator B(){
        return B{};
    }
};

int main(){
   C c;
   A a(c);  // #1
}

GCC и Clang оба сообщают, что c нельзя преобразовать в const A&. Однако в стандарте есть соответствующее правило: explicit operator B() должна быть функцией-кандидатом в этом контексте. То есть:
over.match.copy # 1.2

При инициализации временного объекта, который должен быть привязан к первому параметру конструктора, где параметр имеет тип «ссылка на возможно cv-квалифицированный T» и конструктор вызывается с одним аргументом в контексте прямой инициализации объекта объект типа «cv2 T», также рассматриваются функции явного преобразования. Те, которые не скрыты в S и дают тип, чья cv-неквалифицированная версия является тем же типом, что и T, или является его производным классом, являются функциями-кандидатами.

Более того, в текущем стандарте соответствующее правило по-прежнему имеет то же значение:
over.match.copy # 1.2

Когда тип выражения инициализатора является типом класса «cv S», рассматриваются функции преобразования. Допустимые типы для неявных функций преобразования: T и любой класс, производный от T. При инициализации временного объекта ([class.mem]) для привязки к первому параметру конструктора, где параметр имеет тип «ссылка на cv2 T», а конструктор вызывается с одним аргументом в контексте прямая инициализация объекта типа «cv3 T», допустимые типы для явных функций преобразования одинаковы; в противном случае их нет.

Независимо от стандарта C ++ 17 или текущего стандарта, все они говорят, что в контексте, подобном #1, функция явного преобразования, которая имеет возвращаемый тип класса T или любого класса, производного от T, также является кандидатом.

В моем примере объект a типа A напрямую инициализируется единственным инициализатором c, который является единственным аргументом в вызове конструктора, конструктор копирования A имеет параметр A const&. В этом случае c следует использовать для инициализации временного объекта, который будет привязан к первому параметру. Следовательно, в этом случае explicit operator B() следует рассматривать как функцию-кандидат. Однако и GCC, и Clang отвергают этот пример. GCC очевидно, что сообщают о бессмысленной информации о dignosis.
Это ошибка этих компиляторов? Или я что-то неправильно понял?


person xmh0511    schedule 02.01.2021    source источник
comment
Я думаю, что это отчасти ожидаемое поведение ... Этот код не компилируется с Visual C ++. Если я удалю explicit или верну A из преобразования, оно будет скомпилировано. Насколько я понимаю, часть cv2 T ... cv3 T подразумевает, что оператор явного преобразования должен выдавать T, а не S (который будет в категории: в противном случае их нет). Боковое примечание: цель явного - избежать нежелательного преобразования, и для меня нарезка вообще нежелательна ...   -  person Phil1970    schedule 02.01.2021
comment
@ Phil1970 Я не говорил, что возвращаемый тип S. Обратите внимание на эту формулировку: допустимые типы для функций неявного преобразования - это T, и любой класс, производный от T, и допустимые типы для функций явного преобразования одинаковы; . Таким образом, следует учитывать не только T, но и любой тип, производный от T.   -  person xmh0511    schedule 02.01.2021
comment
Предполагая, что компиляторы правы, а спецификацию сложно понять, я думаю, что вторая спецификация в основном исправляет дыру (в первой), добавляя часть, которая говорит о cv3 T В вашем примере A будет T, а B будет S. Поскольку ваша конверсия является явной, у вас есть cv3 S в качестве возврата вашей конверсии, и вы попадаете в категорию, в противном случае нет никакой категории. Я почти уверен, что новая формулировка исправит нежелательное преобразование в вашем коде. Если вы действительно хотите это преобразование, вы либо удалите explicit, либо добавите преобразование в A.   -  person Phil1970    schedule 02.01.2021
comment
Другая точка зрения состоит в том, что у вас есть 2 преобразования: C в B и B в A. При использовании explicit обычно требуется только одно преобразование. В противном случае скажите мне разницу между явным или неявным.   -  person Phil1970    schedule 02.01.2021
comment
См. Также stackoverflow.com/questions/12372442/ и другие связанные вопросы   -  person Phil1970    schedule 02.01.2021
comment
@ Phil1970 Путем заключения over.match.ref # 1 и over.match.conv # 1, они в основном говорят, что explicit conversion functions рассматривается как кандидат при выполнении прямой инициализации. Однако [over.match.copy # 1.2] - это специальное правило, которое разрешает явной функции преобразования быть кандидатом при инициализации параметра (инициализация копирования), пока объект инициализируется. неквалифицированный тип cv совпадает с типом, на который ссылается параметр конструктора   -  person xmh0511    schedule 03.01.2021
comment
@ Phil1970 Как я уже сказал в вопросе, инициализируемый объект a имеет тип класса A, выражение инициализатора c имеет тип класса C, а параметр конструктора копирования - ссылка на const A. Итак, при инициализации временного объекта ... примените здесь. Взятые A как T и Взятые C как S. C ++ 17 говорит, что те функции преобразования, возвращаемый тип которых T или любой тип класса, производный от T, являются функциями-кандидатами. C ++ 20 говорит, что допустимые типы для явных функций преобразования одинаковы. Итак, расплывчатость на the same. Мое чтение такое же, как и для функций преобразования   -  person xmh0511    schedule 03.01.2021
comment
Я предполагаю, что это ошибка, но я не могу найти похожий отчет об ошибке. Вы можете попробовать сообщить об ошибке и посмотреть, как ответят авторы компилятора.   -  person xskxzr    schedule 06.01.2021
comment
Я думаю, что [over.match.copy] / 1, по крайней мере, сбивает с толку, поскольку он говорит об особом случае для a (одиночного) определяемого пользователем преобразования с помощью явной функции преобразования, разрешенной при прямой инициализации (которая согласована с [class.conv.fct] / 2), но продолжает расширять специальный случай, чтобы разрешить эти явные функции преобразования также в определяемых пользователем последовательностях преобразования последовательностей, поскольку мы требуется стандартное преобразование (производное преобразование в основанное, имеющее рейтинг преобразования) для преобразования результата функции преобразования в целевой тип. Чтобы добавить к путанице, раздел братьев и сестер ...   -  person dfrib    schedule 06.01.2021
comment
... [over.match.conv] имеет другой подход к особому случаю, поскольку он явно позволяет стандартной последовательности преобразования следовать за пользовательским преобразованием с помощью неявных функций преобразования, но ограничивает это, чтобы разрешить только квалификационные преобразования. определяемое пользователем преобразование, если оно выполняется с помощью явной функции преобразования. Это, возможно, могло бы объяснить примечание об ошибке GCC: s, поскольку в нем указывается, что он не может преобразовать результат определяемого пользователем преобразования в целевой тип только с помощью квалификационного преобразования. Возможно, GCC реализовал такое же ограничение для [.... copy], что и для [... conv].   -  person dfrib    schedule 06.01.2021
comment
@dfrib - вторая стандартная последовательность преобразования явных функций преобразования, указанная в [over.match.conv], не проблема. Это согласуется с результатом компиляторов.   -  person xmh0511    schedule 06.01.2021
comment
@jackX Я знаю об этом, но я говорю, что и GCC, и Clang, похоже, реализовали те же ограничения, которые указаны в [over.match.conv], а также для [over.match.copy] - их различия заключаются в тонкий и, более того, оба особых случая, когда общие правила для определяемых пользователем преобразований последовательностей не применяются, согласно [over.best.ics] / 4. А именно, что определяемые пользователем преобразования, использующие явные функции преобразования (в прямом запуске), могут сопровождаться только квалификацией. преобразование, но не любые другие стандартные преобразования.   -  person dfrib    schedule 06.01.2021
comment
@xskxzr Я посчитал это ошибкой. Поскольку функция преобразования с типом возвращаемого значения A is, почему бы не B, который является производным A. Разве B не-А?   -  person xmh0511    schedule 06.01.2021
comment
В этом вашем примере требуется только квалификационное преобразование после пользовательского преобразования (явным функция преобразования), и Clang и GCC принимают ее (хотя, возможно, ошибочно отклоняют ваш исходный пример). GCC, очевидно, сообщает диагностическую информацию, которая не имеет смысла. - обратите внимание, что диагностика GCC: имеет смысл, если мы предполагаем, что они (ошибочно?) наложили те же ограничения, что и [over.match.conv], также для [over.match.copy], в отношении явных функций преобразования (и последующей квалификационной конверсии только по сравнению с любыми стандартными преобразованиями).   -  person dfrib    schedule 06.01.2021
comment
@dfrib Хорошо, почему другие стандартные преобразования не разрешены вторым стандартом функций преобразования? Скорее, только qual. конверсия. разрешено. Может быть, я только говорю, что об этом говорится в стандарте. ИМО, поскольку явные функции преобразования разрешены при прямой инициализации, почему бы в остальном не сохранить согласованность с функциями неявного преобразования, верно? Я предполагаю, что в этих контекстах могут возникнуть проблемы, если будет разрешено другое стандартное преобразование.   -  person xmh0511    schedule 06.01.2021
comment
@dfrib Обратите внимание на квалификацию для второго стандартного преобразования, которое должно быть квалифицированным преобразованием, весь этот раздел используется для инициализации неклассового объекта, over.match.conv # 1. В моем примере инициализируемый объект относится к типу класса, поэтому over.match .copy # 1 относится к моему примеру, а не к [over.match.conv].   -  person xmh0511    schedule 06.01.2021
comment
Я думаю, что вы можете неправильно понять мои комментарии выше (и что мы фактически согласны): я думаю, что GCC и Clang неправильно интерпретируют и реализуют [over.match.copy], который предназначен для целевого объекта типа класса. После этого я предполагаю, что причина этого неверного толкования заключается в том, что они используют те же ограничения, что и из [over.match.conv] (и [over.match.ref]), также при реализации [over .match.copy], даже если первое применяется к целевым типам, не относящимся к классу (и целевым типам ссылочного типа, соответственно). Различия между [over.match.conv] и / copy] довольно тонкие.   -  person dfrib    schedule 06.01.2021
comment
... И действительно, если мы взгляните на реализацию clang, мы видим, что они реализуют свой селектор кандидатов для функций преобразования для [over.match.copy] с использованием предиката для явного особого случая , основанный на [over.match.conv]. Я бы сказал, что это ошибка.   -  person dfrib    schedule 06.01.2021
comment
@dfrib О, я понял, что вы имеете в виду. Я изменил свое мнение в ошибке 98554, чтобы сделать его более понятным.   -  person xmh0511    schedule 06.01.2021
comment
@dfrib На самом деле, IIUC, этот пример все еще не соответствует стандарту, мой аргумент в том, что временного объект, который будет создан здесь. Ссылка имеет прямую обязательную силу. Итак, особый случай инициализации временного объекта ... не должен применяться к этому примеру.   -  person xmh0511    schedule 06.01.2021
comment
Хм, этот пример интересен: можно ли это разрешить в [class .conv.fct] / 2 и [dcl. init.general] /16.5.2, поскольку определяемое пользователем преобразование преобразуется в ссылку целевого типа, а не в целевой тип? В противном случае [dcl.init.general] /16.5.3 применяется, и мы возвращаемся к [over.match.copy], который разрешает только явные функции преобразования при инициализации временных объектов, что запретило бы пример.   -  person dfrib    schedule 06.01.2021
comment
@dfrib В этом объявлении A a(c);, где объект a инициализируется напрямую инициализатором c, поэтому сначала примените [dcl.init.general # 15.6.2], чтобы выбрать конструктор для этой инициализации. . Принимая c в качестве аргумента для вызова конструктора A(A const&), где параметр инициализируется копией из аргумента согласно dcl.init # general-13. Следовательно, это не прямая инициализация, и параметр не привязан к временному. Так что особых правил здесь не должно быть.   -  person xmh0511    schedule 07.01.2021