Учтите следующее:
struct C {
explicit operator bool() const {
return true;
}
};
int main() {
C c;
auto b = static_cast<const bool &>(c);
return 0;
}
Clang++ 18 компилируется нормально, G++ 14 говорит:
test.cpp: In function ‘int main()’:
test.cpp:12:14: error: invalid ‘static_cast’ from type ‘C’ to type ‘const bool&’
12 | auto b = static_cast<const bool &>(c);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
См. Compiler Explorer.
Эта несовместимость возникла в моем коде, потому что тестовые макросы Catch2 используют такой static_cast под капотом, и он не работает с пользовательским типом с explicit operator bool, но только с G++.
Мне кажется, поскольку временный bool может привязываться к const bool &, это следует разрешить.
Кто здесь, следующий стандарту?
Или auto b = static_cast<const bool&>(c.operator bool());
Или даже Or auto b = static_cast<const bool&>(!!c); Из-за explicit в функции-члене explicit operator bool() const.





тлдр; Программа правильно сформирована, поскольку это прямая инициализация, и при прямой инициализации можно использовать функции преобразования.
Прежде всего отметим, что explicit — это прямая инициация . Итак, мы переходим к dcl.init#16:
Семантика инициализаторов следующая. Тип назначения — это тип инициализируемого объекта или ссылки, а тип источника — это тип выражения инициализатора. Если инициализатор не является одним выражением (возможно, заключенным в круглые скобки), тип источника не определен. Если тип назначения является ссылочным типом, см. [dcl.init.ref].
Итак, мы переходим к dcl.init.ref:
Ссылка на тип «cv1 T1» инициализируется выражением типа «cv2 T2» следующим образом:
В противном случае, если выражение инициализатора
- имеет тип класса (т. е. T2 является типом класса), где T1 не связан со ссылкой на T2 и может быть преобразован в rvalue типа «cv3 T3» или lvalue типа функции «cv3 T3», где « cv1 T1» совместим по ссылке с «cv3 T3» (см. [over.match.ref]),
тогда выражение инициализатора в первом случае и преобразованное выражение во втором случае называются преобразованным инициализатором. Если преобразованный инициализатор является значением prvalue, пусть его тип обозначается T4; применяется временное преобразование материализации ([conv.rval]), учитывая, что тип prvalue — «cv1 T4» ([conv.qual]). В любом случае ссылка привязывается к полученному значению glvalue (или к соответствующему подобъекту базового класса).
(выделено мной)
Это означает, что инициализатор static_cast<const bool &>(c) преобразуется в prvalue c, а затем ссылка привязывается к значению x, полученному в результате временной материализации.
Также обратите внимание, что функции преобразования bool можно использовать в контекстах прямой инициализации согласно class.conv.fct:
Функция преобразования может быть явной ([dcl.fct.spec]), и в этом случае она рассматривается только как определяемое пользователем преобразование для прямой инициализации ([dcl.init]). В противном случае пользовательские преобразования не ограничиваются использованием в назначениях и инициализациях.
Так что программа составлена правильно.
Здесь нет «обычно» — единственное ограничение explicit — «можно использовать только при прямой инициализации». Инициализация во время static_cast явно гарантированно будет прямой инициализацией. Таким образом, внутри static_cast все инициализации допускают использование явных операторов преобразования. Вам нужно будет найти определяемое пользователем преобразование, не используемое для инициализации, используемой во время static_cast, чтобы explicit имело значение; Я не вижу ни одного? (и даже тогда, поскольку это часть прямой инициализации, ее законность является сильным аргументом)
Спасибо всем! Я вижу, что вы оба согласны с результатом (Кланг прав), но я вижу разные объяснения. Я действительно не могу самостоятельно рассуждать о том, у кого есть правильное объяснение, чтобы отметить принятый ответ. Вы достигли консенсуса?
@NicolaGigante Оба объяснения на самом деле одинаковы и верны. В моем ответе есть дополнительный dcl.init.ref для полноты. В остальном объяснения те же. Пожалуйста.
Кланг прав.
Когда вы вводите operator bool()explicit, полное ограничение, которое вызывает explicit, описано здесь:
Функция преобразования может быть явной ([dcl.fct.spec] ), и в этом случае она рассматривается только как определяемое пользователем преобразование для прямой инициализации ( [dcl.init]). В противном случае пользовательские преобразования не ограничиваются использованием в назначениях и инициализациях.
static_cast не предполагает присвоений, а только инициализацию. Являются ли инициализации, которые происходят в static_cast, прямыми или нет?
Инициализация, которая происходит
- (15.1) для инициализатора, который представляет собой список выражений в скобках или список инициализации в фигурных скобках,
- (15.2) для
new-инициализатора ([expr.new]),- (15.3) в выражении
static_cast([expr.static.cast]),- (15.4) в преобразовании типа функциональной записи ([expr.type.conv]), и
- (15.5) в форме условия в виде списка инициализации в фигурных скобках
называется прямой инициализацией.
стандарт явно указывает, что инициализация, происходящая в выражении static_cast, является прямой инициализацией.
Таким образом, explicit не может ограничивать работу operator bool() в static_cast. (хорошо, это могло бы быть ограничено, если бы у нас в итоге появилось концептуальное ограничение, которое запрашивало бы свойства присваивания правого типа с присвоением значения bool, но я даже не уверен, КАК бы я это написал!)
Что происходит, когда вы выполняете статическое приведение к ссылке?
Если
Tявляется ссылочным типом, эффект такой же, как при выполнении объявления и инициализацииT t(E);для какой-то придуманной временной переменнойt([dcl.init]), а затем использовать временную переменную как результат преобразования.
Эта инициализация с помощью [dcl.init.general]/15 является прямой инициализацией, поэтому operator bool()const explicit разрешено использовать.
Я не думаю, что это подлежит сомнению
struct foo {
operator bool()const{return true;}
};
foo f;
bool const& b = f;
совершенно законно; Итак, clang прав, другие компиляторы ошибаются.
...
Видимо есть сомнения в том, что bool const& b = f; законно, когда f имеет не explicitoperator bool()const. Итак, вот мои доказательства того, что это законно:
bool const& b = f; является законным благодаря [dcl.init]/5.1.2, который разрешает привязку к const-lvalue-ref-to-T из объекта, который можно преобразовать в rvalue-to-T.
bool const& b = f; безопасно из-за «временной материализации» и продления срока службы ссылки, в результате чего материализованное временное значение будет длиться столько же, сколько и const-lvalue-ref-to-T.
Обратите внимание, что использование = здесь было бы незаконным, если бы operator bool было явным, оно должно было бы быть bool const& b(f);, если f имеет explicit operator bool; однако этот раздел существует лишь для того, чтобы указать на то, что без explicit проблем не будет. Предыдущая логика показывает, что explicit не вызывает проблем, поскольку инициализация в static_cast является явной.
[dcl.init.general]/15 интерпретируется неправильно. Результат static_cast подлежит прямой инициализации, если результат не является ссылкой; Ввод не затрагивается. Другими словами, использованиеstatic_cast похоже на вызов функции; единственным исключением является вызов prvalue или xvalue. Ваша интерпретация противоречит цели ключевого слова explicit по предотвращению непреднамеренных конверсий. G++, похоже, демонстрирует правильное поведение.
@Red.Wave Инициализация статического приведения — это прямая инициализация, точка, стандартным текстом. Если вы не согласны со стандартом, конечно, никакой шкуры с моей спины. Но вы не предоставляете никаких доказательств того, что это не то, что говорит стандарт. Я не вижу пункта «если результат не является ссылкой». «Ввод не затрагивается» также является неуместным предложением, насколько я понимаю? Это не похоже на вызов функции, поскольку это прямая инициализация, а вызовы функций не являются прямой инициализацией.
Я был немного не уверен в этом последнем примере и начал думать о том, когда законно и незаконно привязывать ссылку к висячему временному объекту, а затем не использовать ссылку - пока я не вспомнил, что привязка bool const& b продлевает срок службы материализованного временного объекта, так что все это не имеет значения. Возможно, стоит сделать небольшое замечание.
Итак, давайте навсегда откажемся от ключевого слова explicit. Потому что это не должно ничего делать. И пусть static_cast волшебным образом обходит все возможные меры против непреднамеренных конверсий? Это, безусловно, повышает безопасность языка. Текущая формулировка в стандартном тексте является дефектной, по моему мнению, это подходящий случай для DR. Я бы хотел, чтобы кто-нибудь из комитета оставил отзыв; в конце концов, вопрос помечен как языковой юрист.
@Red.Wave Я думаю, твоя проблема в том, что ты не знаешь, что static_cast является явным преобразованием? Предполагается, что explicit operator bool()const защищает от a+b, когда «a преобразуется в bool, которое преобразуется в целое число, а затем вы добавляете b к этому целому числу». Не предполагается защита от случаев, когда кто-то просит bool. Я не понимаю, почему вы считаете, что static_cast<bool const&>(smart_pointer) нужно запретить? Это довольно безвредно. И вы явно просите преобразовать smart_pointer в bool.
@aschepler Я надеюсь, что любой, кто так глубоко погружается в причуды C++, знает о временной материализации и продлении срока службы ссылки... Но, может быть, это источник страха людей перед приведением к bool const&?
@aschepler Добавил кучу слов, прикрывающих bool const& b = f;
Явность static_cast применима только к касте последнего уровня. static_cast<bool>(c) совершенно законно. Но рассматриваемый актерский состав требует еще одного explicit каста перед кастом последнего уровня: auto const& b=static_cast<bool>(c);
@Red.Wave Я имею в виду, я предоставил стандартные котировки; насколько я могу судить, вы изобретаете вещи, которые не соответствуют стандарту. static_cast<bool const&> — это одна прямая инициализация, а не два приведения, а explicit просто ограничивает использование приведения только во время прямой инициализации. И взятие prvalue bool и привязка к const bool& не является отдельным преобразованием; именно так ссылки связываются с временными объектами.
Таким образом, определение прямой инициализации расширилось и теперь особым образом охватывает такие случаи. Читать стандартную литературу сложно. Мне нужны лучшие интерпретации того, как выполняется static_cast.
@Red.Wave static_cast<X>(expr) буквально эквивалентен X x(expr); с примечанием, что x представляет собой prvalue, а не значение. Y const& x(obj);, когда obj имеет explicit operator Y, создает временный Y, затем связывает его, и время жизни продлевает его до x. explicit блокирует Y const& x = obj;, но когда вы это делаете Y const& x(obj);, это именно тот синтаксис, который должен учитываться explicit (и такие случаи, как статическое приведение, которые эквивалентны).
Итак, конструкция explicit отбрасывает квалификаторы, если точное совпадение не найдено? т. е. он считает частично квалифицированные и неквалифицированные конверсии более низким приоритетом? Это часть процесса прямой инициализации?
@Red.Wave Я связал соответствующий раздел в ответе. eel.is/c++draft/dcl.init#ref-5.1.2 если хотите повторную ссылку. Я рекомендую вам использовать [задать вопрос], чтобы узнать больше о том, как работают преобразования в C++, а не в случайном потоке комментариев.
Мне кажется,
auto b = static_cast<const bool&>(static_cast<bool>(c));должен понадобиться