Разница между `constexpr` и` const`

Какая разница между constexpr и const?

  • Когда я могу использовать только один из них?
  • Когда я могу использовать оба и как выбрать один из них?

Ответы

Ответ 1

Основное значение и синтаксис

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

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

  • constexpr объявляет объект как пригодный для использования в том, что стандарт называет константными выражениями. Но учтите, что constexpr - не единственный способ сделать это.

Применительно к функциям основное отличие заключается в следующем:

  • const может использоваться только для нестатических функций-членов, но не для функций в целом. Это дает гарантию, что функция-член не изменяет ни один из нестатических элементов данных.

  • constexpr может использоваться как с членами, так и с не членами, а также с конструкторами. Он объявляет функцию пригодной для использования в константных выражениях. Компилятор примет его, только если функция соответствует определенным критериям (7.1.5/3,4), наиболее важно (†):

    • Тело функции должно быть не виртуальным и чрезвычайно простым: кроме typedefs и статических утверждений, допускается только один оператор return. В случае конструктора разрешены только список инициализации, typedefs и static assert. (= default и = delete тоже разрешены.)
    • Начиная с С++ 14, правила более смягчены, что разрешено с тех пор внутри функции constexpr: объявление asm, goto, оператор с меткой, отличной от case и default, try-block, определение переменной non -литеральный тип, определение переменной статической или длительности хранения потока, определение переменной, для которой не выполняется инициализация.
    • Аргументы и возвращаемый тип должны быть литеральными типами (то есть, вообще говоря, очень простыми типами, обычно скалярами или агрегатами).

Постоянные выражения

Как сказано выше, constexpr объявляет оба объекта, а также функции, пригодные для использования в константных выражениях. Постоянное выражение не просто константа:

  • Его можно использовать в местах, где требуется оценка во время компиляции, например, параметры шаблона и спецификаторы размера массива:

    template<int N>
    class fixed_size_list
    { /*...*/ };
    
    fixed_size_list<X> mylist;  // X must be an integer constant expression
    
    int numbers[X];  // X must be an integer constant expression
    
  • Но обратите внимание:

    • Объявление чего-либо как constexpr не обязательно гарантирует, что оно будет оценено во время компиляции. Он может использоваться для таких целей, но может использоваться и в других местах, которые оцениваются также во время выполнения.

    • Объект может быть пригоден для использования в константных выражениях без объявления constexpr. Пример:

      int main()
      {
        const int N = 3;
        int numbers[N] = {1, 2, 3};  // N is constant expression
      }
      

    Это возможно, потому что N, будучи постоянным и инициализированным во время объявления с литералом, удовлетворяет критериям для постоянного выражения, даже если оно не объявлено constexpr.

Так, когда я действительно должен использовать constexpr?

  • Объект, подобный N выше, может использоваться как константное выражение, не будучи объявленным constexpr. Это верно для всех объектов, которые:

    • const
    • целочисленного или перечислимого типа и
    • инициализируется во время объявления выражением, которое само является константным выражением

    [Это связано с §5.19/2: константное выражение не должно включать в себя подвыражения, которые включают в себя "модификацию lvalue-to-rvalue, если […] glvalue целочисленного или перечислимого типа […]" Спасибо Ричарду Смиту за исправление моего ранее утверждали, что это верно для всех литеральных типов.]

  • Чтобы функция была пригодна для использования в константных выражениях, она должна быть явно объявлена constexpr; недостаточно просто удовлетворять критериям для функций с постоянным выражением. Пример:

    template<int N>
    class list
    { };
    
    constexpr int sqr1(int arg)
    { return arg * arg; }
    
    int sqr2(int arg)
    { return arg * arg; }
    
    int main()
    {
      const int X = 2;
      list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
      list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
    }
    

Когда я/я должен использовать оба, const и constexpr вместе?

А. В объявлениях объекта. Это никогда не требуется, когда оба ключевых слова ссылаются на один и тот же объект, который должен быть объявлен. constexpr подразумевает const.

constexpr const int N = 5;

такой же как

constexpr int N = 5;

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

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

Здесь NP объявляется как адресное выражение-константа, то есть указатель, который сам является константным выражением. (Это возможно, когда адрес генерируется путем применения оператора адреса к выражению статической/глобальной константы.) Здесь constexpr и constexpr и const: constexpr всегда ссылается на объявленное выражение (здесь NP), тогда как const ссылается на int (он объявляет указатель на const). Удаление const сделает выражение недопустимым (потому что (a) указатель на неконстантный объект не может быть константным выражением, а (b) &N фактически является указателем на константу).

Б. В объявлениях функций-членов. В С++ 11 constexpr подразумевает const, тогда как в С++ 14 и С++ 17 это не так. Функция-член, объявленная в С++ 11 как

constexpr void f();

должен быть объявлен как

constexpr void f() const;

под С++ 14 для того, чтобы все еще использоваться как функция const.

Ответ 2

const применяется для переменных, а предотвращает их изменение в вашем коде.

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

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

Ответ 3

