`static_cast<const bool&>` с `явным оператором bool`

Учтите следующее:


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&>(static_cast<bool>(c)); должен понадобиться

Ted Lyngmo 17.07.2024 15:42

Или auto b = static_cast<const bool&>(c.operator bool());

Eljay 17.07.2024 15:44

Или даже Or auto b = static_cast<const bool&>(!!c); Из-за explicit в функции-члене explicit operator bool() const.

Eljay 17.07.2024 16:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

тлдр; Программа правильно сформирована, поскольку это прямая инициализация, и при прямой инициализации можно использовать функции преобразования.


Прежде всего отметим, что 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 имело значение; Я не вижу ни одного? (и даже тогда, поскольку это часть прямой инициализации, ее законность является сильным аргументом)

Yakk - Adam Nevraumont 17.07.2024 16:34

Спасибо всем! Я вижу, что вы оба согласны с результатом (Кланг прав), но я вижу разные объяснения. Я действительно не могу самостоятельно рассуждать о том, у кого есть правильное объяснение, чтобы отметить принятый ответ. Вы достигли консенсуса?

Nicola Gigante 17.07.2024 18:10

@NicolaGigante Оба объяснения на самом деле одинаковы и верны. В моем ответе есть дополнительный dcl.init.ref для полноты. В остальном объяснения те же. Пожалуйста.

user12002570 17.07.2024 18:22
Ответ принят как подходящий

ТЛ;ДР

Кланг прав.

Подробности:

Когда вы вводите operator bool()explicit, полное ограничение, которое вызывает explicit, описано здесь:

[class.conv.fct]/2

Функция преобразования может быть явной ([dcl.fct.spec] ), и в этом случае она рассматривается только как определяемое пользователем преобразование для прямой инициализации ( [dcl.init]). В противном случае пользовательские преобразования не ограничиваются использованием в назначениях и инициализациях.

static_cast не предполагает присвоений, а только инициализацию. Являются ли инициализации, которые происходят в static_cast, прямыми или нет?

[dcl.init.general]/15:

Инициализация, которая происходит

  • (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, но я даже не уверен, КАК бы я это написал!)

Что происходит, когда вы выполняете статическое приведение к ссылке?

[expr.static.cast]/4:

Если 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 17.07.2024 20:04

@Red.Wave Инициализация статического приведения — это прямая инициализация, точка, стандартным текстом. Если вы не согласны со стандартом, конечно, никакой шкуры с моей спины. Но вы не предоставляете никаких доказательств того, что это не то, что говорит стандарт. Я не вижу пункта «если результат не является ссылкой». «Ввод не затрагивается» также является неуместным предложением, насколько я понимаю? Это не похоже на вызов функции, поскольку это прямая инициализация, а вызовы функций не являются прямой инициализацией.

Yakk - Adam Nevraumont 17.07.2024 20:27

Я был немного не уверен в этом последнем примере и начал думать о том, когда законно и незаконно привязывать ссылку к висячему временному объекту, а затем не использовать ссылку - пока я не вспомнил, что привязка bool const& b продлевает срок службы материализованного временного объекта, так что все это не имеет значения. Возможно, стоит сделать небольшое замечание.

aschepler 18.07.2024 04:25

Итак, давайте навсегда откажемся от ключевого слова explicit. Потому что это не должно ничего делать. И пусть static_cast волшебным образом обходит все возможные меры против непреднамеренных конверсий? Это, безусловно, повышает безопасность языка. Текущая формулировка в стандартном тексте является дефектной, по моему мнению, это подходящий случай для DR. Я бы хотел, чтобы кто-нибудь из комитета оставил отзыв; в конце концов, вопрос помечен как языковой юрист.

Red.Wave 18.07.2024 11:47

@Red.Wave Я думаю, твоя проблема в том, что ты не знаешь, что static_cast является явным преобразованием? Предполагается, что explicit operator bool()const защищает от a+b, когда «a преобразуется в bool, которое преобразуется в целое число, а затем вы добавляете b к этому целому числу». Не предполагается защита от случаев, когда кто-то просит bool. Я не понимаю, почему вы считаете, что static_cast<bool const&>(smart_pointer) нужно запретить? Это довольно безвредно. И вы явно просите преобразовать smart_pointer в bool.

Yakk - Adam Nevraumont 18.07.2024 16:04

@aschepler Я надеюсь, что любой, кто так глубоко погружается в причуды C++, знает о временной материализации и продлении срока службы ссылки... Но, может быть, это источник страха людей перед приведением к bool const&?

Yakk - Adam Nevraumont 18.07.2024 16:06

@aschepler Добавил кучу слов, прикрывающих bool const& b = f;

Yakk - Adam Nevraumont 18.07.2024 16:16

Явность static_cast применима только к касте последнего уровня. static_cast<bool>(c) совершенно законно. Но рассматриваемый актерский состав требует еще одного explicit каста перед кастом последнего уровня: auto const& b=static_cast<bool>(c);

Red.Wave 18.07.2024 16:54

@Red.Wave Я имею в виду, я предоставил стандартные котировки; насколько я могу судить, вы изобретаете вещи, которые не соответствуют стандарту. static_cast<bool const&> — это одна прямая инициализация, а не два приведения, а explicit просто ограничивает использование приведения только во время прямой инициализации. И взятие prvalue bool и привязка к const bool& не является отдельным преобразованием; именно так ссылки связываются с временными объектами.

Yakk - Adam Nevraumont 18.07.2024 16:59

Таким образом, определение прямой инициализации расширилось и теперь особым образом охватывает такие случаи. Читать стандартную литературу сложно. Мне нужны лучшие интерпретации того, как выполняется static_cast.

Red.Wave 18.07.2024 17:09

@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 (и такие случаи, как статическое приведение, которые эквивалентны).

Yakk - Adam Nevraumont 18.07.2024 17:29

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

Red.Wave 18.07.2024 18:22

@Red.Wave Я связал соответствующий раздел в ответе. eel.is/c++draft/dcl.init#ref-5.1.2 если хотите повторную ссылку. Я рекомендую вам использовать [задать вопрос], чтобы узнать больше о том, как работают преобразования в C++, а не в случайном потоке комментариев.

Yakk - Adam Nevraumont 18.07.2024 20:08

Другие вопросы по теме