Какая разница между constexpr
и const
?
- Когда я могу использовать только один из них?
- Когда я могу использовать оба и как выбрать один из них?
Какая разница между constexpr
и const
?
Оба ключевых слова могут быть использованы в объявлении объектов, а также функций. Основное отличие применительно к объектам заключается в следующем:
const
объявляет объект как константу. Это подразумевает гарантию того, что после инициализации значение этого объекта не изменится, и компилятор может использовать этот факт для оптимизации. Это также помогает запретить программисту писать код, который изменяет объекты, которые не должны были изменяться после инициализации.
constexpr
объявляет объект как пригодный для использования в том, что стандарт называет константными выражениями. Но учтите, что constexpr
- не единственный способ сделать это.
Применительно к функциям основное отличие заключается в следующем:
const
может использоваться только для нестатических функций-членов, но не для функций в целом. Это дает гарантию, что функция-член не изменяет ни один из нестатических элементов данных.
constexpr
может использоваться как с членами, так и с не членами, а также с конструкторами. Он объявляет функцию пригодной для использования в константных выражениях. Компилятор примет его, только если функция соответствует определенным критериям (7.1.5/3,4), наиболее важно (†):
return
. В случае конструктора разрешены только список инициализации, typedefs и static assert. (= default
и = delete
тоже разрешены.)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
.
const
применяется для переменных, а предотвращает их изменение в вашем коде.
constexpr
сообщает компилятору, что это выражение приводит к значению времени компиляции, поэтому его можно использовать в таких местах, как длины массивов, назначая const
переменные и т.д. Ссылка заданная Оли, имеет много отличных примеров.
В принципе, они вообще представляют собой два разных понятия и могут (и должны) использоваться вместе.
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 для повышения безопасности, производительности и инкапсуляции в С++.
В соответствии с книгой "Язык программирования С++ 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).
И 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.
Как уже указывалось @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
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 **
определенных вещей во время компиляции для улучшения производительности во время выполнения.
Прежде всего, оба являются классификаторами в c++. Объявленная переменная const должна быть инициализирована и не может быть изменена в будущем. Следовательно, обычно переменная, объявленная как const, будет иметь значение даже до компиляции.
Но для constexpr это немного другое.
Для constexpr вы можете указать выражение, которое можно было бы оценить во время компиляции программы.
Очевидно, что переменная, объявленная как constexper, не может быть изменена в будущем так же, как const.