C++模板簡介(二)—— 模板元編程

編譯期的整數操作

​ 模板元編程是一個挺有意思 (但是毫無卵用) 的東西。比如我們可以實現編譯期的快速排序。(但是 constexpr 函數基本把這部分給廢掉了)

導入

​ 下面是一個例子:

template <int A, int B>
struct add { static constexpr int value = A + B; };

cout << add<1, 2>::value;

輸出 3,但是這個 3 是在編譯期計算出來的而不是運行時。不過實際上。。

template <typename T>
constexpr T add(T a, T b) { return a + b; }

就可以實現一樣的東西,而且看起來更直觀。

那麼我們學這個有什麼用呢?(純粹好玩)

基本類型

integer_constant

我們希望一些運算的工作放在編譯期完成,那麼我們就需要一些東西來存儲或表示我們的運算結果。我們從導入中提到的例子能看到,我們表示一個編譯期的整數,都是在模板的參數中表示。那麼我們就知道了應該如何表示一個編譯期的常量:

template <typename T, T v>
struct integral_constant {
    static constexpr T value = v;
    typedef T value_type;
    constexpr operator T() const noexcept { return value; }
    constexpr T operator()() const noexcept { return value; }
};

template <int v>
using integer_constant = integral_constant<int, v>;

這樣我們就能在模板參數中表示一個整型的常量,常量的值就在模板參數中,比如:integer_constant<5> 這個類型就能表示 5。

integer_sequence

函數式編程中另一個重要的數據結構就是列表,我們再來看如何表示一個列表:

template <typename T, T... values>
struct integral_sequence { };

template <int ... values>
using integer_sequence = integral_sequence<int, values...>;

這裏利用了可變模板參數來實現列表。這樣我們就可以通過 integer_sequence<1, 2, 3, 4, 5> 來表示列表:[1, 2, 3, 4, 5]

基本函數

現在有了基本數據類型,我們就可以在這上面構建一系列的函數了。我們已經知道了模式匹配,那麼我們就可以照貓畫虎地寫出相應的基本函數:

length

template <typename integer_sequence>
struct length;

template <int... values>
struct length<integer_sequence<values...>> : integral_constant<size_t, sizeof...(values)> {};

template <typename T>
constexpr size_t length_v = length<T>::value;

push_front

template <int V, typename integer_sequence>
struct push_front;

template <int V, int... values>
struct push_front<V, integer_sequence<values...>> {
	using type = integer_sequence<V, values...>;
};

template <int V, typename T>
using push_front_t = typename push_front<V, T>::type;

////////

push_front_t<1, integer_sequence<2, 3, 4, 5>> === integer_sequence<1, 2, 3, 4, 5>;

empty

template <typename integer_sequence>
struct empty;

template <>
struct empty<integer_sequence<>> : std::true_type {};

template <int... values>
struct empty<integer_sequence<values...>> : std::false_type {};

template <typename T>
constexpr bool empty_v = empty<T>::type::value;

////////

empty_v<integer_sequence<>> == true;
empty_v<integer_sequence<1, 2, 3, 4, 5>> == false;

conditional

這個 if 像一個三目表達式。函數式語言中 if 只是一個表達式,必須有 else 子句。

template <bool Cond, typename Then, typename Else>
struct conditional;

template <typename Then, typename Else>
struct conditional<true, Then, Else> { using type = Then; };

template <typename Then, typename Else>
struct conditional<false, Then, Else> { using type = Else; };

template <bool Cond, typename Then, typename Else>
using conditional_t = typename conditional<Cond, Then, Else>::type;

///////

conditional<true, true_type, false_type> === true_type;
conditional<false, false_type, true_type> === true_type;

map

template <template <int> typename Mapper, typename integer_sequence>
struct map;

template <template <int> typename Mapper>
struct map<Mapper, integer_sequence<>> {
	using type = integer_sequence<>;
};

template <template <int> typename Mapper, int head, int... tail>
struct map<Mapper, integer_sequence<head, tail...>> {
	using type = typename push_front<Mapper<head>::value, typename map<Mapper, integer_sequence<tail...>>::type>::type;
};

template <template <int> typename Mapper, typename T>
using map_t = typename map<Mapper, T>::type;

/////////
template <int T> struct increment { static constexpr int value = T + 1; };
map_t<increment, integer_sequence<1, 2, 3, 4, 5>> === integer_sequence<2, 3, 4, 5, 6>;

filter

filter f [] = []
filter f (x:[]) = if (f x) [x] else []
filter f (x:xs) = if (f x) [x] ++ (filter f xs) else (filter f xs)
template <template <int> typename Filter, typename integer_sequence>
struct filter;

template <template <int> typename Filter>
struct filter<Filter, integer_sequence<>> {
	using type = integer_sequence<>;
};

template <template <int> typename Filter, int head, int... tail>
struct filter<Filter, integer_sequence<head, tail...>> {
	using type = typename conditional<Filter<head>::value,
		typename push_front<head, typename filter<Filter, integer_sequence<tail...>>::type>::type,
		typename filter<Filter, integer_sequence<tail...>>::type
		>::type;
};

template <template <int> typename Filter, typename T>
using filter_t = typename filter<Filter, T>::type;

/////////
template <int T> struct is_even { static constexpr bool value = T % 2 == 0; };
filter_t<is_even, integer_sequence<1, 2, 3, 4, 5>> === integer_sequence<2, 4>;

print

void print(integer_sequence<> seq) {
    std::cout << std::endl;
}

template <int head>
void print(integer_sequence<head> seq) {
    std::cout << head << std::endl;
}

template <int head, int... tail>
void print(integer_sequence<head, tail...> seq) {
    std::cout << head << ' ';
    print(integer_sequence<tail...>());
}