Обзор

  • const гарантирует, что программа не изменит значение объекта. Однако const не гарантирует, какой тип инициализации объект претерпевает.

    Рассмотрим:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization
    

    Функция max() просто возвращает буквальное значение. Однако, поскольку инициализатор является вызовом функции, mx подвергается инициализации во время выполнения. Поэтому вы не можете использовать его как постоянное выражение:

    int arr[mx];  // error: "constant expression required"
    
  • constexpr - это новое ключевое слово С++ 11, которое избавляет вас от необходимости создавать макросы и жестко закодированные литералы. Это также гарантирует при определенных условиях, что объекты подвергаются статической инициализации. Он контролирует время оценки выражения. Выполняя оценку времени компиляции своего выражения, constexpr позволяет вам определять истинные константные выражения, которые имеют решающее значение для критически важных приложений, системного программирования, шаблонов и, вообще говоря, в любом коде, который опирается на константы времени компиляции.

Функции константного выражения

Функция константного выражения представляет собой объявленную функцию constexpr. Его тело должно быть не виртуальным и состоять только из одного оператора return, кроме typedefs и статических утверждений. Его аргументы и возвращаемое значение должны иметь литералы. Он может использоваться с аргументами non-constant-expression, но когда это делается, результат не является постоянным выражением.

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

constexpr int max() { return INT_MAX; }           // OK
constexpr long long_max() { return 2147483647; }  // OK
constexpr bool get_val()
{
    bool res = false;
    return res;
}  // error: body is not just a return statement

constexpr int square(int x)
{ return x * x; }  // OK: compile-time evaluation only if x is a constant expression
const int res = square(5);  // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y);          // OK: runtime evaluation of square(y)

Объекты с постоянным выражением

Объектом константного выражения является объявленный объект constexpr. Он должен быть инициализирован константным выражением или значением rvalue, созданным конструктором константного выражения с аргументами константного выражения.

Объект с постоянным выражением ведет себя так, как если бы он был объявлен const, за исключением того, что он требует инициализации перед использованием, а его инициализатор должен быть постоянным выражением. Следовательно, объект константного выражения всегда может использоваться как часть другого константного выражения.

struct S
{
    constexpr int two();      // constant-expression function
private:
    static constexpr int sz;  // constant-expression object
};
constexpr int S::sz = 256;
enum DataPacket
{
    Small = S::two(),  // error: S::two() called before it was defined
    Big = 1024
};
constexpr int S::two() { return sz*2; }
constexpr S s;
int arr[s.two()];  // OK: s.two() called after its definition

Конструкторы константных выражений

Конструктор константных выражений является объявленным конструктором constexpr. Он может иметь список инициализации членов, но его тело должно быть пустым, кроме typedefs и static asserts. Его аргументы должны иметь буквальные типы.

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

struct complex
{
    // constant-expression constructor
    constexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body
    // constant-expression functions
    constexpr double real() { return re; }
    constexpr double imag() { return im; }
private:
    double re;
    double im;
};
constexpr complex COMP(0.0, 1.0);         // creates a literal complex
double x = 1.0;
constexpr complex cx1(x, 0);              // error: x is not a constant expression
const complex cx2(x, 1);                  // OK: runtime initialization
constexpr double xx = COMP.real();        // OK: compile-time initialization
constexpr double imaglval = COMP.imag();  // OK: compile-time initialization
complex cx3(2, 4.6);                      // OK: runtime initialization

Советы из книги "Эффективный современный С++" Скотта Мейерса о constexpr:

  • constexpr объекты const и инициализируются значениями, известными во время компиляции;
  • constexpr функции выдают результаты компиляции во время вызова с аргументами, значения которых известны во время компиляции;
  • constexpr объекты и функции могут использоваться в более широком диапазоне контекстов, чем объекты и функции не constexpr;
  • constexpr является частью интерфейса объектов или функций.

Источник: Использование constexpr для повышения безопасности, производительности и инкапсуляции в С++.

Ответ 4

В соответствии с книгой "Язык программирования С++ 4th Editon" Бьярне Страуступа
const: подразумевается примерно "Я обещаю не изменять это значение (§7.5). Это используется в первую очередь для указания интерфейсов, чтобы данные могли передаваться в функции, не опасаясь, что они будут изменены.
Компилятор выполняет обещание, сделанное const.
constexpr: значение грубо '' должно быть оценено во время компиляции (§10.4). Это используется в основном для указания констант, чтобы разрешить Например:

const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression

Для функции, которая может использоваться в постоянном выражении, то есть в выражении, которое будет оцениваться компилятором, он должен быть определен constexpr.
Например:

constexpr double square(double x) { return x∗x; }


