C++ Template Parameter Packs with sequence



C++ Template Parameter Packs with sequence

Simple Explanation

sequence<x,x,x> is a template that holds a compile-time list of integer values. It's like a

tuple but for numbers only, and the values are fixed at compile time. It's commonly used in

template metaprogramming to pass multiple integer parameters to other templates.


Simple Usage Example

.
#include <iostream>
// A simple sequence template that holds compile-time integers
template<int... Values>
struct sequence {
static constexpr int size = sizeof...(Values);
// Function to print all values
static void print() {
((std::cout << Values << " "), ...); // C++17 fold expression
std::cout << std::endl;
}
};

// Template that uses sequence to create arrays
template<typename T, int... Dims>
class MultiArray {
private:
static constexpr int total_size = (Dims * ...); // multiply all dimensions
T data[total_size];
public:
MultiArray() {
std::cout << "Created array with dimensions: ";
((std::cout << Dims << "x"), ...);
std::cout << " = " << total_size << " elements" << std::endl;
}
static constexpr int get_total_size() { return total_size; }
};

int main() {
// Create sequences with different values
sequence<1, 8> block_warps; // 1 block, 8 warps
sequence<16, 16, 8> tensor_dims; // 16x16x8 tensor
sequence<2, 4, 8, 16> powers_of_2; // sequence of powers

std::cout << "Block warps: ";
block_warps.print(); // Output: 1 8

std::cout << "Tensor dims: ";
tensor_dims.print(); // Output: 16 16 8

std::cout << "Powers of 2: ";
powers_of_2.print(); // Output: 2 4 8 16

// Use sequence values as template parameters
MultiArray<float, 10, 20> matrix2d; // 10x20 = 200 elements
MultiArray<int, 4, 4, 4> tensor3d; // 4x4x4 = 64 elements

return 0;
}
..

Output:
  Block warps: 1 8
  Tensor dims: 16 16 8
  Powers of 2: 2 4 8 16
  Created array with dimensions: 10x20x = 200 elements
  Created array with dimensions: 4x4x4x = 64 elements


Thank you. 🙏

C++ Temporary Function Objects Pattern



A C++ pattern where you create a temporary object and immediately call its function operator in

one line. The syntax Class<Template>{constructor_args}(function_args) creates an object, uses

it, then destroys it automatically.


Simple Usage Example

.

#include <iostream>
#include <vector>

// A simple function object that adds a value to each element
template<typename T>
class AddValue {
private:
T value;
public:
AddValue(T val) : value(val) {}

void operator()(std::vector<T>& arr) {
for(auto& x : arr) {
x += value;
}
}
};

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};

// Pattern: Class<Template>{args}(target)
AddValue<int>{10}(numbers); // Add 10 to each element

// Print result
for(int x : numbers) {
std::cout << x << " "; // Output: 11 12 13 14 15
}

return 0;
}

..


This pattern is commonly used in libraries for one-time operations like filling arrays, applying

transformations, or configuring objects without creating persistent variables.

Thank you! 🙇🏻‍♂️

C++ Variadic Template example code for studying

 

Example 1: Basic Variadic Template

#include <iostream>

// 1. Simple function that accepts any number of arguments
template <typename... Args>
void print_simple(Args... args) {
    std::cout << "Number of arguments: " << sizeof...(args) << std::endl;
}

int main() {
    print_simple();                    // 0 arguments
    print_simple(1);                   // 1 argument
    print_simple(1, 2.5, "hello");    // 3 arguments
    return 0;
}

Example 2: Print All Arguments (Recursion)

#include <iostream>

// Base case: no arguments left
void print() {
    std::cout << std::endl;
}

// Recursive case: print first, then rest
template <typename First, typename... Rest>
void print(First first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);  // Call with remaining arguments
}

int main() {
    print(1, 2, 3);                        // Output: 1 2 3
    print("Hello", 42, 3.14, 'A');        // Output: Hello 42 3.14 A
    return 0;
}

Example 3: Simple Integer Sequence

#include <iostream>

// Our own simple Sequence class
template <int... Values>
struct Sequence {
    static constexpr int size = sizeof...(Values);
    
    static void print() {
        std::cout << "Sequence contains " << size << " values: ";
        ((std::cout << Values << " "), ...);  // C++17 fold expression
        std::cout << std::endl;
    }
};

int main() {
    using Seq1 = Sequence<1, 2, 3>;
    using Seq2 = Sequence<10>;
    using Seq3 = Sequence<>;  // empty
    
    Seq1::print();  // Output: Sequence contains 3 values: 1 2 3
    Seq2::print();  // Output: Sequence contains 1 values: 10
    Seq3::print();  // Output: Sequence contains 0 values:
    
    std::cout << "Seq1 size: " << Seq1::size << std::endl;  // Output: 3
    
    return 0;
}

Example 4: Sequence with Array Access

#include <iostream>
#include <array>

template <int... Values>
struct Sequence {
    static constexpr int size = sizeof...(Values);
    static constexpr std::array<int, size> values = {Values...};
    
    static constexpr int at(int index) {
        return values[index];
    }
    
    static void print_all() {
        for (int i = 0; i < size; ++i) {
            std::cout << "Index " << i << ": " << at(i) << std::endl;
        }
    }
};

