Skillversitas

Constants, Literals, and Constexpr in C++

A constant is a value that cannot be changed while the program is running. In C++, there are several ways to declare a constant: using const, constexpr, #define (C style), and literals. Understanding the differences between these methods is important for writing code that is safe, efficient, and easy to maintain.


Why Use Constants?

  1. Safety — Prevents accidental changes to a value
  2. Readability — A constant name is more meaningful than a raw number (magic number)
  3. Maintainability — Value changes are made in only one place
  4. Optimization — The compiler can optimize code with constant values

Code Example

#include <iostream>
#include <cmath>

using namespace std;

int main() {
    // Using const
    const double PI = 3.14159265358979;
    const int MAX_LIMIT = 100;
    const string APP_NAME = "Calculator";

    // Using constexpr (evaluated at compile time)
    constexpr double GRAVITY = 9.80665;
    constexpr int BUFFER_SIZE = 1024;

    // Using literals directly
    double radius = 5.0;
    double circleArea = PI * radius * radius;

    cout << "=== " << APP_NAME << " ===" << endl;
    cout << "Circle area (r=" << radius << "): " << circleArea << endl;
    cout << "Gravity: " << GRAVITY << " m/s²" << endl;

    // Literals in various bases
    cout << "\n=== Literals in Various Bases ===" << endl;
    int decimal = 42;
    int octal = 052;
    int hexadecimal = 0x2A;
    int binary = 0b101010;

    cout << "Decimal: " << decimal << endl;
    cout << "Octal (052): " << octal << endl;
    cout << "Hexadecimal (0x2A): " << hexadecimal << endl;
    cout << "Binary (0b101010): " << binary << endl;

    // Character literals
    char letter = 'A';
    char newline = '\n';
    wchar_t omega = L'Ω';

    // String literals
    const char* cText = "Hello C string";
    string cppText = "Hello C++ string";
    const wchar_t* wideText = L"Wide text";

    // Boolean literal
    bool valid = true;
    bool invalid = false;

    // Null pointer literal
    int* ptr = nullptr;

    return 0;
}

Code Explanation

1. The const Keyword

const declares a variable whose value cannot be changed after initialization.

const int LIMIT = 100;
LIMIT = 200;  // ❌ Error: assignment to a const variable

Characteristics of const:
- Evaluated at runtime (when the program is running)
- Can be used with any data type
- Must be initialized at declaration
- Stored in regular memory (its address can be taken)
- Can be used for class members

// const with various data types
const int size = 10;
const double pi = 3.14159;
const string name = "John";
const char* text = "Hello";

// const on pointers
int value = 10;
int* const fixedPtr = &value;       // Fixed pointer (address cannot be changed)
const int* constPtr = &value;     // Pointed-to data is constant (value cannot be changed)
const int* const fixedConstPtr = &value; // Both are fixed

// const on references
const int& ref = value;  // Constant reference — value cannot be changed through ref

2. The constexpr Keyword (C++11)

constexpr declares an expression or function that is evaluated at compile time.

constexpr int SIZE = 100;        // Evaluated at compile time
constexpr double PI = 3.14159;     // Evaluated at compile time
constexpr int SQUARE(int x) {      // constexpr function
    return x * x;
}
constexpr int result = SQUARE(10);  // Computed at compile time

Characteristics of constexpr:
- Evaluated at compile time
- The value must be determinable at compile time
- Faster (because it is already computed before the program runs)
- Can be used for array sizes, template arguments, and more
- constexpr variables are implicitly const

Differences Between const and constexpr:

Aspect const constexpr
Evaluation Runtime Compile time
Value Can depend on runtime Must be known at compile time
Speed Normal Faster (pre-computed)
Usage Variables, parameters, members Variables, functions, constructors
Memory Regular memory Can be initialized in .rodata

3. Preprocessor #define (C Style)

#define is a C-language preprocessor directive that replaces text literally before compilation.

#define PI 3.14159
#define MAX_LIMIT 100
#define CIRCLE_AREA(x) (PI * (x) * (x))

Note: In C++, #define should be avoided because it has no type safety and can cause hard-to-find bugs. Use const or constexpr instead.

Problems with #define:

#define SQUARE(x) x * x

int result = SQUARE(3 + 2);
// The preprocessor turns it into: 3 + 2 * 3 + 2 = 3 + 6 + 2 = 11
// NOT the expected 25!

// Solution with constexpr
constexpr int square(int x) { return x * x; }
int result = square(3 + 2);  // 25 — correct!

Literals

A literal is a constant value written directly in the program code. Literals are the most basic representation of data in C++.

Numeric Literals

Decimal (Base 10)

int a = 42;
int b = -17;
double c = 3.14;

Octal (Base 8)

An octal literal starts with a zero 0.

int octal = 052;    // = 42 in decimal
int negativeOctal = -017;  // = -15 in decimal

Hexadecimal (Base 16)

A hexadecimal literal starts with 0x or 0X.

int hexa = 0x2A;   // = 42 in decimal
int hexaUpper = 0xFF;  // = 255 in decimal
int hexaLower = 0xff;  // Also = 255 (case-insensitive)

Binary (Base 2, C++14)

A binary literal starts with 0b or 0B.

int binary = 0b101010;  // = 42 in decimal
int negativeBinary = -0b1100;  // = -12 in decimal

Digit Separator (C++14)

For making long numbers easier to read:

int million = 1'000'000;           // = 1000000
double pi = 3.141'592'653'589'793;  // Easier to read
int binary = 0b1010'0011'1100'0101;  // Long binary number
int hexa = 0xFF'FF'FF'FF;  // Hexadecimal

