Обчислювальна довжина рядка C під час компіляції. Це справді constexpr?


94

Я намагаюся обчислити довжину рядкового літералу під час компіляції. Для цього я використовую такий код:

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

Якщо це правда, для мене просто відкрилися двері для обчислень строкових літералів часу компіляції ... наприклад, я можу обчислювати хеші під час компіляції та багато іншого ...


3
Поки параметр є постійним виразом, він повинен бути.
chris

1
@chris Чи є гарантія того, що щось, що може бути константним виразом, має бути оцінене під час компіляції, коли воно використовується в контексті, який не вимагає постійного виразу?
TC

12
До речі, в тому числі, <cstdio>а потім дзвінки ::printfне є портативними. Стандарт вимагає лише <cstdio>надати std::printf.
Ben Voigt

1
@BenVoigt Добре, дякую, що вказав на це :) Спочатку я використовував std :: cout, але сформований код був досить великим, щоб знайти фактичні значення :)
Mircea Ispas

3
@Felics Я часто використовую godbolt, відповідаючи на питання, що стосуються оптимізації та використання, printfможе призвести до значно меншої кількості коду.
Шафік Ягмор

Відповіді:


76

Постійні вирази не гарантовано обчислюються під час компіляції, ми маємо лише ненормативну цитату з проекту стандартного розділу C ++, 5.19 Постійні вирази, де це сказано:

[...]> [Примітка: Постійні вирази можна обчислювати під час перекладу - кінцева примітка]

Ви можете призначити результат constexprзмінній, щоб переконатися, що він обчислюється під час компіляції, ми можемо побачити це з посилання на Bjarne Stroustrup C ++ 11, в якому сказано ( наголос мій ):

Окрім того, що ми можемо оцінювати вирази під час компіляції, ми хочемо мати можливість вимагати, щоб вирази оцінювались під час компіляції; constexpr перед визначенням змінної робить це (і передбачає const):

Наприклад:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup дає короткий опис того, коли ми можемо забезпечити компіляцію оцінки часу в цьому записі в блозі isocpp, і каже:

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

Отже, це окреслює два випадки, коли його слід оцінювати під час компіляції:

  1. Використовуйте його там, де потрібен константний вираз, здається, це десь у проекті стандарту, де використовується фраза shall be ... converted constant expressionабо shall be ... constant expression, наприклад, пов’язаний масив.
  2. Використовуйте його для ініціалізації а, constexprяк я зазначив вище.

4
Тим не менш, в принципі компілятор має право бачити об'єкт з внутрішнім зв'язком або взагалі не мати з ним зв'язку constexpr int x = 5;, зауважити, що він не вимагає значення під час компіляції (припускаючи, що він не використовується як параметр шаблону чи що інше) і фактично видає код, який обчислює початкове значення під час виконання, використовуючи 5 безпосередніх значень 1 та 4 операції додавання. Більш реалістичний приклад: компілятор може досягти межі рекурсії та відкласти обчислення до часу виконання. Якщо ви не зробите щось, що змусить компілятор насправді використовувати значення, "гарантоване оцінювання під час компіляції" - це проблема якості інформації.
Steve Jessop

@SteveJessop Bjarne, схоже, використовує концепцію, яка не має аналога, який я можу знайти в проекті стандарту, який використовується як засіб постійного вираження, що оцінюється при перекладі. Отже, здається, що стандарт прямо не говорить про те, що він говорить, тому я, як правило, погоджуюся з вами. Хоча і Бьярн, і Херб, схоже, погоджуються з цим, що може свідчити про те, що це просто недостатньо визначено.
Шафік Ягмур,

2
Я думаю, що вони обидва розглядають лише "поважних до себе компіляторів", на відміну від гіпотези, що відповідає стандартам, але навмисно заважає компілятору. Це корисно як міркування про те, що насправді гарантує стандарт , а не багато іншого ;-)
Стів Джессоп,

3
@SteveJessop Свідомо обструктивні компілятори, такі як сумнозвісний (і, на жаль, неіснуючий) Hell ++. Така річ насправді була б чудовою для перевірки відповідності / портативності.
Енджу більше не пишається SO

Згідно з правилом "як би", навіть використання значення як уявної постійної часу компіляції недостатньо: компілятор може безкоштовно доставити копію вашого джерела та перекомпілювати її під час виконання, або виконати обчислення ру ти, щоб визначити тип змінну, або просто безглуздо повторювати свій constexprрозрахунок з чистого зла. Навіть безкоштовно чекати 1 секунду на персонажа в даному рядку джерела, або взяти заданий рядок джерела і використовувати його, щоб поставити шахову позицію, а потім грати обидві сторони, щоб визначити, хто виграв.
Якк - Адам Неврамонт

