Showing posts with label constexpr. Show all posts
Showing posts with label constexpr. Show all posts

12/16/2025

Shows difference between functions, concepts, and constexpr variables

 

.

// study.marearts.com: When to use () and when not to in C++
// Shows difference between functions, concepts, and constexpr variables

#include <iostream>
#include <concepts>

// ==========================================
// CASE 1: FUNCTION - NEEDS ()
// ==========================================

// Regular function
bool isPositiveFunction(int x) {
return x > 0;
}

// constexpr function
constexpr bool isPositiveConstexpr(int x) {
return x > 0;
}

// consteval function (C++20)
template <typename T>
consteval bool IsIntegerFunction() {
return std::is_integral_v<T>;
}

void example1() {
std::cout << "\n=== CASE 1: FUNCTIONS - MUST USE () === study.marearts.com" << std::endl;
// Functions NEED ()
if (isPositiveFunction(5)) { // ← () required
std::cout << "✓ 5 is positive (function call)" << std::endl;
}
if constexpr (IsIntegerFunction<int>()) { // ← () required
std::cout << "✓ int is integer (consteval function call)" << std::endl;
}
// ❌ This would be ERROR:
// if (isPositiveFunction) { } // ERROR: can't use function without calling it
}

// ==========================================
// CASE 2: CONCEPT - NO ()
// ==========================================

// Concept (C++20)
template <typename T>
concept IsInteger = std::is_integral_v<T>;

void example2() {
std::cout << "\n=== CASE 2: CONCEPTS - NO () === study.marearts.com" << std::endl;
// Concepts don't use ()
if constexpr (IsInteger<int>) { // ← NO () - it's a concept, not function
std::cout << "✓ int is integer (concept check)" << std::endl;
}
// ❌ This would be ERROR:
// if constexpr (IsInteger<int>()) { } // ERROR: concept is not a function
}

// ==========================================
// CASE 3: CONSTEXPR VARIABLE - NO ()
// ==========================================

// constexpr variable
template <typename T>
constexpr bool is_integer_variable = std::is_integral_v<T>;

void example3() {
std::cout << "\n=== CASE 3: CONSTEXPR VARIABLES - NO () === study.marearts.com" << std::endl;
// constexpr variables don't use ()
if constexpr (is_integer_variable<int>) { // ← NO () - it's a variable, not function
std::cout << "✓ int is integer (constexpr variable)" << std::endl;
}
// ❌ This would be ERROR:
// if constexpr (is_integer_variable<int>()) { } // ERROR: variable is not callable
}

// ==========================================
// COMPARISON IN SAME CONTEXT
// ==========================================

// All three doing the same thing, different ways:
template <typename T>
consteval bool IsPositiveFunction() { return sizeof(T) > 0; } // Function

template <typename T>
concept IsPositiveConcept = sizeof(T) > 0; // Concept

template <typename T>
constexpr bool is_positive_variable = sizeof(T) > 0; // Variable

void example4() {
std::cout << "\n=== COMPARISON: FUNCTION vs CONCEPT vs VARIABLE === study.marearts.com" << std::endl;
// Function needs ()
if constexpr (IsPositiveFunction<int>()) { // ← () YES
std::cout << "✓ Function: use ()" << std::endl;
}
// Concept doesn't use ()
if constexpr (IsPositiveConcept<int>) { // ← () NO
std::cout << "✓ Concept: no ()" << std::endl;
}
// Variable doesn't use ()
if constexpr (is_positive_variable<int>) { // ← () NO
std::cout << "✓ Variable: no ()" << std::endl;
}
}

// ==========================================
// DISPATCHER PATTERN EXAMPLE
// ==========================================

// Like CK Builder dispatcher!

// Old way: consteval functions (need ())
template <typename T>
consteval bool IsSpecialAlgorithmFunction() { return sizeof(T) > 4; }

// New way: concepts (no ())
template <typename T>
concept IsSpecialAlgorithmConcept = sizeof(T) > 4;

template <typename T>
void dispatch_old_way() {
if constexpr (IsSpecialAlgorithmFunction<T>()) { // ← () YES (function)
std::cout << "Old dispatcher: Special algorithm (function)" << std::endl;
}
else {
std::cout << "Old dispatcher: Normal algorithm" << std::endl;
}
}

