Почему constexpr работает на нечистые функции

во время просмотра одного из моих старых вопросов о constexpr я наткнулся на очень важный комментарий (IMHO). В основном это сводится к: (это законно С++ 11:()

 constexpr double f(bool b)
 {
 return b? 42:42/(rand()+1); // how pure is rand ;)
 }

Мой вопрос в том, в чем причина, по которой это допускается стандартом. Поскольку я большой поклонник ссылочной прозрачности, я надеюсь, что у них есть веская причина:), и я хотел бы это знать.

Кстати, есть связанный Q, но большая часть A даже не упоминает чистую вещь, или когда они это делают, они не указывают аргументацию, почему std позволяет это. Связь между constexpr и чистыми функциями

Ответ 1

В стандарте соответствующее требование закладывается ниже основного списка требований для функций constexpr. Это в §7.1.5/5:

Для функции constexpr, если нет значений аргументов функции, так что подстановка функции вызовет постоянное выражение (5.19), программа плохо сформирована; не требуется диагностика.

В §5.19 определяются требования для постоянных выражений, так что вы не можете вызвать rand().

Ослабленное ограничение позволяет вам иметь функции, которые являются условно чистыми. Ваш пример f(true) является допустимым аргументом шаблона, но f(false) не является.

Недостатком, конечно же, является то, что компилятор не будет проверять, что функция constexpr действительно может использоваться по назначению. Вам нужно написать тестовые примеры.

А, латб-ответ также верен. (Но этот текст сформулирован более просто.)

Ответ 2

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

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

Пример

constexpr int add(int a, int b) { return a + b; } //pure!

const int a = 2, b = 3; //const
int c = 2, d = 3;       //non-const

//we may read update c and d here!

const int v1 = add(2,3);  //computed at compile-time
const int v2 = add(a,3);  //computed at compile-time
const int v3 = add(2,b);  //computed at compile-time
const int v4 = add(a,b);  //computed at compile-time

const int v3 = add(c,3);  //computed at runtime
const int v3 = add(c,b);  //computed at runtime
const int v3 = add(a,d);  //computed at runtime
const int v3 = add(c,d);  //computed at runtime

Обратите внимание, что здесь add является чистой функцией, независимо от того, вычисляется ли она во время компиляции или времени выполнения.

Ответ 3

Потому что для некоторой области входных параметров нечистый путь никогда не будет принят. Для этого домена constexpr будет работать нормально.

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

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