Вычисление длины строки C во время компиляции. Это действительно констебр?

Я пытаюсь вычислить длину строкового литерала во время компиляции. Для этого я использую следующий код:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

Все работает так, как ожидалось, программа печатает 4 и 8. Код сборки, сгенерированный clang, показывает, что результаты вычисляются во время компиляции:

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

Мой вопрос: гарантируется ли стандартом, что функция length будет оцениваться временем компиляции?

Если это правда, то дверь для компиляции времени строковых литералов вычислений только открылась для меня... например, я могу вычислить хэши во время компиляции и многое другое...

Ответ 1

Константные выражения не гарантируются для оценки во время компиляции, у нас есть только ненормативная цитата из проекта стандарта С++ 5.19 Константные выражения, которые говорят об этом хотя:

[...] > [Примечание: константные выражения могут быть оценены во время перевод.-end note]

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

Помимо возможности оценивать выражения во время компиляции, мы хотите иметь возможность требовать выражения, подлежащие оценке при компиляции время; constexpr перед определением переменной делает это (и означает const):

Например:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup дает краткое изложение того, когда мы можем заверить оценку времени компиляции в этой записи isocpp и говорит:

[...] Правильный ответ - как указано Herb - это то, что согласно стандарту функция constexpr может оцениваться во время компиляции или времени выполнения, если оно не используется как постоянное выражение, и в этом случае оно должно быть оценено в время компиляции. Чтобы гарантировать оценку времени компиляции, мы должны либо использовать где требуется постоянное выражение (например, в качестве привязки массива или как ярлык case) или использовать его для инициализации constexpr. Я бы надеялся что ни один уважающий себя компилятор не упустит оптимизацию возможность сделать то, что я изначально сказал: "Функция constexpr оценивается во время компиляции, если все его аргументы постоянны Выражения".

Итак, это описывает два случая, когда их следует оценивать во время компиляции:

  • Используйте его там, где требуется постоянное выражение, это может быть где угодно в проекте стандарта, где используется фраза shall be ... converted constant expression или shall be ... constant expression, такая как привязка к массиву.
  • Используйте его для инициализации constexpr, как я выше описывал.

Ответ 2

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

Используйте его в контексте, где требуется постоянное выражение.

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}

Ответ 3

Просто обратите внимание, что современные компиляторы (например, gcc-4.x) выполняют strlen для строковых литералов во время компиляции, потому что они обычно определяются как внутренняя функция. Без оптимизации. Хотя результат не является постоянной времени компиляции.

например:.

printf("%zu\n", strlen("abc"));

Результаты в:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf

Ответ 4

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

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

Посмотрите на этот образец кода на ideone.

Ответ 5

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

Я использовал следующий трюк для принудительной оценки во время компиляции. К сожалению, он работает только со встроенными значениями (т.е. Не с плавающей запятой).

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

Теперь, если вы пишете

if (static_eval<int, length("hello, world")>::value > 7) { ... }

вы можете быть уверены, что оператор if - это константа времени компиляции без накладных расходов во время выполнения.

Ответ 6

Краткое объяснение из статьи в Википедии Обобщенные константные выражения:

Использование constexpr для функции накладывает некоторые ограничения на то, что может выполнять эта функция. Во-первых, функция должна иметь непустой тип возврата. Во-вторых, тело функции не может объявлять переменные или определять новые типы. В-третьих, тело может содержать только декларации, нулевые утверждения и один оператор возврата. Должны существовать значения аргументов, так что после замены аргумента выражение в операторе return выражает константное выражение.

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