template <typename T>
void dispatch_new_way() {
if constexpr (IsSpecialAlgorithmConcept<T>) { // ← () NO (concept)
std::cout << "New dispatcher: Special algorithm (concept)" << std::endl;
}
else {
std::cout << "New dispatcher: Normal algorithm" << std::endl;
}
}

void example5() {
std::cout << "\n=== DISPATCHER PATTERN (Like CK Builder) ===" << std::endl;
dispatch_old_way<long>(); // Old way with functions
dispatch_new_way<long>(); // New way with concepts
}

// ==========================================
// MAIN
// ==========================================

int main() {
std::cout << "=== When to Use () in C++ === study.marearts.com" << std::endl;
example1(); // Functions
example2(); // Concepts
example3(); // Variables
example4(); // Comparison
example5(); // Dispatcher pattern
std::cout << "\n=== SUMMARY ===" << std::endl;
std::cout << "FUNCTION: Use () → IsXXX<T>()" << std::endl;
std::cout << "CONCEPT: No () → IsXXX<T>" << std::endl;
std::cout << "VARIABLE: No () → is_xxx<T>" << std::endl;
return 0;
}

/*
TO COMPILE:
g++ -std=c++20 when_to_use_parentheses.cpp -o when_to_use_parentheses
./when_to_use_parentheses

OUTPUT:
Functions need ()
Concepts don't need ()
Variables don't need ()
*/


..

Compile-Time vs Runtime

1. Concept - Always Compile Time 

template <typename T>

concept IsInteger = std::is_integral_v<T>;


// Checked at COMPILE TIME:

if constexpr (IsInteger<int>) {  // ← Compiler checks this

    // This code exists in binary

}

else {

    // This code REMOVED from binary

}

Compiler decides which branch to include in the compiled program.

2. constexpr - Can Be Either

Compiler decides based on context!

3. consteval - MUST Be Compile Time ✅








12/12/2025

Why Can't We Use Functions in "requires"? #04_why_not_both.cpp

 

.

// Example 4: Why Can't We Use Functions in "requires"?
// This shows the difference between concepts and constexpr functions

#include <iostream>
#include <concepts>

// ==========================================
// Define types
// ==========================================

struct Dog {
const char* name = "MareArts.com";
};

struct Cat {
const char* name = "Whiskers";
};

// ==========================================
// METHOD 1: Using CONCEPT (works in requires)
// ==========================================

template <typename T>
concept HasName = requires(T t) {
{ t.name } -> std::convertible_to<const char*>;
};

template <typename T>
requires HasName<T> // ✅ Works! Concept in requires clause
void printNameWithConcept(T animal) {
std::cout << "Name: " << animal.name << "\n";
}

// ==========================================
// METHOD 2: Using CONSTEXPR FUNCTION (doesn't work in requires!)
// ==========================================

template <typename T>
consteval bool hasNameFunction() {
return requires(T t) {
{ t.name } -> std::convertible_to<const char*>;
};
}

// ❌ This won't compile!
// template <typename T>
// requires hasNameFunction<T>() // ❌ ERROR: Can't use function in requires!
// void printNameWithFunction(T animal) {
// std::cout << "Name: " << animal.name << "\n";
// }

// ==========================================
// METHOD 3: Using CONSTEXPR FUNCTION with "if constexpr" (works!)
// ==========================================

template <typename T>
void printNameIfConstexpr(T animal) {
// ✅ Works! Function in "if constexpr"
if constexpr (hasNameFunction<T>()) {
std::cout << "Name: " << animal.name << "\n";
}
else {
std::cout << "No name!\n";
}
}

// ==========================================
// WHY THE DIFFERENCE?
// ==========================================

/*
"requires" clause:
- Part of template declaration
- Needs a CONCEPT (compile-time constraint)
- Determines if template can even exist
- Syntax: template <...> requires CONCEPT<T>

"if constexpr":
- Inside function body
- Can use ANY compile-time expression
- Chooses which code branch to keep
- Syntax: if constexpr (EXPRESSION)
*/

// ==========================================
// Main
// ==========================================

int main() {
Dog dog;
Cat cat;
std::cout << "=== Using Concept in 'requires' ===\n";
printNameWithConcept(dog);
printNameWithConcept(cat);
std::cout << "\n=== Using Function in 'if constexpr' ===\n";
printNameIfConstexpr(dog);
printNameIfConstexpr(cat);
return 0;
}

