Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Compile-Time Class Objects
Create class objects that can be evaluated entirely at compile time.
Constexpr Aggregates and Classes
constexpr enables compile-time evaluation of expressions. With classes, constexpr enables creating objects and calling functions at compile time.
constexpr member functions
Member functions can be constexpr, allowing them to be evaluated at compile time:
class Circle
{
private:
double radius{};
public:
constexpr Circle(double r) : radius{ r } {}
constexpr double area() const
{
return 3.14159 * radius * radius;
}
};
int main()
{
constexpr Circle c{5.0};
constexpr double a{ c.area() }; // Computed at compile time!
return 0;
}
The entire calculation happens during compilation, not at runtime.
Requirements for constexpr functions
A constexpr member function must:
- Be marked
constexpr - Have a return type that's a literal type
- Have parameters that are literal types
- Have a body that only uses constexpr operations
class Point
{
public:
int x{};
int y{};
constexpr Point(int xPos, int yPos) : x{ xPos }, y{ yPos } {}
constexpr int distanceFromOrigin() const
{
return x * x + y * y; // Simple operations only
}
};
constexpr constructors
Constructors can be constexpr, enabling compile-time object creation:
class Rectangle
{
private:
int width{};
int height{};
public:
constexpr Rectangle(int w, int h)
: width{ w }, height{ h }
{
}
constexpr int area() const
{
return width * height;
}
};
int main()
{
constexpr Rectangle rect{10, 20};
constexpr int a{ rect.area() }; // 200, computed at compile time
static_assert(a == 200); // Compile-time assertion
return 0;
}
Runtime and compile-time use
constexpr functions can be used both ways:
class Calculator
{
public:
constexpr int square(int n) const
{
return n * n;
}
};
int main()
{
constexpr Calculator calc{};
// Compile-time
constexpr int compile{ calc.square(5) };
// Runtime
int userInput{};
std::cin >> userInput;
int runtime{ calc.square(userInput) };
return 0;
}
Literal types
For a class object to be constexpr, the class must be a literal type.
A literal and a literal type are distinct things. A literal is a constexpr value in source code (like
5 or "hello"). A literal type is a type that can be used for constexpr values. A literal always has a literal type, but a value with a literal type doesn't have to be a literal.
A class is a literal type if:
- It has at least one
constexprconstructor (or is an aggregate) - All members are literal types
- It has a trivial destructor
- All base classes are literal types
class LiteralType
{
public:
int value{};
double amount{};
constexpr LiteralType(int v, double a)
: value{ v }, amount{ a }
{
}
};
// Can use at compile time
constexpr LiteralType obj{42, 3.14};
Aggregates are implicitly constexpr-capable
Aggregates (structs/classes with no private members, no user-provided constructors, and no virtual functions) are automatically literal types:
struct Point // Aggregate
{
int x{};
int y{};
constexpr int sum() const { return x + y; }
};
int main()
{
constexpr Point p{3, 4}; // OK: aggregates support constexpr
constexpr int s{ p.sum() }; // 7, computed at compile time
return 0;
}
No explicit constexpr constructor is needed for aggregates.
Non-literal types
Classes with non-literal members can't be constexpr:
class NonLiteral
{
public:
std::string name{}; // std::string is not literal
constexpr NonLiteral(std::string n) // Error!
: name{ n }
{
}
};
constexpr and arrays
constexpr works with arrays of objects:
class Value
{
public:
int num{};
constexpr Value(int n) : num{ n } {}
constexpr int doubled() const
{
return num * 2;
}
};
int main()
{
constexpr Value values[]{ Value{1}, Value{2}, Value{3} };
constexpr int sum{ values[0].doubled() + values[1].doubled() };
return 0;
}
Practical example: compile-time calculations
class Factorial
{
public:
constexpr int calculate(int n) const
{
return (n <= 1) ? 1 : n * calculate(n - 1);
}
};
int main()
{
constexpr Factorial fact{};
constexpr int result{ fact.calculate(5) }; // 120, at compile time
int arr[fact.calculate(4)]{}; // Array of size 24
return 0;
}
constexpr with conditional logic
class Math
{
public:
constexpr int absolute(int n) const
{
return (n < 0) ? -n : n;
}
constexpr int max(int a, int b) const
{
return (a > b) ? a : b;
}
};
int main()
{
constexpr Math math{};
constexpr int value{ math.max(math.absolute(-5), math.absolute(3)) };
// value is 5, computed at compile time
return 0;
}
When a constexpr function is evaluating in a compile-time context, only constexpr functions can be called. This means the constructor and any member functions called must all be constexpr.
Advanced note: Pre-C++20, constexpr functions had significant restrictions including no loops (requiring recursion instead), limited statements, and no dynamic memory allocation. C++20 relaxed many of these restrictions:
// C++20 and later
class Counter
{
public:
constexpr int sumTo(int n) const
{
int sum{};
for (int i{ 1 }; i <= n; ++i) // Loops allowed in C++20
sum += i;
return sum;
}
};
When to use constexpr
Use constexpr for:
- Mathematical calculations known at compile time
- Configuration values
- Lookup tables
- Type traits and meta-programming
- Compile-time validation
Don't use constexpr for:
- Functions that perform I/O
- Functions requiring runtime data
- Functions with complex dependencies
- Classes managing resources
Benefits
Performance: Work done at compile time doesn't happen at runtime.
Safety: Compile-time evaluation catches errors early.
Optimization: Enables more compiler optimizations.
Type safety: constexpr values can be used in contexts requiring compile-time constants (array sizes, template arguments).
class Config
{
public:
constexpr int getBufferSize() const { return 1024; }
};
int main()
{
constexpr Config cfg{};
int buffer[cfg.getBufferSize()]{}; // Size known at compile time
return 0;
}
Mark functions constexpr when possible - if a function can be constexpr, make it so. Use constexpr for compile-time constants to replace macros and magic numbers. Test at compile time using static_assert to verify constexpr calculations. Don't force constexpr where it doesn't make sense - not everything benefits from compile-time evaluation.
constexpr Circle c{10.0};
static_assert(c.area() > 314.0 && c.area() < 315.0); // Compile-time check
Summary
constexpr member functions: Member functions marked constexpr can be evaluated at compile time, allowing compile-time computation of results when called with constant expressions.
constexpr constructors: Constructors marked constexpr enable creating objects at compile time, allowing object initialization during compilation rather than runtime.
Dual-use functions: constexpr functions can be used both at compile time (with constant expressions) and at runtime (with runtime values), providing flexibility without code duplication.
Literal types: A class is a literal type if it has at least one constexpr constructor, all members are literal types, it has a trivial destructor, and all base classes are literal types.
Requirements: constexpr member functions must be marked constexpr, have literal return types and parameters, and only use operations that can be evaluated at compile time.
Benefits: Using constexpr moves work from runtime to compile time, improving performance, enabling compile-time safety checks with static_assert, and allowing constexpr values in contexts requiring compile-time constants like array sizes.
When to use: Use constexpr for mathematical calculations, configuration values, lookup tables, and compile-time validation. Avoid it for I/O operations, functions requiring runtime data, or resource management.
constexpr with classes enables powerful compile-time programming, allowing entire calculations to happen during compilation. This improves runtime performance and enables stronger type safety through compile-time verification while maintaining readable, natural code structure.
Compile-Time Class Objects - Quiz
Test your understanding of the lesson.
Practice Exercises
Constexpr Classes
Create compile-time class instances using constexpr constructors.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!