int main() {
    using MySeq = Sequence<10, 20, 30, 40>;
    
    std::cout << "Size: " << MySeq::size << std::endl;
    std::cout << "Element at index 2: " << MySeq::at(2) << std::endl;
    
    MySeq::print_all();
    
    return 0;
}

Example 5: Type Sequence (Like std::tuple)

#include <iostream>
#include <typeinfo>

// Store multiple types
template <typename... Types>
struct TypeList {
    static constexpr int size = sizeof...(Types);
};

// Helper to print type names
template <typename T>
void print_type() {
    std::cout << typeid(T).name() << " ";
}

template <typename... Types>
void print_types() {
    std::cout << "Types (" << sizeof...(Types) << "): ";
    ((print_type<Types>()), ...);
    std::cout << std::endl;
}

int main() {
    using MyTypes = TypeList<int, double, char>;
    std::cout << "Number of types: " << MyTypes::size << std::endl;
    
    print_types<int, double, char>();
    print_types<std::string>();
    
    return 0;
}

Example 6: CK-Style Sequence Usage

#include <iostream>

// Simplified CK-style Sequence
template <int... Is>
struct Sequence {
    static constexpr int Size() { return sizeof...(Is); }
    
    template <int N>
    static constexpr int At() {
        constexpr int arr[] = {Is...};
        return arr[N];
    }
};

// Alias for convenience
template <int... Is>
using S = Sequence<Is...>;

// Function that uses sequences for dimension mapping
template <int... InputDims, int... OutputDims>
void describe_mapping(S<InputDims...>, S<OutputDims...>) {
    std::cout << "Mapping: ";
    std::cout << "Input dims [";
    ((std::cout << InputDims << " "), ...);
    std::cout << "] -> Output dims [";
    ((std::cout << OutputDims << " "), ...);
    std::cout << "]" << std::endl;
}

int main() {
    // Define dimension indices
    S<0> batch_dim{};      // Dimension 0: batch
    S<1> height_dim{};     // Dimension 1: height
    S<2> width_dim{};      // Dimension 2: width
    S<3> channel_dim{};    // Dimension 3: channel
    
    // Example: NHWC to NCHW transformation mapping
    describe_mapping(S<0,1,2,3>{}, S<0,3,1,2>{});
    // Output: Mapping: Input dims [0 1 2 3 ] -> Output dims [0 3 1 2 ]
    
    // Example: Merge height and width
    describe_mapping(S<0,1,2,3>{}, S<0,1,2>{});
    // Output: Mapping: Input dims [0 1 2 3 ] -> Output dims [0 1 2 ]
    
    // Access sequence properties
    using NHWC = S<0,1,2,3>;
    std::cout << "NHWC dimensions: " << NHWC::Size() << std::endl;
    std::cout << "Height dimension index: " << NHWC::At<1>() << std::endl;
    
    return 0;
}

Example 7: Advanced - Tensor Transformation Simulator

#include <iostream>
#include <vector>

template <int... Dims>
struct Sequence {
    static constexpr int size = sizeof...(Dims);
    static constexpr int dims[size] = {Dims...};
};

template <int... Is>
using S = Sequence<Is...>;

// Simplified tensor descriptor
struct TensorDesc {
    std::vector<int> shape;
    std::vector<std::string> dim_names;
    
    TensorDesc(std::initializer_list<int> s, std::initializer_list<std::string> n)
        : shape(s), dim_names(n) {}
    
    void print() const {
        std::cout << "Tensor shape: ";
        for (size_t i = 0; i < shape.size(); ++i) {
            std::cout << dim_names[i] << "=" << shape[i] << " ";
        }
        std::cout << std::endl;
    }
};

// Transform function that uses sequences
template <int... InputIndices, int... OutputIndices>
TensorDesc transform_tensor(const TensorDesc& input,
                           S<InputIndices...> in_mapping,
                           S<OutputIndices...> out_mapping) {
    std::vector<int> new_shape;
    std::vector<std::string> new_names;
    
    // Simple pass-through transformation
    constexpr int in_dims[] = {InputIndices...};
    for (int idx : in_dims) {
        new_shape.push_back(input.shape[idx]);
        new_names.push_back(input.dim_names[idx]);
    }
    
    return TensorDesc{new_shape, new_names};
}

int main() {
    // Create NHWC tensor
    TensorDesc input_tensor({32, 224, 224, 3}, {"N", "H", "W", "C"});
    std::cout << "Input ";
    input_tensor.print();
    
    // Transform to NCH (drop W dimension)
    auto output = transform_tensor(input_tensor, 
                                  S<0,3,1>{},    // Take N, C, H
                                  S<0,1,2>{});   // Map to positions 0,1,2
    std::cout << "Output ";
    output.print();
    
    // Another example: reorder to NCHW
    auto reordered = transform_tensor(input_tensor,
                                     S<0,3,1,2>{},  // N,C,H,W order
                                     S<0,1,2,3>{}); // Same positions
    std::cout << "Reordered ";
    reordered.print();
    
    return 0;
}

Key Concepts to Remember:

  1. ... in template: Declares a parameter pack that can accept any number of arguments
  2. ... after variable: Expands the parameter pack
  3. sizeof...(): Gets the number of elements in a pack
  4. Sequence: A compile-time container of values (usually integers)
  5. In CK: Sequences are used to specify which dimensions to transform and how to map them