/*
SUMMARY - Where Can You Use What?

┌──────────────────────────┬──────────────┬──────────────────┐
│ │ "requires" │ "if constexpr" │
├──────────────────────────┼──────────────┼──────────────────┤
│ Concept │ ✅ │ ✅ │
│ Constexpr Function │ ❌ │ ✅ │
│ Any compile-time expr │ ❌ │ ✅ │
└──────────────────────────┴──────────────┴──────────────────┘

This is why:
- Factories use concepts in "requires" (IsReferenceAlgorithm concept)
- Dispatcher uses constexpr functions in "if constexpr" (IsReferenceAlgorithm())

After refactoring:
- We removed the concept (not needed anymore)
- Kept the constexpr function (used in dispatcher)
- Removed algorithm checks from factory "requires" clauses
*/


..


.

### 4. Why Not Both? (04_why_not_both.cpp)
**What it teaches:** Why we can't mix them up

```
┌──────────────────────────┬──────────────┬──────────────────┐
│ │ "requires" │ "if constexpr" │
├──────────────────────────┼──────────────┼──────────────────┤
│ Concept │ ✅ │ ✅ │
│ Constexpr Function │ ❌ │ ✅ │
└──────────────────────────┴──────────────┴──────────────────┘
```

**Key Point:** Constexpr functions DON'T work in `requires` clauses!

..

Now #1~#4 series is done.

g++ -std=c++20 01_concept_basics.cpp -o 01_concepts
g++ -std=c++20 02_constexpr_functions.cpp -o 02_constexpr
g++ -std=c++20 03_dispatcher_pattern.cpp -o 03_dispatcher
g++ -std=c++20 04_why_not_both.cpp -o 04_why


recap!

## Key Takeaways 🎯

1. **Concepts** check if types have certain features
- Used in `requires` clauses
- Like: `requires HasName<T>`

2. **Constexpr functions** run at compile time
- Used in `if constexpr` statements
- Like: `if constexpr (isDog<T>())`

3. **Dispatcher pattern** separates concerns:
- **Dispatcher** checks algorithm type (using `if constexpr` + constexpr functions)
- **Factory** checks direction only (using `requires` + concepts)

4. **You can't mix them:**
- Constexpr functions DON'T work in `requires` clauses
- That's why we removed the algorithm check from factory `requires` clauses!


Thank you!

study.marearts.com


Dispatcher Pattern Example #03_dispatcher_pattern.cpp

 

.

// Example 3: The Dispatcher Pattern (Like CK Convolution!)
// This is exactly how the convolution dispatcher works!

#include <iostream>
#include <concepts>

// ==========================================
// Step 1: Define algorithm types
// ==========================================

enum class AlgorithmType {
SIMPLE,
FAST,
PARALLEL
};

// Simple algorithm
struct SimpleAlgorithm {
static constexpr AlgorithmType type = AlgorithmType::SIMPLE;
};

// Fast algorithm
struct FastAlgorithm {
static constexpr AlgorithmType type = AlgorithmType::FAST;
int optimization_level = 3;
};

// Parallel algorithm
struct ParallelAlgorithm {
static constexpr AlgorithmType type = AlgorithmType::PARALLEL;
int num_threads = 8;
};

// ==========================================
// Step 2: Algorithm detection functions (like IsReferenceAlgorithm!)
// ==========================================

// Check if algorithm is Simple
template <typename T>
consteval bool IsSimpleAlgorithm() {
return std::is_same_v<T, SimpleAlgorithm>;
}

// Check if algorithm is Fast
template <typename T>
consteval bool IsFastAlgorithm() {
return std::is_same_v<T, FastAlgorithm>;
}

// Check if algorithm is Parallel
template <typename T>
consteval bool IsParallelAlgorithm() {
return std::is_same_v<T, ParallelAlgorithm>;
}

// ==========================================
// Step 3: Factories (only check direction, not algorithm!)
// ==========================================

enum class Direction {
FORWARD,
BACKWARD
};

// Factory for Simple Algorithm
template <Direction DIR, typename Algorithm>
requires (DIR == Direction::FORWARD) // ← Only check direction!
struct SimpleFactory {
static void process() {
std::cout << "SimpleFactory: Processing with basic algorithm\n";
}
};

// Factory for Fast Algorithm
template <Direction DIR, typename Algorithm>
requires (DIR == Direction::FORWARD) // ← Only check direction!
struct FastFactory {
static void process() {
std::cout << "FastFactory: Processing with optimized algorithm\n";
}
};

