В этом посте я расскажу как сделать совершенно бесполезную вещь — вычислять простые числа при помощи шаблонов C++.
Алгоритмы проиллюстрированы кодом на Scheme, поэтому осторожно: скобочки!
Мы хотм получить compile-time аналог функции. В программировании на шаблонах в качестве функции выступает структура, параметры шаблона которой отвечают аргументам функции, а константные статические члены — результатам.
Аналогичный код на C++:
«Вызов» такой функции происходит через инстанцирование шаблона:
Шаблоны C++ позволяют обрабатывать некоторые значения параметров шаблона особым образом (специализация). Это позволяет организовать условную управляющую конструкцию:
Для организации циклической обработки будем использовать рекурсию. Описанных выше приёмов достаточно для описания любой функции.
Мы хотим получить функцию, которая возвращает i-e простое число:
Сразу такую функцию реализовать не получится, нужно начать с чего-то более простого. Проще всего проверить, является ли некоторое n простым числом. Для проверки проверяем, что остаток от деления n на все числа от n-1 до 1 не равен нулю.
Вопрос о том, считать ли 1 простым числ��м оставим открытым, но в данной реализации проверки 1 считается простым.
Определим функцию, которая находит наименьшее простое число, большее заданного (то есть, следующее).
Что такое i-e простое число? Это следующее за (i-1)-м:
Полный исходный код:
Scheme pastebin.com/21VFjYt0
C++ pastebin.com/cfSvBRNH
Алгоритмы проиллюстрированы кодом на Scheme, поэтому осторожно: скобочки!
Управляющие конструкции
Мы хотм получить compile-time аналог функции. В программировании на шаблонах в качестве функции выступает структура, параметры шаблона которой отвечают аргументам функции, а константные статические члены — результатам.
(define (f x) (* x x)) (f 10) ; => 100
Аналогичный код на C++:
template<int x> struct f { static const int value = x * x; };
«Вызов» такой функции происходит через инстанцирование шаблона:
int main() { std::cout << f<10>::value << std::endl; // 100 }
Шаблоны C++ позволяют обрабатывать некоторые значения параметров шаблона особым образом (специализация). Это позволяет организовать условную управляющую конструкцию:
(define (g x) (if (= x 0) 42 (* x x))) (g 10) ; => 100 (g 0) ; => 42
template<int x> struct g { static const int value = x * x; }; template<> struct g<0> { static const int value = 42; }; int main() { std::cout << g<10>::value << std::endl; // 100 std::cout << g<0>::value << std::endl; // 42 }
Для организации циклической обработки будем использовать рекурсию. Описанных выше приёмов достаточно для описания любой функции.
Вычисление простых чисел
Постановка задачи
Мы хотим получить функцию, которая возвращает i-e простое число:
int main() { std::cout << prime<0>::value << std::endl; // 2 std::cout << prime<1>::value << std::endl; // 3 std::cout << prime<2>::value << std::endl; // 5 std::cout << prime<3>::value << std::endl; // 7 // ... }
Проверка на простоту
Сразу такую функцию реализовать не получится, нужно начать с чего-то более простого. Проще всего проверить, является ли некоторое n простым числом. Для проверки проверяем, что остаток от деления n на все числа от n-1 до 1 не равен нулю.
(define (is-prime n) (is-prime-rec n (- n 1))) (define (is-prime-rec n div) (if (or (= div 1) (= div 0)) #t (and (not (= 0 (remainder n div))) (is-prime-rec n (- div 1))))) (is-prime 2) ; => #t (is-prime 3) ; => #t (is-prime 4) ; => #f (is-prime 5) ; => #t
template<int n, int div> struct is_prime_rec { static const bool value = (n % div != 0) && is_prime_rec<n, div - 1>::value; }; template<int n> struct is_prime_rec<n, 1> { static const bool value = true; }; template<int n> struct is_prime_rec<n, 0> { static const bool value = true; }; template<int n> struct is_prime { static const bool value = is_prime_rec<n, n - 1>::value; }; int main() { std::cout << is_prime<1>::value << std::endl; // 1 std::cout << is_prime<2>::value << std::endl; // 1 std::cout << is_prime<3>::value << std::endl; // 1 std::cout << is_prime<4>::value << std::endl; // 0 std::cout << is_prime<5>::value << std::endl; // 1 std::cout << is_prime<6>::value << std::endl; // 0 std::cout << is_prime<7>::value << std::endl; // 1 }
Вопрос о том, считать ли 1 простым числ��м оставим открытым, но в данной реализации проверки 1 считается простым.
Нахождение следующего простого числа
Определим функцию, которая находит наименьшее простое число, большее заданного (то есть, следующее).
(define (next-prime n) (if (is-prime (+ n 1)) (+ n 1) (next-prime (+ n 1)))) (next-prime 2) ; => 3 (next-prime 3) ; => 5 (next-prime 5) ; => 7 (next-prime 7) ; => 11
template<int n> struct next_prime; template<int n, bool cond> struct next_prime_if { static const int value = n + 1; }; template<int n> struct next_prime_if<n, false> { static const int value = next_prime<n + 1>::value; }; template<int n> struct next_prime { static const int value = next_prime_if<n, is_prime<n + 1>::value>::value; }; int main() { std::cout << next_prime<1>::value << std::endl; // 2 std::cout << next_prime<2>::value << std::endl; // 3 std::cout << next_prime<3>::value << std::endl; // 5 std::cout << next_prime<4>::value << std::endl; // 5 std::cout << next_prime<5>::value << std::endl; // 7 std::cout << next_prime<6>::value << std::endl; // 7 std::cout << next_prime<7>::value << std::endl; // 11 }
Нахождение просто числа по номеру
Что такое i-e простое число? Это следующее за (i-1)-м:
(define (prime i) (if (= i 0) 2 (next-prime (prime (- i 1))))) (prime 0) ; => 2 (prime 1) ; => 3 (prime 2) ; => 5 (prime 3) ; => 7
template<int i> struct prime { static const int value = next_prime<prime<i - 1>::value >::value; }; template<> struct prime<0> { static const int value = 2; }; int main() { std::cout << prime<0>::value << std::endl; // 2 std::cout << prime<1>::value << std::endl; // 3 std::cout << prime<2>::value << std::endl; // 5 std::cout << prime<3>::value << std::endl; // 7 std::cout << prime<4>::value << std::endl; // 11 // ... }
Полный исходный код:
Scheme pastebin.com/21VFjYt0
C++ pastebin.com/cfSvBRNH
