編譯期的整數操作
模板元編程是一個挺有意思 (但是毫無卵用) 的東西。比如我們可以實現編譯期的快速排序。(但是 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>;
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>>;
};