Конструктор Constexpr не удовлетворяет требованиям, но по-прежнему constexpr. Почему?

Стандарт говорит о функциях / конструкторах шаблона constexpr в dcl.constexpr / 6:

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.

Интересная часть:

fail to satisfy the requirements for a ... constexpr constructor, that specialization is still a ... constexpr constructor

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

Почему существует это правило? Почему не удаляется constexpr, если функция не удовлетворяет требованиям?

Текущее поведение плохо по двум причинам:

  • не-constexpr-ness перехватывается не в ближайшем возможном месте, а в фактическом выражении constexpr, где оно используется. Итак, мы должны найти проблемную часть, из которой constexpr был удален незаметно.
  • объект, который предназначен для статической инициализации (потому что у него есть конструктор constexpr), будет динамически инициализированный без каких-либо ошибок / предупреждений (потому что конструктор «на самом деле» не constexpr).

Есть ли у этого правила какие-то плюсы, которые уравновешивают его?

Интересно, что если вы вернетесь, чтобы прочитать предыдущие предложения по этому поводу - например, open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf - вы обнаружите, что формулировка такая, как вы предлагаете.

Jack Aidley 05.12.2018 14:52

@JackAidley: Ого, хорошая находка! Я прослежу, может быть, где-то есть обсуждение этого (для других это на странице 13, пункт 5).

geza 05.12.2018 15:31

Изменение формулировки, похоже, связано с исправлением этого отчета о дефекте, но я не могу понять, почему: open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1358 - может быть, кто-то другой может?

Jack Aidley 05.12.2018 15:51

Хм, разве «Дополнительная заметка (январь 2013 г.):» не является темой этого вопроса? Кажется, комитет в курсе?

Max Langhof 05.12.2018 15:59

Боковое примечание: теперь есть Consteval, который ведет себя таким образом.

jonspaceharper 06.12.2018 02:58
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
5
532
2

Ответы 2

Это правило позволяет вам написать шаблонный конструктор / функцию и пометить его как constexpr, даже если это не всегда constexpr (хотя бы иногда).

Например, std::pair имеет constexpr конструкторы, но, конечно, его можно использовать вне константных выражений.

Это вполне разумно, потому что в противном случае вам пришлось бы дублировать все эти функции (один раз с constexpr и один раз без него), даже если код точно такой же. Не будем даже рассматривать двусмысленность.

Поскольку, как правило, невозможно доказать, что шаблон никогда не может удовлетворить constexpr, для него не требуется никакой диагностики (но он плохо сформирован, поэтому компиляторы могут пожаловаться вам, если они могут доказать это для данного случая).

Вы правы, что это не очень полезно, если вы хотите указать, что «эта функция должна использовать Только в постоянном выражении», но это не то, к чему стремится эта формулировка.

Обновлено: чтобы уточнить, constexprдля функций означает только "допустимо для вычисления внутри константного выражения" (более точная формулировка здесь), нет"может быть оценено только во время компиляции". Напротив, переменные constexpr должны быть инициализированы постоянным выражением.


Еще одно изменение: у нас есть точная формулировка для обсуждения, спасибо @JackAidley!

If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the constexpr specifier is ignored and the specialization is not a constexpr function.

Проблема заключается в том, что «существует по крайней мере один набор аргументов, для которого функция может быть вычислена константой» является частью «требований для функции constexpr». Следовательно, компиляторы не могут реализовать это предложение, поскольку невозможно доказать (в общем), существует ли такой набор для данной функции (или создания экземпляра шаблона функции). Вы должны либо еще больше замутить это требование, либо отказаться от этого аспекта. Похоже, комитет выбрал второе.

С пометкой constexpr конструктора шаблонного класса проблем нет. Проблема в том, что когда этот шаблон является специализированным, и компилятор знает, что конструктор не constexpr, он не должен рассматривать его как constexpr. Но из-за этого правила он все равно трактует его как constexpr. А сбой наступит поздно, когда конструктор фактически используется в константном выражении. Но это могло произойти и раньше, когда «поддельный» конструктор constexpr использовался в другом конструкторе constexpr (если бы свойство consexpr было семантически удалено).

geza 05.12.2018 14:47

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

geza 05.12.2018 14:47

Я думаю, вы неправильно понимаете, что означает constexpr для функций. constexpr функции может должны оцениваться во время компиляции (для некоторого набора аргументов). Это не гарантированный для каждого выбора аргументов. Если вам требуется инициализировать значение во время компиляции, отметьте значение как constexpr (это вызовет проблему). Отметка только функции просто делает законным использование функции в константном выражении, это не требует, чтобы каждый вызов выполнялся во время компиляции.

Max Langhof 05.12.2018 15:13

Конечно, но текущие компиляторы выдают ошибку (даже если это отчет о недоставке), когда случай тривиален, а выражение никогда не дает constexpr. Но из-за этого правила (якобы) они этого не делают.

geza 05.12.2018 15:24

@geza Покажите, пожалуйста, такой случай. Обратите внимание, что функции, помеченные как constexpr, нарушающие пункты списка здесь, должны быть диагностированы. Компилятор может (но не требуется) для дополнительной диагностики случаев, когда никакой набор аргументов не может привести к оценке времени компиляции («Диагностика не требуется для нарушения этого маркера.»). Обратите внимание, что ключевое слово используется для того, чтобы компилятор помогал вам, а не для того, чтобы компилятор делал то, что в противном случае он бы не сделал.