//////// for integer_sequence<1, 2, 3, 4, 5> outputs "1 2 3 4 5\n"

concat

template <typename I1, typename I2>
struct concat;

template <typename I1, typename I2>
using concat_t = typename concat<I1, I2>::type;

template <typename I1>
struct concat<I1, integer_sequence<>> {
    using type = I1;
};

template <typename I1, int head, int... tail>
struct concat<I1, integer_sequence<head, tail...>> {
    using type = concat_t<push_back_t<head, I1>, integer_sequence<tail...>>;
};

reverse

template <typename integer_sequence>
struct reverse;

template <>
struct reverse<integer_sequence<>> {
    using type = integer_sequence<>;
};

template <int head, int... tail>
struct reverse<integer_sequence<head, tail...>> {
    using type = typename push_back<head, typename reverse<integer_sequence<tail...>>::type>::type;
};

template <typename T>
using reverse_t = typename reverse<T>::type;

/////////
reverse_t<integer_sequence<1, 2, 3, 4, 5>> === integer_sequence<5, 4, 3, 2, 1>;

reduce

template <int e, template <int, int> typename F, typename integer_sequence>
struct reduce;

template <int e, template <int, int> typename F>
struct reduce<e, F, integer_sequence<>> {
    static constexpr int value = e;
};

template <int e, template <int, int> typename F, int head, int... tail>
struct reduce<e, F, integer_sequence<head, tail...>> {
    static constexpr int value = F<head, reduce<e, F, integer_sequence<tail...>>::value>::value;
};

////////// samples

template <int A, int B> struct add {
    static const vlaue = A + B;
};

cout << reduce<0, add, integer_sequence<1, 2, 3, 4, 5>>::value;
// outputs 15

format

我在這裏插一句,已經不知道該寫在哪裏了。。。我們可以利用 C++ 的可變模板參數列表實現一個簡易的字符串格式化工具函數,比如 format("Welcome! {}. Current time is {}:{}:{}.", sir, hour, minute, second)從而實現對 String.format 的模仿。這個應該很容易想出來怎麼寫:

template <typename StreamT>
void format0(StreamT &ss, const wchar_t *fmt)
{
    ss << fmt;
}

template <typename StreamT, typename T, typename... Args>
void format0(StreamT &ss, const wchar_t *fmt, const T &arg0, Args... args)
{
    if (!(*fmt))
        return;
    if (*(fmt + 1) && *fmt == L'{' && *(fmt + 1) == L'}')
    {
        ss << arg0;
        format0(ss, fmt + 2, args...);
    }
    else
    {
        ss << *fmt;
        format0(ss, fmt + 1, arg0, args...);
    }
}

template <typename... Args>
std::wstring format(const wchar_t *fmt, Args... args)
{
    std::owstringstream ss;
    format0(ss, fmt, args...);
    return ss.str();
}

排序

插入排序

接下來我們實現一個編譯期的插入排序。首先我們看 Haskell 的版本。首先是插入函數

insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
insert x (y:ys) = if x < y then x:y:ys else y:insert x ys

那麼實現插入排序就很簡單了:

insertionSort :: Ord a => [a] -> [a]
insertionSort [] = []
insertionSort (x:xs) = insert x (insertionSort xs)

我們對應實現 C++ 版本:

template <template <int, int> typename Compare, int x, typename integer_sequence>
struct insert;

template <template <int, int> typename Compare, int x>
struct insert<Compare, x, integer_sequence<>> {
    using type = integer_sequence<x>;
};

template <template <int, int> typename Compare, int x, int head, int... tail>
struct insert<Compare, x, integer_sequence<head, tail...>> {
    using type = typename conditional<Compare<x, head>::value,
                                      integer_sequence<x, head, tail...>,
                                      typename push_front<head, typename insert<Compare, x, integer_sequence<tail...>>::type>::type>::type;
};

template <template <int, int> typename Compare, typename integer_sequence>
struct insertion_sort;

template <template <int, int> typename Compare>
struct insertion_sort<Compare, integer_sequence<>> {
    using type = integer_sequence<>;
};

template <template <int, int> typename Compare, int head, int... tail>
struct insertion_sort<Compare, integer_sequence<head, tail...>> {
    using type = typename insert<Compare, head, typename insertion_sort<Compare, integer_sequence<tail...>>::type>::type;
};

測試樣例:

template <int A, int B>
struct lessThan {
    static const bool value = A < B;
};

int main() {
    using s = integer_sequence<7, 5, 4, 6, 0>;
    print(typename insertion_sort<lessThan, s>::type());
}

快速排序

我們先看 Haskell 版本的快速排序:

quicksort [] = []
quicksort (x:xs) = quicksort small ++ (x : quicksort large)
    where small = [y | y <- xs, y < x]
          large = [y | y <- xs, y >= x]

這個快速排序很簡單,每次取列表的第一個元素作爲中值。

template <template <int, int> typename Compare, typename integer_sequence>
struct quick_sort;

template <template <int, int> typename Compare>
struct quick_sort<Compare, integer_sequence<>> {
    using type = integer_sequence<>;
};

template <template <int, int> typename Compare, int head, int... tail>
struct quick_sort<Compare, integer_sequence<head, tail...>> {
    template <int y>
    using partial_compare_t = Compare<y, head>;

    template <int y>
    struct not_partial_compare_t {
        static constexpr bool value = !partial_compare_t<y>::value;
    };

    using type = concat_t<
        typename quick_sort<Compare, filter_t<partial_compare_t, integer_sequence<tail...>>>::type,
        push_front_t<head, typename quick_sort<Compare, filter_t<not_partial_compare_t, integer_sequence<tail...>>>::type>>;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章