27

Дійсно легко з'ясувати, чи викликає виклик constexprфункції основний константний вираз, чи це просто оптимізація:

Використовуйте його в контексті, де потрібен постійний вираз.

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

4
... і компілюйте -pedantic, якщо ви використовуєте gcc. В іншому випадку ви не отримуєте жодних попереджень та помилок
BЈович

@ BЈовић Або використовуйте його в контексті, де GCC не має розширень, які потенційно можуть заважати, наприклад, аргументу шаблону.
Енджу більше не пишається ТО

Чи не був би хак з переліком надійнішим? Такі як enum { Whatever = length("str") }?
гострий зуб

18
Варто згадатиstatic_assert(length("str") == 3, "");
chris

8
constexpr auto test = /*...*/;є, мабуть, найбільш загальним і прямим.
TC

19

Тільки зауважимо, що сучасні компілятори (наприклад gcc-4.x) роблять strlenдля рядкових літералів під час компіляції, оскільки це зазвичай визначається як внутрішня функція . Без увімкненої оптимізації. Хоча результат - не константа часу компіляції.

Наприклад:

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

Призводить до:

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

Зауважте, це працює, оскільки strlenце вбудована функція, якщо ми використовуємо -fno-builtinsїї, повертається до виклику під час виконання, дивіться її в прямому ефірі
Шафік Ягмур

strlenце constexprдля мене, навіть з -fno-nonansi-builtins(здається, -fno-builtinsбільше не існує в g ++). Я кажу "constexpr", тому що я можу це зробити, template<int> void foo();і foo<strlen("hi")>(); g ++ - 4.8.4
Аарон Макдейд

18

Дозвольте мені запропонувати іншу функцію, яка обчислює довжину рядка під час компіляції, не будучи рекурсивною.

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

Погляньте на цей зразок коду на Ideone .


4
Це може бути не рівне strlen через вбудований '\ 0': strlen ("hi \ 0there")! = Length ("hi \ 0there")
unkulunkulu

Це правильний шлях, це приклад у Effective Modern C ++ (якщо я правильно пам’ятаю). Однак є хороший клас рядків, який цілком constexpr. Подивіться на цю відповідь: str_const Скотта Шурра , можливо, це буде більш корисним (і менше стилем C).
QuantumKarl


зараз ти робиш: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Пабло Аріель

7

Немає гарантії, що constexprфункція оцінюється під час компіляції, хоча будь-який розумний компілятор буде робити це на відповідних рівнях оптимізації. З іншого боку, параметри шаблону повинні оцінюватися під час компіляції.

Я використав наступний фокус, щоб змусити оцінку під час компіляції. На жаль, він працює лише з інтегральними значеннями (тобто не зі значеннями з плаваючою комою).

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

Тепер, якщо ви пишете

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

Ви можете бути впевнені, що ifоператор - це константа часу компіляції без накладних витрат на час виконання.


8
або просто використовуйте std :: integral_constant <int, length (...)> :: value
Mircea Ispas

1
Приклад є трохи безглуздим використанням, тому що будь- lenякий constexprзасіб lengthвсе одно потрібно оцінювати під час компіляції.
chris

@ Кріс , я не знаю , що це повинно бути, хоча я помітив , що це з моїм компілятором.
5gon12eder

Гаразд, згідно з більшістю інших відповідей він повинен, тому я змінив приклад, щоб бути менш безглуздим. Насправді, це була ifумова (де дуже важливо, щоб компілятор виконував усунення мертвого коду), для якого я спочатку використовував фокус.
5gon12eder

1

Коротке пояснення із запису Вікіпедії про узагальнені константні вирази :

Використання constexpr для функції накладає певні обмеження на те, що ця функція може зробити. По-перше, функція повинна мати недійсний тип повернення. По-друге, тіло функції не може оголошувати змінні або визначати нові типи. По-третє, тіло може містити лише декларації, нульові оператори та один оператор повернення. Повинні існувати значення аргументів, щоб після заміни аргументу вираз у операторі return давав постійний вираз.

Наявність constexprключового слова перед визначенням функції вказує компілятору перевірити, чи виконуються ці обмеження. Якщо так, і функція викликається з константою, повернене значення гарантується постійним і, отже, може використовуватися де завгодно, коли потрібен константний вираз.


Ці умови не гарантують, що повернуте значення є постійним . Наприклад, функцію можна викликати з іншими значеннями аргументів.
Ben Voigt

Правильно, @BenVoigt. Я редагував його для виклику з постійним виразом.
kaedinger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.