constexpr и consteval
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 87 из 104.
C++ позволяет выполнять вычисления во время компиляции, а не во время выполнения, что может значительно повысить производительность. Ключевое слово constexpr (C++11) и ключевое слово consteval (C++20) дают вам контроль над тем, когда вычисляются выражения.
Функция constexpr может быть вычислена на этапе компиляции, если ей переданы константные аргументы, но она также может выполняться во время выполнения с неконстантными входными данными:
#include <iostream>
constexpr int square(int n) {
return n * n;
}
int main() {
constexpr int compileTime = square(5); // Вычисляется во время компиляции
int x = 7;
int runtime = square(x); // Вычисляется во время выполнения
std::cout << compileTime << "\n"; // 25
std::cout << runtime << "\n"; // 49
}Когда вам нужно гарантировать вычисление на этапе компиляции, используйте consteval. Функция consteval должна возвращать константу — вызов её с аргументами времени выполнения приводит к ошибке компиляции:
consteval int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5); // OK: 120 вычисляется во время компиляции
// int x = 5;
// int bad = factorial(x); // ОШИБКА: x не является константой
}Вы также можете использовать constexpr с переменными и даже конструкторами классов, что позволяет создавать целые объекты во время компиляции. Это особенно полезно для таблиц поиска, конфигурационных значений или любых данных, которые не меняются во время выполнения программы.
Задание
ЛегкоВы организуете свой код в трех файлах:
MathUtils.h: Определите математические функции времени компиляции.Создайте следующие функции:
cube— функцияconstexpr, которая принимаетintи возвращает его куб (n * n * n)triangularNumber— функцияconstexpr, которая вычисляет n-ое треугольное число по формуле n * (n + 1) / 2. Это значение всегда должно вычисляться во время компиляции.sumOfSquares— функцияconstexpr, которая принимает два целых числа и возвращает сумму их квадратов (a*a + b*b)
Config.h: Создайте структуру конфигурации, используя константы времени компиляции.Определите структуру
Configс конструкторомconstexpr, который принимает три целых числа:width,heightиdepth. Сохраните их как публичные члены. Также добавьте методconstexprс именемvolume(), который возвращает width * height * depth.Ниже структуры создайте глобальную константу
constexprс именемDEFAULT_CONFIG, инициализированную значениями 10, 20 и 5.main.cpp: Считайте два целых числа из входных данных, представляющих значения времени выполнения.Сначала продемонстрируйте вычисление во время компиляции, создав переменные
constexpr:- Сохраните
cube(4)в переменной constexpr и выведите:Cube of 4: [value] - Сохраните
triangularNumber(10)в переменной constexpr и выведите:10th triangular number: [value] - Выведите объем конфигурации по умолчанию:
Default volume: [value]
Затем продемонстрируйте, что функции
constexprтакже могут работать во время выполнения, используя два введенных значения:- Вызовите
cube()с первым входным значением и выведите:Cube of [input]: [result] - Вызовите
sumOfSquares()с обоими входными значениями и выведите:Sum of squares: [result]
- Сохраните
Например, при входных данных 3 и 4:
Cube of 4: 64
10th triangular number: 55
Default volume: 1000
Cube of 3: 27
Sum of squares: 25При входных данных 5 и 12:
Cube of 4: 64
10th triangular number: 55
Default volume: 1000
Cube of 5: 125
Sum of squares: 169Шпаргалка
C++ позволяет выполнять вычисления на этапе компиляции, используя ключевые слова constexpr (C++11) и consteval (C++20).
Функция constexpr может быть вычислена на этапе компиляции с константными аргументами, но также выполняется во время выполнения с неконстантными входными данными:
constexpr int square(int n) {
return n * n;
}
constexpr int compileTime = square(5); // Вычисляется на этапе компиляции
int x = 7;
int runtime = square(x); // Вычисляется во время выполненияФункция consteval должна вычисляться на этапе компиляции. Вызов её со значениями времени выполнения приводит к ошибке компиляции:
consteval int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int result = factorial(5); // OK: вычисляется на этапе компиляции
// int x = 5;
// int bad = factorial(x); // ОШИБКА: x не является константойВы можете использовать constexpr с переменными, конструкторами классов и методами, чтобы обеспечить создание объектов на этапе компиляции:
struct Config {
int width, height, depth;
constexpr Config(int w, int h, int d)
: width(w), height(h), depth(d) {}
constexpr int volume() const {
return width * height * depth;
}
};
constexpr Config config(10, 20, 5); // Создано на этапе компиляцииПопробуйте сами
#include <iostream>
#include "MathUtils.h"
#include "Config.h"
using namespace std;
int main() {
// Считать два целых числа из входного потока
int input1, input2;
cin >> input1;
cin >> input2;
// TODO: Продемонстрировать вычисление во время компиляции
// Создать переменную constexpr, хранящую cube(4), и вывести: "Cube of 4: [value]"
// TODO: Создать переменную constexpr, хранящую triangularNumber(10)
// и вывести: "10th triangular number: [value]"
// TODO: Вывести громкость из конфигурации по умолчанию: "Default volume: [value]"
// TODO: Продемонстрировать использование функций constexpr во время выполнения
// Вызвать cube() с input1 и вывести: "Cube of [input1]: [result]"
// TODO: Вызвать sumOfSquares() с обоими входными значениями
// и вывести: "Sum of squares: [result]"
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер12Возможности современного C++
Семантика перемещения и RvaluesИдеальная передача (Perfect Forwarding)Лямбда-выражения в деталяхstd::function и std::bindconstexpr и constevalСтруктурированные привязкиoptional, variant, any