Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced C++ Template Techniques

Tech 1

Non-Type Template Parameters

Template parameters are categorized into type parameters and non-type parameters. A type parameter is preceded by class or typename in the template list, while a non-type parameter is a compile-time constant that functions as a parameter for class or function templates and is treated as a constant within them.

namespace cpp_plus_plus {
// Static array template with default size
 template<typename ElemType, unsigned int DefaultSize = 10>
 class FixedArray {
 public:
  ElemType& At(unsigned int idx) { return arr[idx]; }
  const ElemType& At(unsigned int idx) const { return arr[idx]; }
  unsigned int Count() const { return length; }
  bool IsEmpty() const { return length == 0; }
 private:
  ElemType arr[DefaultSize];
  unsigned int length = 0;
 };
}
#include <iostream>
using namespace std;

template<typename ValueType, unsigned int FixedVal = 10>
void DisplayFixedValue(ValueType& val) {
    val = FixedVal;
    cout << val << endl;
}

int main() {
    int x = 3;
    DisplayFixedValue(x);
    return 0;
}

Key Points:

  1. Floating-point numbers, class objects, and strings cannot serve as non-type template parameters.
  2. Non-type parameters must be determinable at compile time.

Template Specialization

Function Template Specialization

A specialized function template overrides the default template behavior for specific types. Steps to specialize a function template:

  1. Define a base function template first.
  2. Use template<> followed by the specialized functon name.
  3. Specify the target type(s) in angle brackets after the function name.
  4. Match the base template's parameter list exactly to avoid errors.
#include <iostream>
using namespace std;

struct OrderDate {
    int year, month, day;
    OrderDate(int y, int m, int d) : year(y), month(m), day(d) {}
    bool operator<(const OrderDate& rhs) const {
        return year < rhs.year || (year == rhs.year && month < rhs.month) || (year == rhs.month && month == rhs.month && day < rhs.day);
    }
};

// Base function template for comparison
 template<typename CompType>
 bool CompareElements(CompType left, CompType right) {
     return left < right;
 }

// Specialization for OrderDate pointers
 template<>
 bool CompareElements<OrderDate*>(OrderDate* left, OrderDate* right) {
     return *left < *right;
 }

int main() {
    cout << CompareElements(1, 2) << endl;
    OrderDate d1(2022, 7, 7), d2(2022, 7, 8);
    cout << CompareElements(d1, d2) << endl;
    OrderDate* p1 = &d1, * p2 = &d2;
    cout << CompareElements(p1, p2) << endl;
    return 0;
}

Note: For simple type-specific fixes, defining a separate non-template function is often easier than specializing.


Class Template Specialization

Full Specialization

Full specialization replaces all template parameters with specific types.

#include <iostream>
using namespace std;

template<typename TypeA, typename TypeB>
class PairStorage {
 public:
  PairStorage() { cout << "PairStorage<TypeA, TypeB>" << endl; }
 private:
  TypeA item1;
  TypeB item2;
};

// Full specialization for TypeA=int, TypeB=char
 template<>
 class PairStorage<int, char> {
 public:
  PairStorage() { cout << "PairStorage<int, char>" << endl; }
 private:
  int item1;
  char item2;
 };

void TestSpecializations() {
    PairStorage<int, int> s1;
    PairStorage<int, char> s2;
}
Partial Specialization

Partial specialization restricts or fixes part of the template parameters. It can also impose tighter constraints on parameters.

Fixing a Subset of Parameters
// Specialization for TypeB=double
 template<typename TypeA>
 class PairStorage<TypeA, double> {
 public:
  PairStorage() { cout << "PairStorage<TypeA, double>" << endl; }
 private:
  TypeA item1;
  double item2;
 };
Imposing Tighter Constraints
// Specialization for both parameters as references
 template<typename TypeA, typename TypeB>
 class PairStorage<TypeA&, TypeB&> {
 public:
  PairStorage(const TypeA& a, const TypeB& b) : item1(a), item2(b) {
      cout << "PairStorage<TypeA&, TypeB&>" << endl;
  }
 private:
  const TypeA& item1;
  const TypeB& item2;
 };

// Specialization for both parameters as pointers
 template<typename TypeA, typename TypeB>
 class PairStorage<TypeA*, TypeB*> {
 public:
  PairStorage() { cout << "PairStorage<TypeA*, TypeB*>" << endl; }
 private:
  TypeA* item1;
  TypeB* item2;
 };

Usage Example:

void TestPartialSpecializations() {
    PairStorage<double, int> s3;
    PairStorage<int, double> s4;
    PairStorage<int*, char*> s5;
    int a = 10, b = 20;
    PairStorage<int&, int&> s6(a, b);
}

Template Separate Compilation

What Is Separate Compilation?

Separate compilation splits a program into multiple source files, compiling each to an object file and linking them into an executable.

Issues with Templates and Separation

Templates are instantiated on demand. If a template's declaration and definition are split between header (.h) and implementation (.cpp) files:

  • The .cpp file's compiler lacks instantiation context, so no function code is generated.
  • The .h file's compiler has a declaration but no definition for instantiation.
  • The linker fails to find the missing functon definitions.

Example Files:

  • FixedArray.h (declaration)
#include <iostream>
#include <cassert>
namespace cpp_plus_plus {
 template<typename ElemType, unsigned int ArrSize = 10>
 class FixedArray {
 public:
  unsigned int Count() const;
 private:
  ElemType arr[ArrSize];
  unsigned int length = 0;
 };

void HelperFunc();
}
  • FixedArray.cpp (definition)
namespace cpp_plus_plus {
 template<typename ElemType, unsigned int ArrSize>
 unsigned int FixedArray<ElemType, ArrSize>::Count() const {
    return length;
 }

void HelperFunc() {
    std::cout << "cpp_plus_plus::HelperFunc()" << std::endl;
 }

// Explicit instantiation
 template class FixedArray<int>;
 template class FixedArray<double>;
}
  • main.cpp (usage)
#include "FixedArray.h"
using namespace cpp_plus_plus;
int main() {
    FixedArray<int> arr1;
    std::cout << arr1.Count() << std::endl;
    HelperFunc();
    FixedArray<double> arr2;
    std::cout << arr2.Count() << std::endl;
    FixedArray<std::string> arr3;
    // std::cout << arr3.Count() << std::endl; // Error: No explicit instantiation for std::string
    return 0;
}

Solutions

  1. Combine Declaration and Definition: Place all template code (declaration + definition) in a single header file (or .hpp file). This allows the compiler to instantiate the template when it processes the header.
  2. Explicit Instantiation: List all required instantiation types in the .cpp file (as in the FixedArray.cpp example). This is inflexible for generic use.

Template Summary

Advantages:

  1. Code Reuse: Eliminates redundant code for similar data types.
  2. Flexibility: Enables writing generic algorithms that work with any compatiblle type.

Disadvantages:

  1. Code Bloat: Each unique instantiation generates separate binary code.
  2. Debugging Challenges: Compilation errors for templates are often verbose and hard to interpret.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.