const int forty_two = two_i - four;
Помимо использования константных выражений для инициализации переменных, которые могут использоваться в других константных выражениях, есть ряд случаев, где разрешается применять
• Задание границ массива:
int bounds = 99; │Ошибка, bounds — не константное
int array[bounds];←┘выражение
const int bounds2 = 99;│Правильно, bounds2 — константное
int array2[bounds2]; ←┘выражение
• Задание значения параметра шаблона, не являющего типом:
template
struct test {}; │Ошибка, bounds —
│не константное
testвыражение
testПравильно, bounds2 —
│константное выражение
• Задание непосредственно в определении класса инициализатора для переменной-члена класса целочисленного типа со спецификаторами static const:
class X {
static const int the_answer = forty_two;
};
• Употребление в инициализаторах встроенных типов или агрегатов, применяемых для статической инициализации:
struct my_aggregate {
int a;
int b;
};
static my_aggregate ma1 =│Статическая
{ forty_two, 123 }; ←┘инициализация
int dummy = 257; │Динамическая
static my_aggregate ma2 = {dummy, dummy};←┘инициализация
Такая статическая инициализация полезна для предотвращения зависимости от порядка инициализации и состояний гонки.
Всё это не ново и было описано еще в стандарте С++ 1998 года. Но в новом стандарте появилось и дополнение в части константных выражений — ключевое слово constexpr.
Ключевое слово constexpr применяется главным образом как модификатор функции. Если параметр и возвращаемое функцией значение удовлетворяют определенным условиям, а тело функции достаточно простое, то в ее объявлении можно указать constexpr и использовать функцию в константных выражениях. Например:
constexpr int square(int x) {
return x*x;
}
int array[square(5)];
В этом случае массив array будет содержать 25 значений, потому что функция square объявлена как constexpr. Конечно, из того, что функцию
int dummy = 4; (1) Ошибка, dummy — не константное
int array[square(dummy)];←┘выражение
В этом примере dummy не является константным выражением (1), поэтому не является таковым и square(dummy). Это обычный вызов функции, и, следовательно, для задания границ массива array его использовать нельзя.
А.4.1. constexpr и определенные пользователем типы
До сих пор мы употребляли в примерах только встроенные типы — такие, как int. Но в новом стандарте С++ допускаются константные выражения любого типа, удовлетворяющего требованиям, предъявляемым к
• в классе должен существовать тривиальный копирующий конструктор;
• в классе должен существовать тривиальный деструктор;
• все нестатические переменные-члены данного класса и его базовых классов должны иметь тривиальный тип;
• в классе должен существовать либо тривиальный конструктор по умолчанию, либо constexpr-конструктор, отличный от копирующего конструктора.
О constexpr-конструкторах мы поговорим чуть ниже. А пока обратимся к классам с тривиальным конструктором по умолчанию. Пример такого класса приведён ниже:
class CX {
private:
int а;
int b;
public:
CX() = default; ←(1)
CX(int a_, int b_) : ←(2)
a(a_), b(b_) {}
int get_a() const {
return a;
}
int get_b() const {
return b;
}
int foo() const {
return a + b;
}
};
Здесь мы явно объявили конструктор по умолчанию (1) constexpr-функцию, которая создает новые экземпляры этого класса:
constexpr CX create_cx() {