Max Langhof 05.12.2018 15:31

Посмотрите на код здесь: stackoverflow.com/questions/53632120/…. Поместите член NonConstexpr в Foo. Тогда он больше не будет компилироваться (проверено с помощью gcc и clang).

geza 05.12.2018 15:33

Ознакомьтесь с комментариями под вопросом: Джек Эйдли нашел более старое предложение, где оно сработало так, как было бы логично (для меня). Но, возможно, это предложение сильно отличалось и в других отношениях. Но в любом случае они это изменили из-за чего-то.

geza 05.12.2018 15:35

Я имел в виду пример «текущий компилятор выдает ошибку в тривиальном случае». То, что вы связали, - это просто демонстрация всего, что было сказано до сих пор, и у меня не хватает способов сформулировать это. Что касается «почему они изменили это предложение», см. Начало моего ответа для обоснованного предположения. Или, скорее, позвольте мне спросить вас: если бы constexpr работал так, как вы (и исходное предложение) хотели, сколько конструкторов std::pair должно было бы быть? У скольких из них будет одинаковый код?

Max Langhof 05.12.2018 15:36

Это пример того. Если вы добавите элемент NonConstexpr в Foo, он не будет компилироваться. Тривиальный пример: Foo не помещается в константное выражение, но не может быть скомпилирован, поскольку конструктор Foo никогда не может быть частью константного выражения.

geza 05.12.2018 15:39

Для std::pair никаких изменений не потребуется.

geza 05.12.2018 15:40

@geza: Тогда непонятно, что ты вообще предлагаешь. Если применение constexpr к конструктору класса шаблона, который создается с типом, в котором невозможно быть constexpr, приводит к ошибке компиляции, как сделать такой тип условно constexpr без дублирования конструкторов и использования гимнастики SFINAE?

Nicol Bolas 05.12.2018 15:42

@NicolBolas: Хм, может, я не вижу здесь ничего тривиального. constexpr написан для конструктора шаблона (как сейчас). Но если специализация приведет к конструктору, не являющемуся constexpr, то constexpr будет проигнорирован. Также как если бы этого не было там написано. Также как говорится в этом старом предложении. Могу ли я упустить из виду что-то здесь, что делает это нежизнеспособным?

geza 05.12.2018 15:46

@geza: Если это то, о чем вы просите, чем это функционально отличается от того, что у нас есть сейчас?

Nicol Bolas 05.12.2018 15:47

@NicolBolas: Это будет означать, что мой текущий пример не будет компилироваться, поскольку текущие компиляторы диагностируют тривиальные случаи.

geza 05.12.2018 15:50

@geza: Если «мой текущий пример» относится к вопросу, на который вы ссылались, то уже не компилируется. Если это не так, то каков ваш текущий пример?

Nicol Bolas 05.12.2018 15:51

@NicolBolas: хм, какой компилятор? Для меня компилируется. И с помощью gcc, и с помощью clang.

geza 05.12.2018 15:54

@NicolBolas: gcc, clang, msvc компилирует его, icc отклоняет: godbolt.org/z/ggGfya

geza 05.12.2018 16:12

В более ранние версии предложения по изменению языка он работал так, как вы предлагаете:

If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function,the constexprspecifier is ignored and the specialization is not a constexpr function.

Но позже это было изменено. Мне не удалось найти какой-либо окончательный ответ на ваш вопрос, но я думаю, что разумно полагать, что ответ заключается в том, что constexpr вносит другие семантические изменения в код, и они сохраняются, даже если функция больше не может использоваться в других операторах constexpr . Если вы посмотрите на отчет о дефектах 1358, который включает изменение текущей формулировки, вы можете увидеть промежуточную форму слов, которая включает примечание о сохранении статуса const независимо от этого.

Я также считаю, что, хотя сохранение статуса constexpr не является интуитивным, оба ваших аргумента против этого неверны:

  1. Обнаружение constexpr при создании экземпляра шаблона идет вразрез с тем, как обычно работают шаблоны C++ - вы получаете сообщение об ошибке только тогда, когда пытаетесь использовать шаблон таким образом, чтобы его нельзя было использовать для этого типа; просто невозможность заполнить всю подпись не является ошибкой. Внедрение специальной механики для constexpr было бы излишне запутанным и ограничивало бы полезность, поскольку теперь вам нужно было бы писать разные шаблоны для типов constexprable и un-constexprable.

  2. Так как, он поддерживает спецификатор constexpr, откат не к общей динамической инициализации во время выполнения, а к динамической инициализации во время инициализации static. Это может вызвать проблемы из-за статического порядка инициализации Fiasco, но, по крайней мере, происходит до того, как будет введена функция main().

@ T.C. Я не могу помочь, но чувствую, что тот, кто решил, что инициализация statics не является статической инициализацией и что Fiasco порядка статической инициализации происходит только при динамической инициализации, а не статической инициализации, в то время немного хихикал.

Jack Aidley 06.12.2018 11:05

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