Character Literals

char a = 'A';           // Regular character
char b = '\n';          // Escape sequence
char c = '\t';          // Tab
char d = '\\';          // Backslash
char e = '\'';          // Single quote
char f = '\0';          // Null character

// Unicode character literals
char16_t unicode16 = u'Ω';    // UTF-16
char32_t unicode32 = U'Ω';    // UTF-32
wchar_t wide = L'Ω';          // Wide character

String Literals

// C-style string
const char* text1 = "Hello, World!";

// C++ string
string text2 = "Hello, C++!";

// Raw string literal (C++11) — without escape sequences
const char* path = R"(C:\Users\Documents\file.txt)";
const char* multiLine = R"(First line
Second line
Third line)";

// Strings with encoding prefix
const char* utf8 = u8"Welcome";    // UTF-8
const wchar_t* wide_str = L"Wide text";  // Wide string
const char16_t* utf16 = u"Unicode";       // UTF-16
const char32_t* utf32 = U"Unicode 32";    // UTF-32

Boolean Literals

bool a = true;
bool b = false;

Nullptr Literal (C++11)

int* ptr = nullptr;     // Safe null pointer
double* dptr = nullptr; // Works for all pointer types

User-Defined Literals (C++11)

C++ allows you to define your own literals with custom suffixes:

#include <iostream>
#include <string>

using namespace std;

// Literal for Celsius temperature
constexpr long double operator"" _celsius(long double celsius) {
    return celsius;
}

// Literal for distance in meters
constexpr long double operator"" _meter(long double meter) {
    return meter;
}

// Literal for conversion to Fahrenheit
constexpr long double operator"" _fahrenheit(long double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}

// Literal for strings
string operator"" _s(const char* str, size_t len) {
    return string(str, len);
}

int main() {
    auto temperature = 36.5_celsius;
    auto distance = 100.0_meter;
    auto fahrenheit = 36.5_fahrenheit;
    auto name = "John"_s;

    cout << "Temperature: " << temperature << " °C" << endl;
    cout << "Distance: " << distance << " m" << endl;
    cout << "Fahrenheit: " << fahrenheit << " °F" << endl;
    cout << "Name: " << name << endl;

    return 0;
}

Custom literals are very useful for making code more expressive and type-safe, for example in unit systems (units) or formatting.


Comparing Constant Methods

Method Compile-time Type-safe Scope-aware Debuggable
#define Yes (preprocessor) No No No
const No (runtime) Yes Yes Yes
constexpr Yes Yes Yes Yes
enum Partial Yes Yes Partial

Recommendations

  1. Use constexpr for values that can be computed at compile time
  2. Use const for values that can only be determined at runtime
  3. Avoid #define for constants — use const or constexpr
  4. Use enum class for a set of related values

Everyday Use Cases

Application Configuration with Constants

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

// Application constants using constexpr
constexpr int PAGE_SIZE = 20;
constexpr int PASSWORD_LIMIT = 8;
constexpr double DEFAULT_TAX = 0.11;
constexpr const char* APP_VERSION = "2.1.0";

// constexpr function for compile-time computation
constexpr double calculateCircleArea(double r) {
    return 3.14159265358979 * r * r;
}

int main() {
    // Using literals for input
    double radius = 7.0;
    double area = calculateCircleArea(radius);

    cout << "=== Application Configuration ===" << endl;
    cout << "Version: " << APP_VERSION << endl;
    cout << "Page size: " << PAGE_SIZE << endl;
    cout << "Default tax: " << (DEFAULT_TAX * 100) << "%" << endl;

    cout << "\n=== Calculation ===" << endl;
    cout << "Circle area (r=" << radius << "): " << area << endl;

    // Literals with digit separators
    long long population = 275'501'339;
    double gdp = 1'319'100'000'000.00;

    cout << "\n=== Indonesia Data ===" << endl;
    cout << "Population: " << population << endl;
    cout << "GDP: $ " << gdp << endl;

    // constexpr for array size
    constexpr int NUM_STUDENTS = 5;
    int scores[NUM_STUDENTS] = {85, 90, 78, 92, 88};

    double total = 0;
    for (int i = 0; i < NUM_STUDENTS; i++) {
        total += scores[i];
    }
    double average = total / NUM_STUDENTS;

    cout << "\n=== Score Data ===" << endl;
    cout << "Average: " << average << endl;

    return 0;
}

Avoiding Magic Numbers

// ❌ Bad: magic numbers
void processPayment(double amount) {
    double tax = amount * 0.11;
    double discount = amount > 1000000 ? amount * 0.05 : 0;
    if (amount < 50000) {
        cout << "Minimum payment not met" << endl;
    }
}

// ✅ Good: using constants
constexpr double TAX_PERCENT = 0.11;
constexpr double DISCOUNT_PERCENT = 0.05;
constexpr double DISCOUNT_LIMIT = 1000000.0;
constexpr double MINIMUM_PAYMENT = 50000.0;

void processPayment(double amount) {
    double tax = amount * TAX_PERCENT;
    double discount = amount > DISCOUNT_LIMIT ? amount * DISCOUNT_PERCENT : 0;
    if (amount < MINIMUM_PAYMENT) {
        cout << "Minimum payment not met" << endl;
    }
}

Conclusion

C++ provides several ways to declare a constant: const for runtime, constexpr for compile time, and #define (which should be avoided). Literals are constant values written directly in code, available in various formats: decimal, octal (0), hexadecimal (0x), binary (0b), character, string, and boolean. User-defined literals allow creating custom literals for more expressive code. Use constexpr wherever possible for optimal performance, and const for values that are only known at runtime.