Skip to content

Guest Lecture: Template Metaprogramming

Prerequisite: typename 的用法

  1. 声明模板参数,如:template <typename T>。不多解释。

  2. 声明嵌套类型,如:

template<typename C>
void print2nd(const C& container){
    if(container.size() >= 2){
        typename C::const_iterator it(container.begin());
        ++it;
        int value = *it;  
        cout<<value;
    }
}

此处,如果没有 typename,C++ 会默认将 C::const_iterator 当作一个变量/静态成员。

Introduction: the my_distance function

我们希望实现一个泛型的距离函数,可以返回两个迭代器之间的举例:

template <typename It>
size_t my_distance (It a, It b) {
    return b - a;
}

但是,这个函数只适用于 random access iterator。

template <typename It>
size_t my_distance (It a, It b) {
    size_t result = 0;
    while (first != last) {
        ++first, ++result;
    }
    return result;
}

但是,这个函数对于 random access iterator 而言,太慢了。

我们希望兼顾两者——对于 RAI,就用上面的方法;对于非 RAI,就用下面的。

Type Computation

image-20240117161356561

There are old and new stuffs in the image above:

image-20240117162118432

Regular vs. meta-functions

image-20240117162659508

Example:

template <typename T>
struct type_identity {
    using type = T;
};

template <int V>
struct int_identity {
    static const int value = V;
};

using K = type_identity<int>::type;
int val = int_identity<3>::value;

Note: We use struct to simulate a function. But struct itself, by essence, is NOT a function.

Summary:

  • Meta-functions are structs that treats its template types/values as the parameters, and places the return values as public members (i.e. member types and static member values) .
  • We never need to create an instance of the struct
  • say, we can add <constructor> = delete; to prevent anyone from instantiate it.

Template Specialization

You can have a "generic" template, as well as "specialized" templates for particular types.

Most classical (fully specialized) example:

template <typename T>   // generic
class vector {
    // implementation using an array
    // that is resized as necessary
}

template <>             // specialized
class vector<bool> {
    // implementation using bit array
    // so each element takes one bit
}

Also a partially specialized example:

template <typename K, typename V>
struct HashMap {
  // generic
};

template <typename K, typename V>
struct HashMap<K*, V> {
  // partially specialized
};

template <typename V>
struct HashMap<int, V> {
  // partially specialized
};

template <>
struct HashMap<int, int> {
  // fully specialized
};

Summary: Deduction Rules

The compiler will

  • rank all the templates
  • and try each one until it finds one that works.

An error will occur only if

  • none of the templates work
  • or if there is a tie.

More examples

Generic and specialized templates resemble "a gigantic if-else statement".

is_same

template <typename U, typename T>
struct is_same {
    static const bool value = false;
}

template <typename T>
struct is_same <T, T> { // `struct [struct name] <...>` is the special syntax for specialized template
    static const bool value = true;  
};

remove_const

template <typename T>
struct remove_const {
    using type = T;
}

template <typename T>
struct remove_const <const T> {
    using type = T; 
};

Finally, the generic yet specialized implementation of my_distance

template <typename It>
size_t my_distance (It a, It b) {
    using category = typename std::iterator_traits<It>::iterator_category; // `category` is a type!
    // `iterator_traits<...>` can be considered as a vector-valued metafunction
    // and `iterator_category` is one of the components of the vector

    if constexpr (std::is_same<category, std::random_access_iterator_tag>::value) {
        return b - a;
    } else {
        size_t result = 0;
        while (first != last) {
            ++first, ++result;
        }
        return result;
    }
}

Note: if constexpr is a new syntax in C++17. The if-else condition can be evaluated at compile time, enabling the compiler to discard one of the branches enclosed in the brackets.