const vs constexpr на змінних


303

Чи є різниця між наступними визначеннями?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

Якщо ні, то який стиль є кращим у С ++ 11?



Обидва - константа часу компіляції. Але ви можете зробити const_cast першого і написати до нього. Але його буде оптимізовано будь-яким компілятором, оскільки це не впливає на "читання", як це відбувається під час компіляції.
Боніта Монтеро

Відповіді:


347

Я вважаю, що є різниця. Давайте перейменовамо їх, щоб ми могли про них говорити простіше:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

Обидва PI1і PI2постійні, тобто ви не можете їх змінювати. Однак лише PI2 константа часу компіляції. Вона повинна бути ініціалізована під час компіляції. PI1може бути ініціалізовано під час компіляції або час виконання. Крім того, тільки PI2 може бути використаний в контексті , який вимагає постійного часу компіляції. Наприклад:

constexpr double PI3 = PI1;  // error

але:

constexpr double PI3 = PI2;  // ok

і:

static_assert(PI1 == 3.141592653589793, "");  // error

але:

static_assert(PI2 == 3.141592653589793, "");  // ok

Що ви повинні використовувати? Використовуйте те, що відповідає вашим потребам. Ви хочете переконатися, що у вас є константа часу компіляції, яку можна використовувати в контекстах, де потрібна константа часу компіляції? Ви хочете мати можливість ініціалізувати його з обчисленнями, виконаними під час виконання? І т.д.


60
Ти впевнений? Тому що const int N = 10; char a[N];роботи та межі масиву повинні бути константами часу компіляції.
fredoverflow

10
Я впевнений, що стосується написаних нами прикладів (протестував кожен з них перед публікацією). Однак мій компілятор дозволяє мені перетворити PI1на інтегральну константу часу компіляції для використання в масиві, але не для використання як нетиповий параметр інтегрального шаблону. Тож конвертованість часу на компіляцію PI1в цілісний тип здається мені невеликим ударом і недоліком.
Говард Хінант

34
@FredOverflow: Індекси масивів без const "працювали" близько десятиліття (для цього є, наприклад, розширення g ++), але це не означає, що це суто законний C ++ (хоча деякі новітні стандарти C або C ++ зробили це законним , я забув, який). Що стосується відмінностей констант компіляції, параметри шаблону та використання як enumініціалізатора - це єдині дві помітні відмінності між constі constexpr(і жоден з них не працює double).
Деймон

17
Пункт 4 5.19 Постійні вирази [expr.const] також є (ненормативним) зауваженням, яке чудово окреслює, що реалізація може робити арифметику з плаваючою комою інакше (наприклад, щодо точності) під час компіляції, ніж під час виконання. Таким чином , 1 / PI1і 1 / PI2може призводити до різних результатів. Я не думаю, що ця технічність є не такою важливою, як рекомендації у цій відповіді.
Люк Дантон

4
Але constexpr double PI3 = PI1;для мене це працює правильно. (MSVS2013 CTP). Що я роблю неправильно?
NuPagadi

77

Тут немає різниці, але це важливо, коли у вас є тип, який має конструктор.

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

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

Переважно це має значення, коли ініціалізація під час виконання буде трудомісткою, і ви хочете відсунути цю роботу на компілятор, де це також забирає багато часу, але не сповільнює час виконання компільованої програми


3
Я погоджуюсь: висновок, до якого я дійшов, constexprпризвів до встановлення діагнозу, якщо обчислення об'єкта за часом складання неможливо. Що менш зрозуміло, чи може функція, яка очікує постійного параметра, може виконуватися під час компіляції, якщо параметр повинен бути оголошений як, constа не як constexpr: тобто, буде constexpr int foo(S)виконуватися під час компіляції, якщо я дзвоню foo(s0)?
Матьє М.

4
@MatthieuM: Сумніваюся, чи foo(s0)було б виконано під час компіляції, але ти ніколи не знаєш: компілятору дозволено робити такі оптимізації. Звичайно, ні gcc 4.7.2, ні clang 3.2 не дозволяють мені збиратиconstexpr a = foo(s0);
rici

50

constexpr вказує значення, яке є постійним і відомим під час компіляції.
const вказує значення, яке є лише постійним; це не обов'язково знати під час компіляції.

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

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

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

Усі об’єкти constexpr є const, але не всі об'єкти const є constexpr.

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


2
Мені дуже сподобалося ваше пояснення .можливо, будь ласка, прокоментуйте більше про те, де є випадки, які нам можуть знадобитися використовувати константи часу компіляції в сценаріях реального життя.
Маюх Саркар

1
@MayukhSarkar Просто Google C ++ чому constexpr , наприклад stackoverflow.com/questions/4748083 / ...
underscore_d

18

Constexpr символьна константа повинна бути присвоєно значення, яке відоме під час компіляції. Наприклад:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

Для обробки випадків, коли значення "змінної", ініціалізованої зі значенням, яке не відомо на час компіляції, але ніколи не змінюється після ініціалізації, C ++ пропонує другу форму константи ( const ). Наприклад:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

Такі " змінні const " дуже поширені з двох причин:

  1. У C ++ 98 не було constexpr, тому люди використовували const .
  2. Елемент списку "Змінні", які не є постійними виразами (їх значення не відоме під час компіляції), але не змінюють значення після ініціалізації, самі по собі є корисними.

Довідка: "Програмування: принципи та практика використання C ++" від Stroustrup


25
Можливо, вам слід було б згадати, що текст у вашій відповіді взято дослівно з "Програмування: принципи та практика використання C ++" від Stroustrup
Aky
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.