Попытка реализовать приятный (простой, простой, без TMP, без макросов, без нечитаемого извилистого кода, без странного синтаксиса при его использовании) хэш времени компиляции с помощью пользовательских литералов, я обнаружил, что, очевидно, GCC понимает, что такое константное выражение: сильно отличается от моего понимания.
Поскольку вывод кода и компилятора говорят более тысячи слов, без лишних слов:
#include <cstdio>
constexpr unsigned int operator"" _djb(const char* const str, unsigned int len)
{
static_assert(__builtin_constant_p(str), "huh?");
return len ? str[0] + (33 * ::operator"" _djb(str+1, len-1)) : 5381;
}
int main()
{
printf("%u\n", "blah"_djb);
return 0;
}
Код довольно простой, не так много, чтобы объяснить, и не так много, чтобы спросить — за исключением того, что он не оценивает во время компиляции. Я попытался использовать разыменование указателя вместо использования индекса массива, а также прерывания рекурсии в !*str
, все к тому же результату.
static_assert
был добавлен позже, когда ловил рыбу в неспокойных водах, почему хэш просто не мог быть оценен во время компиляции, когда я твердо верил, что это должно быть. Что ж, удивительно, это только озадачило меня больше, но ничего не прояснило! Оригинальный код, без static_assert
, хорошо принят и компилируется без предупреждений (gcc 4.7.2).
Выход компилятора:
[...]\main.cpp: In function 'constexpr unsigned int operator"" _djb(const char*, unsigned int)':
[...]\main.cpp:5:2: error: static assertion failed: huh?
Насколько я понимаю, строковый литерал, ну … буквальный. Другими словами, константа времени компиляции. В частности, это известная во время компиляции последовательность постоянных символов, начинающаяся с постоянного адреса, назначенного компилятором (и, таким образом, известного), оканчивающегося на '\0'
, Это логически подразумевает, что длина, вычисляемая компилятором литерала, предоставляется operator""
это constexpr
также.
Кроме того, я понимаю, что вызов constexpr
Функция только с параметрами времени компиляции делает ее элегантной в качестве инициализатора для перечисления или в качестве параметра шаблона, другими словами, это должно привести к вычислению во время компиляции.
Конечно в принципе всегда допустимый для компилятора, чтобы оценить constexpr
функция во время выполнения, но возможность переместить оценку во время компиляции constexpr
, в конце концов.
Где моя ошибка, и есть ли способ реализовать пользовательский литерал, который может принимать строковый литерал, чтобы он действительно вычислялся во время компиляции?
Возможно актуальные похожие вопросы:
Может ли строковый литерал быть подписан в константном выражении?
Пользовательские литеральные аргументы не являются constexpr?
Первый, кажется, предполагает, что по крайней мере для char const (&str)[N]
это работает, и GCC принимает это, хотя я по общему признанию не могу следовать за заключением.
Второй использует целочисленные литералы, а не строковые литералы, и, наконец, решает проблему с помощью шаблонного метапрограммирования (что мне не нужно). Так что, видимо, проблема не ограничивается строковыми литералами?
У меня нет GCC 4.7.2, чтобы попробовать, но ваш код без статического утверждения (подробнее об этом позже) хорошо компилируется и выполняет функцию во время компиляции с обоими GCC 4.7.3 а также GCC 4.8. Я думаю, вам придется обновить свой компилятор.
Компилятору не всегда разрешается перемещать оценку во время выполнения: некоторые контексты, такие как аргументы шаблона, и static_assert
, требует оценки во время компиляции или ошибка, если это невозможно. Если вы используете свой UDL в static_assert
если это возможно, вы заставите компилятор оценить его во время компиляции. В обоих моих тестах это так.
Теперь, чтобы __builtin_constant_p(str)
, Для начала, как задокументировано, __builtin_constant_p
может выдавать ложные негативы (то есть иногда может возвращать 0 для константных выражений).
str
не является доказуемо константным выражением, потому что это аргумент функции. Вы можете заставить компилятор оценивать функцию во время компиляции в некоторых контекстах, но это не значит, что это может никогда оцените его во время выполнения: некоторые контексты никогда не вызывают принудительную оценку во время компиляции (и фактически, в некоторых из этих контекстов оценка во время компиляции просто невозможна). str
может быть непостоянным выражением.
Статические утверждения проверяются, когда компилятор видит функцию, а не один раз для каждого вызова, который видит компилятор. Это делает тот факт, что вы всегда называете это в контексте времени компиляции, не имеет значения: имеет значение только тело. Так как str
иногда может быть непостоянным выражением, __builtin_constant_p(str)
в этом контексте не может быть правдой: он может производить ложные отрицания, но он не производит ложных срабатываний.
Чтобы было понятнее: static_assert(__builtin_constant_p("blah"), "")
пройдет (ну, теоретически это может быть неудачей, но я сомневаюсь, что компилятор выдаст ложный отрицательный результат здесь), потому что "blah"
является всегда постоянное выражение, но str
не то же самое выражение, что и "blah"
,
Для полноты, если рассматриваемый аргумент был числового типа (подробнее об этом позже), и вы сделали тест вне статического утверждения, ты мог бы получить тест, чтобы вернуть истину если вы передали константу, и false, если вы передали не константу. В статическом утверждении это всегда терпит неудачу.
Но! Документы за __builtin_constant_p
раскрыть одну интересную деталь:
Однако если вы используете его во встроенной функции и передаете аргумент функции в качестве аргумента встроенному, GCC никогда не вернет 1, когда вы вызываете встроенную функцию со строковой константой или составным литералом (см. Составные литералы) и не вернет 1, когда вы передадите постоянное числовое значение встроенной функции, если вы не укажете опцию -O.
Как видите, у встроенного есть ограничение делает тест всегда возвращаемым если данное выражение является строковой константой.
Других решений пока нет …