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

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

#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 функция будет оцениваться во время компиляции?

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

8 ответов

Решение

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

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

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

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

Например:

constexpr int len1 = length("abcd") ;

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

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

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

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

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

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

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

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

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

Взгляните на этот пример кода на ideone.

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

Например:

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

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

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

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

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

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

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

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

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

очень простой:

sizeof("myStringLiteral") делает работу.

в sizeofВнутренняя функция компилятора оценивается гарантированно во время компиляции. Это мощная функция компилятора, которую часто недооценивают. Он работает на C ++, а также на C.

Примечание: вам может потребоваться преобразовать из size_t в int и вычесть 1, оба они также выполняются во время компиляции:

int test_sizeof_text = (int)(sizeof("1234567")-1);

sizeof("text") - это размер, включая завершающий 0, следовательно, -1 для количества символов.

Начиная с C++20 вы можете использовать consteval вместо constexpr для принудительного выполнения функции во время компиляции.

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

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

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

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