// Factory for Parallel Algorithm
template <Direction DIR, typename Algorithm>
requires (DIR == Direction::FORWARD) // ← Only check direction!
struct ParallelFactory {
static void process() {
std::cout << "ParallelFactory: Processing with parallel algorithm\n";
}
};

// ==========================================
// Step 4: DISPATCHER - Routes to correct factory
// ==========================================

template <Direction DIR, typename Algorithm>
void dispatch() {
std::cout << "\n=== DISPATCHER START (MareArts.com)===\n";
// Check direction first
if constexpr (DIR == Direction::FORWARD) {
std::cout << "Direction: FORWARD\n";
// Check algorithm type (like the conv dispatcher!)
if constexpr (IsSimpleAlgorithm<Algorithm>()) { // ← Check algorithm here!
std::cout << "Algorithm: SIMPLE\n";
SimpleFactory<DIR, Algorithm>::process();
}
else if constexpr (IsFastAlgorithm<Algorithm>()) { // ← Check algorithm here!
std::cout << "Algorithm: FAST\n";
FastFactory<DIR, Algorithm>::process();
}
else if constexpr (IsParallelAlgorithm<Algorithm>()) { // ← Check algorithm here!
std::cout << "Algorithm: PARALLEL\n";
ParallelFactory<DIR, Algorithm>::process();
}
else {
// Force compile error for unknown algorithms
[]<bool flag = false>() { static_assert(flag, "Unknown algorithm!"); }();
}
}
else {
std::cout << "Direction: BACKWARD\n";
std::cout << "Not implemented yet!\n";
}
std::cout << "=== DISPATCHER END ===\n\n";
}

// ==========================================
// Main - Try different algorithms!
// ==========================================

int main() {
std::cout << "=== Dispatcher Pattern Example ===\n";
std::cout << "This is exactly how CK convolution dispatcher works!\n";
// Try Simple algorithm
dispatch<Direction::FORWARD, SimpleAlgorithm>();
// Try Fast algorithm
dispatch<Direction::FORWARD, FastAlgorithm>();
// Try Parallel algorithm
dispatch<Direction::FORWARD, ParallelAlgorithm>();
return 0;
}

/*
DISPATCHER PATTERN SUMMARY:

┌─────────────────────────────────────────┐
│ USER CODE │
│ dispatch<FORWARD, SimpleAlgorithm>() │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ DISPATCHER │
│ ✅ Check direction (if constexpr) │
│ ✅ Check algorithm (if constexpr) │
│ - IsSimpleAlgorithm() │
│ - IsFastAlgorithm() │
│ - IsParallelAlgorithm() │
│ ✅ Route to correct factory │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ FACTORY │
│ requires (DIR == FORWARD) │
│ ❌ NO algorithm check! │
│ ✅ Just build the thing │
└─────────────────────────────────────────┘

KEY POINTS:
1. Dispatcher uses consteval functions with "if constexpr"
2. Factories use concepts with "requires"
3. Algorithm checking happens in dispatcher, not factory
4. This is EXACTLY the CK convolution pattern!
*/


..

### 3. Dispatcher Pattern (03_dispatcher_pattern.cpp) ⭐ MOST IMPORTANT
**What it teaches:** Exactly how CK convolution works!

```cpp
// Detection functions (like IsReferenceAlgorithm!)
template <typename T>
consteval bool IsSimpleAlgorithm() {
return std::is_same_v<T, SimpleAlgorithm>;
}

// Dispatcher - checks algorithm and routes
template <Direction DIR, typename Algorithm>
void dispatch() {
if constexpr (DIR == Direction::FORWARD) {
// Check algorithm type here!
if constexpr (IsSimpleAlgorithm<Algorithm>()) {
SimpleFactory<DIR, Algorithm>::process();
}
else if constexpr (IsFastAlgorithm<Algorithm>()) {
FastFactory<DIR, Algorithm>::process();
}
}
}

// Factory - only checks direction!
template <Direction DIR, typename Algorithm>
requires (DIR == Direction::FORWARD) // Only check direction
struct SimpleFactory {
// NO algorithm check here!
};
```

**Output:**
```
=== DISPATCHER START ===
Direction: FORWARD
Algorithm: SIMPLE
SimpleFactory: Processing with basic algorithm
=== DISPATCHER END ===
```

**Key Point:** This is EXACTLY the CK pattern!


Study.marearts.com!!!