return CX();
}
Можно также написать простую constexpr-функцию, которая копирует свой параметр:
constexpr CX clone(CX val) {
return val;
}
Но это практически и всё, что можно сделать, — constexpr-функции разрешено вызывать только другие constexpr-функции. Тем не менее, допускается применять спецификатор constexpr к функциям-членам и конструкторам CX:
class CX {
private:
int а;
int b;
public:
CX() = default;
constexpr CX(int a_, int b_): a(a_), b(b_) {}
constexpr int get_a() const { ←(1)
return a;
}
constexpr int get_b() { ←(2)
return b;
}
constexpr int foo() {
return a + b;
}
};
Отметим, что теперь квалификатор const в функции get_a() (1) избыточен, потому что он и так подразумевается ключевым словом constexpr. Функция get_b() достаточно «константная» несмотря на то, что квалификатор const опущен (2). Это дает возможность строить более сложные constexpr-функции, например:
constexpr CX make_cx(int a) {
return CX(a, 1);
}
constexpr CX half_double(CX old) {
return CX(old.get_a()/2, old.get_b()*2);
}
constexpr int foo_squared(CX val) {
return square(val.foo());
}
int array[foo_squared(
half_double(make_cx(10)))]; ←49 элементов
Всё это, конечно, интересно, но уж слишком много усилий для того, чтобы всего лишь вычислить границы массива или значение целочисленной константы. Основное же достоинство константных выражений и constexpr-функций в контексте пользовательских типов заключается в том, что объекты литерального типа, инициализированные константным выражением, инициализируются статически и, следовательно, не страдают от проблем, связанных с зависимостью от порядка инициализации и гонок.
CX si = half_double(CX(42, 19));
Это относится и к конструкторам. Если конструктор объявлен как constexpr, а его параметры — константные выражения, то такая инициализация считается
Особенно существенно это для таких классов, как std::mutex (см. раздел 3.2.1) и std::atomic<> (см. раздел 5.2.6), поскольку иногда мы хотим, чтобы некий глобальный объект синхронизировал доступ к другим переменным, но так, чтобы не было гонок при доступе к нему самому. Это было бы невозможно, если бы конструктор мьютекса мог стать жертвой гонки, поэтому конструктор по умолчанию в классе std::mutex объявлен как constexpr, чтобы инициализация мьютекса всегда производилась на этапе статической инициализации.
А.4.2. constexpr-объекты
До сих пор мы говорили о применении constexpr к функциям. Но этот спецификатор можно применять и к объектам. Чаще всего, так делают для диагностики; компилятор проверяет, что объект инициализирован константным выражением, constexpr-конструктором или агрегатным инициализатором, составленным из константных выражений. Кроме того, объект автоматически объявляется как const:
constexpr int i = 45;←Правильно
constexpr std::string s("hello");←┐Ошибка, std::string —
int foo(); │не литеральный тип
constexpr int j = foo();←Ошибка, foo() не объявлена как constexpr
A.4.3. Требования к constexpr-функциям
Чтобы функцию можно было объявить как constexpr, она должна удовлетворять нескольким требованиям. Если эти требования не выполнены, компилятор сочтет наличие спецификатора constexpr ошибкой. Требования таковы:
• все параметры должны иметь литеральный тип;
• возвращаемое значение должно иметь литеральный тип;
• тело функции может содержать только предложение return и ничего больше;
• выражение в предложении return должно быть константным;
• любой конструктор или оператор преобразования, встречающийся в выражении для вычисления возвращаемого значения, должен быть объявлен как constexpr.