Не подразумевает ли constexpr noexcept?
Обозначает ли спецификатор constexpr
спецификатор noexcept
для функции? Отвечать на аналогичный вопрос говорит "да" относительно спецификатора inline
, но статья Эрика Ниблера заставляет задуматься о возможном ответе на текущую. На мой взгляд, ответ может зависеть от контекста использования функции constexpr
: это контекст контекста или контекст времени выполнения, т.е. Все параметры функции, известные во время компиляции или нет.
Я ожидал, что ответ будет "да", но простая проверка показывает, что это не так.
constexpr
bool f(int) noexcept
{
return true;
}
constexpr
bool g(int)
{
return true;
}
static_assert(noexcept(f(1)));
static_assert(noexcept(g(2))); // comment this line to check runtime behaviour
#include <cassert>
#include <cstdlib>
int
main(int argc, char * [])
{
assert(noexcept(f(argc)));
assert(noexcept(g(argc)));
return EXIT_SUCCESS;
}
Ответы
Ответ 1
Нет, это не может быть так, потому что не всякое inovocation функции constexpr должно быть оценено как подвыражение основного константного выражения. Нам нужно только одно значение аргумента, которое позволяет это. Таким образом, функция constexpr может содержать оператор throw, если у нас есть значение аргумента, которое не вызывает эту ветвь.
Это описано в стандартном разделе проекта С++ 14 7.1.5
Спецификатор constexpr [dcl.constexpr], который сообщает нам, что разрешено в функции constexpr:
Определение функции constexpr должно удовлетворять следующим ограничениям:
-
он не должен быть виртуальным (10.3);
-
его тип возврата должен быть литеральным типом;
-
каждый из его типов параметров должен быть литеральным типом;
-
его тело-функция должно быть = delete, = default или составной оператор, который не содержит
который, как мы видим, не запрещает throw
и фактически запрещает очень мало, так как Расслабляющие ограничения на функции constexpr предложили стать частью C++ 14.
Ниже мы видим правило, в котором говорится, что функция constexpr корректна, если существует хотя бы одно значение аргумента, так что оно может быть оценено как подвыражение основного константного выражения:
Для несимвольной, не дефолтной функции constexpr или не-шаблона, не дефолтного, не наследующего constexpr constructor, , если значения аргумента отсутствуют, так что вызов функции или конструктора может быть оцененным подвыражением основного константного выражения (5.19), программа плохо сформирована; нет требуется диагностика.
и ниже этого параграфа мы имеем следующий пример, который показывает прекрасный пример для этого случая:
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
Таким образом, мы ожидаем вывод для следующего примера:
#include <iostream>
constexpr int f(bool b) { return b ? throw 0 : 0; }
int main() {
std::cout << noexcept( f(1) ) << "\n"
<< noexcept( f(0) ) << "\n" ;
}
быть (видеть его в прямом эфире с gcc):
0
1
Visual Studio через webcompiler также дает тот же результат. Как отмечал hvd, у clang есть ошибка, о чем свидетельствует отчет об ошибке noexcept должен проверить, является ли выражение постоянным выражением.
Отчет об ошибках 1129
Отчет о дефектах 1129: Default nothrow для функций constexpr задает тот же вопрос:
Функция constexpr не может возвращаться через исключение. Это должно быть распознано, а функция, объявленная constexpr без явной спецификации исключения, должна обрабатываться как объявленная noexcept (true), а не обычная noexcept (false). Для шаблона функции, объявленного constexpr без явной спецификации исключения, его следует считать noexcept (true) тогда и только тогда, когда ключевое слово constexpr соблюдается при заданном экземпляре.
и ответ был:
Посылка неверна: исключение запрещено только при вызове функции constexpr в контексте, который требует постоянного выражения. Используется как обычная функция, он может бросать.
и он изменил 5.3.7 [expr.unary.noexcept] пункт 3 bullet 1 (добавление отмечено с акцентом):
потенциально оцениваемый вызов80 для функции, функции-члена, указателя функции или указателя функции-члена, который не имеет спецификацию исключения без бросания (15.4 [except.spec]), , если вызов не является константой выражение (5.20 [expr.const]),
Ответ 2
Говорят noexcept
, что:
Результат является ложным, если выражение содержит [...] вызов любого типа функции, которая не имеет спецификацию исключения без метаданных, если только это не является постоянным выражением.
Кроме того, около constexpr
, верно, что:
оператор noexcept всегда возвращает true для константного выражения
Ни при каких обстоятельствах это не означает, что спецификатор constexpr
заставляет спецификатор noexcept
для окружающего выражения, как кто-то показывал в комментариях контрпример, и вы также проверяли.
В любом случае из документации есть следующая интересная заметка о связи между noexcept
и constexpr
:
Поскольку оператор noexcept всегда возвращает true для константного выражения, его можно использовать для проверки того, принимает ли конкретный вызов функции constexpr ветку постоянного выражения
EDIT: пример с GCC
Благодаря @hvd за его интересный комментарий/пример с GCC о моей последней цитате.
constexpr int f(int i) {
return i == 0 ? i : f(i - 1);
}
int main() {
noexcept(f(512));
return noexcept(f(0)) == noexcept(f(0));
}
В приведенном выше коде возвращается 0
с предупреждением о том, что оператор noexcept(f(512))
не имеет эффекта.
Комментируя это утверждение, которое якобы не имеет эффекта, возвращаемое значение изменяется на 1
.
EDIT: известная ошибка в clang
Опять же, благодаря @hvd также для этой ссылки, то есть о известной ошибке в clang относительно кода, упомянутого в вопросе.
Цитата из отчета об ошибке:
В книге С++, [expr.unary.noexcept] p3:
"Результат оператора noexcept является ложным, если в потенциально-оцененном контексте выражение будет содержать потенциально оцениваемый вызов функции, функции-члена, указателя функции или указателя функции-члена, который не имеет небрасывающего исключение-спецификация (15.4), если вызов не является постоянным выражением (5.19)".
Мы не реализуем эту последнюю фразу.
Ответ 3
Нет, вообще нет.
Функция constexpr может быть вызвана в контексте non-constepr, в котором разрешено исключать исключение (за исключением, конечно, если вы вручную указали его как noexcept(true)
).
Однако, как часть константного выражения (например, в вашем примере), он должен вести себя так, как если бы он был указан noexcept(true)
(конечно, если оценка выражения приведет к тому, что исключение будет выбрано, t приведет к вызову std::terminate
, поскольку программа еще не запущена, но вместо этого приводит к ошибке времени компиляции).
Как я и ожидал, ваш пример не вызывает статическое утверждение с MSVC и g++. Я не уверен, что это ошибка в clang, или я пропускаю что-то в стандарте.
Ответ 4
Вам разрешено вызывать исключение в функции constexpr. Он сконструирован таким образом, что разработчик может указать ошибку компилятору. Учтите, что у вас есть следующая функция:
constexpr int fast_sqrt(int x) {
return (x < 0) ? throw invalid_domain() : fast_sqrt_impl(x);
}
В этом случае, если x отрицательно, нам нужно немедленно прекратить компиляцию и указать проблему пользователю с ошибкой компилятора. Это следует за тем, что ошибки компилятора лучше, чем ошибки времени выполнения (сбой быстрый).
Стандарт С++ говорит об этом в (5.20):
Условное выражение e является выражением постоянной константы, если оценка e, следуя правилам абстрактная машина (1.9), оценила бы одно из следующих выражений:
- выражение throw (5.17)