До сих пор мы говорили о применении 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
.
На самом деле, это вполне понятные требования: у компилятора должна быть возможность встроить вызов функции в константное выражение, и при этом оно должно остаться константным. Кроме того, запрещается что-либо изменять; constexpr
-функции являются чистыми , то есть не имеют побочных эффектов.
К constexpr
-функциям, являющимся членами класса, предъявляются дополнительные требования:
• constexpr
функции-члены не могут быть виртуальными;
• класс, членом которого является функция, должен иметь литеральный тип.
Для constexpr
-конструкторов действуют другие правила:
• тело конструктора должно быть пустым;
• все базовые классы должны быть инициализированы;
• все нестатические данные-члены должны быть инициализированы;
• все выражения, встречающиеся в списке инициализации членов, должны быть константными;
• конструкторы, выбранные для инициализации данных-членов и базовых классов, должны быть constexpr
-конструкторами;
• все конструкторы и операторы преобразования, используемые для конструирования данных-членов и базовых классов в соответствующем выражении инициализации, должны быть объявлены как constexpr
.
Это тот же набор правил, что и для функций, с тем отличием, что возвращаемого значения нет, а, значит, нет и предложения return
. Вместо возврата значения конструктор инициализирует базовые классы и данные-члены в списке инициализации членов. Тривиальные копирующие конструкторы неявно объявлены как constexpr
.
А.4.4. constexpr
и шаблоны
Спецификатор constexpr
в объявлении шаблона функции или функции-члене шаблонного класса игнорируется, если типы параметров и возвращаемого значения для данной конкретизации шаблона не являются литеральными. Это позволяет писать шаблоны функций, которые становятся constexpr
-функциями, если параметры шаблона имеют подходящие типы, и обычными встраиваемыми функциями в противном случае. Например:
template
constexpr T sum(T a, T b) {
return a + b;
} │
Правильно, sum
constexpr int i = sum(3, 42);←┘
constexpr
std::string s =
sum(std::string("hello"), │
Правильно, но sum
std::string(" world"));←┘
He constexpr
Функция должна удовлетворять также всем остальным требованиям, предъявляемым к constexpr
-функциям. Нельзя включить в тело шаблона функции, объявленного как constexpr
, несколько предложений только потому, что это шаблон; компилятор сочтет это ошибкой.
Лямбда-функции — одно из самых интересных новшеств в стандарте C++11, потому что они позволяют существенно упростить код и исключить многие стереотипные конструкции, которые применяются при написании объектов, допускающих вызов. Синтаксис лямбда-функций в C++11 позволяет определить функцию в той точке выражения, где она необходима. Это отличное решение, например, для передачи предикатов функциям ожидания из класса std::condition_variable
(как в примере из раздела 4.1.1), потому что дает возможность кратко выразить семантику в терминах доступных в данной точке переменных, а не запоминать необходимое состояние в переменных-членах класса с оператором вызова.
Читать дальше