Чтобы быть constexpr, функция должна быть довольно простой: просто оператор return, вычисляющий значение. Функция constexpr может использоваться для непостоянных аргументов, но когда это делается, результат не является постоянное выражение. Мы разрешаем вызывать функцию constexpr с аргументами не константного выражения в контекстах, которые не требуют постоянных выражений, так что мы не должны определять по существу одна и та же функция дважды: один раз для постоянных выражений и один раз для переменных.
В нескольких местах постоянные выражения требуются с помощью языковых правил (например, границ массива (§2.2.5, §7.3), метки случаев (п. 2.2.4, п. 9.4.2), некоторые аргументы шаблона (§ 25.2) и константы, объявленные с использованием constexpr). В других случаях оценка времени компиляции важна для производительности. Независимо от проблемы производительности, понятие неизменности (объекта с неизменным состоянием) является важная проблема проектирования (§10.4).

Ответ 5

И const и constexpr могут применяться к переменным и функциям. Хотя они похожи друг на друга, на самом деле это очень разные понятия.

И const и constexpr означают, что их значения не могут быть изменены после их инициализации. Так, например:

const int x1=10;
constexpr int x2=10;

x1=20; // ERROR. Variable 'x1' can't be changed.
x2=20; // ERROR. Variable 'x2' can't be changed.

Принципиальным отличием между const и constexpr является время, когда их значения инициализации известны (оценены). Хотя значения переменных const можно вычислять как во время компиляции, так и во время выполнения, constexpr всегда оценивается во время компиляции. Например:

int temp=rand(); // temp is generated by the the random generator at runtime.

const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so 'constexpr' can't be applied here.

Основным преимуществом, чтобы узнать, известно ли значение во время компиляции или во время выполнения, является тот факт, что константы времени компиляции могут использоваться всякий раз, когда необходимы константы времени компиляции. Например, C++ не позволяет вам указывать C-массивы с переменной длиной.

int temp=rand(); // temp is generated by the the random generator at runtime.

int array1[10]; // OK.
int array2[temp]; // ERROR.

Итак, это означает, что:

const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.


int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.

Так const переменные можно определить как компилировать временные константы, такие как size1, которые могут быть использованы для определения размеров массивов и время выполнения константы, такие как size2, которые известна только во время выполнения и не может быть использована для определения размера массива. С другой стороны, constexpr всегда определяет константы времени компиляции, которые могут указывать размеры массива.

Оба const и constexpr могут быть применены к функциям тоже. Функция const должна быть функцией-членом (метод, оператор), где применение ключевого слова const означает, что метод не может изменять значения их полей (не статических). Например.

class test
{
   int x;

   void function1()
   {
      x=100; // OK.
   }

   void function2() const
   {
      x=100; // ERROR. The const methods can't change the values of object fields.
   }
};

constexpr - это другое понятие. Он помечает функцию (член или не член) как функцию, которая может быть оценена во время компиляции, если в качестве аргументов переданы константы времени компиляции. Например, вы можете написать это.

constexpr int func_constexpr(int X, int Y)
{
    return(X*Y);
}

int func(int X, int Y)
{
    return(X*Y);
}

int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.

int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.

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

int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.

constexpr может применяться к функциям-членам (методам), операторам и даже конструкторам. Например.

class test2
{
    static constexpr int function(int value)
    {
        return(value+1);
    }

    void f()
    {
        int x[function(10)];


    }
};

Более "сумасшедший" образец.

class test3
{
    public:

    int value;

    // constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
    constexpr int getvalue() const
    {
        return(value);
    }

    constexpr test3(int Value)
        : value(Value)
    {
    }
};


constexpr test3 x(100); // OK. Constructor is constexpr.

int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.

Ответ 6

Как уже указывалось @0x499602d2, const гарантирует, что после инициализации значение не может быть изменено, когда constexpr (введено в С++ 11) гарантирует, что переменная является постоянной времени компиляции.
Рассмотрим следующий пример (из LearnCpp.com):

cout << "Enter your age: ";
int age;
cin >> age;

const int myAge{age};        // works
constexpr int someAge{age};  // error: age can only be resolved at runtime

Ответ 7

const int var можно динамически установить на значение во время выполнения, и как только оно будет установлено на это значение, его уже нельзя будет изменить.

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

Вот хороший пример:

int main(int argc, char*argv[]) {
    const int p = argc; 
    // p = 69; // cannot change p because it is a const
    // constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time 
    constexpr int r = 2^3; // this works!
    // r = 42; // same as const too, it cannot be changed
}

Приведенный выше фрагмент прекрасно компилируется, и я прокомментировал те, которые приводят к ошибке.

Ключевыми понятиями, на которые следует обратить внимание, являются понятия compile time и run time. В C++ были введены новые инновации, предназначенные для максимально возможного использования ** know ** определенных вещей во время компиляции для улучшения производительности во время выполнения.

Ответ 8

Прежде всего, оба являются классификаторами в c++. Объявленная переменная const должна быть инициализирована и не может быть изменена в будущем. Следовательно, обычно переменная, объявленная как const, будет иметь значение даже до компиляции.

Но для constexpr это немного другое.

Для constexpr вы можете указать выражение, которое можно было бы оценить во время компиляции программы.

Очевидно, что переменная, объявленная как constexper, не может быть изменена в будущем так же, как const.