Check if number is prime during compilation in C++

You do not need preprocessor to compute something at compile-time. Usually, when computation is needed, you use template metaprogramming (or constexpr functions as suggested by chris in his answer)

Via template metaprogramming you can solve the task as follows:

First you define a template which can check at compile time if the given value N is divisble by D or any value lower than D greater than 1.

template <int N, int D>
struct tmp {
    static const bool result = (N%D) && tmp<N,D-1>::result;
};

template <int N>
struct tmp<N,1> {
    static const bool result = true;
};

The value tmp<N,D>::result is true only when the numbers 2, 3, ... D do not divide N.

With the above tool at hand, creating is_prime compile-time checker is fairly easy:

template <int N>
struct is_prime {
    static const bool result = tmp<N,N-1>::result;
};

Now the compile-time value is_prime<N>::result is true when N is prime, and false otherwise. The value can be supplied to further templates, like the Assert of yours.


C++11 constexpr version that should be able to check numbers up to roughly 1500 on any compiler that implements the suggested recursion depth limit:

constexpr bool is_prime_helper( std::size_t target, std::size_t check ) {
  return (check*check > target) ||
    (
      (target%(check+1) != 0)
      && (target%(check+5) != 0)
      && is_prime_helper( target, check+6 )
    );
}
constexpr bool is_prime( std::size_t target ) {
  return (target != 0) && (target !=1) &&
    ( ( target == 2 || target == 3 || target == 5 )
    || ((target%2 != 0) && (target%3 != 0) && (target%5)!=0 &&
    is_prime_helper( target, 6 )));
}

to improve this, we do some fun with a binary search tree:

#include <cstddef>

constexpr bool any_factors( std::size_t target, std::size_t start, std::size_t step) {
  return
      !(start*start*36 > target)
  &&
  (
    ( (step==1)
      && (
        (target%(start*6+1) == 0)
        || (target%(start*6+5) == 0)
      )
    )
    ||
    ( (step > 1)
      &&
      (
        any_factors( target, start, step/2 )
        || any_factors( target, start+step/2, step-step/2 )
      )
    )
  );
}

which we then use like this:

constexpr bool is_prime( std::size_t target ) {
  // handle 2, 3 and 5 explicitly:
  return 
    (target == 2 || target == 3 || target == 5)
  ||
    (
      target != 0
      && target != 1
      && target%2 != 0
      && target%3 != 0
      && target%5 != 0
      && !any_factors( target, 1, target/6 + 1 ) // can make that upper bound a bit tighter, but I don't care
    );
}
#include <iostream>
int main() {
  std::cout << "97:" << is_prime(97) << "\n";
  std::cout << "91:" << is_prime(91) << "\n";
}

which will recurse ~ log_2(target/6) times, which means that the recursion limit of constexpr expressions of 512 that C++11 standard requests that compilers implement as a minimum is no longer a problem.

Live example, with debugging embedded.

This will work up to basically the limits of std::size_t on your system. I've tested it with 111111113.

This is insanely easier in c++14, as we no longer need one-line constexpr functions, but instead sane structure. See here.

constexpr bool any_factors( std::size_t target, std::size_t start, std::size_t step ) {
  if (start*start*36 > target)
  {
      return false;
  }
  if (step==1)
  {
    bool one_mod_6 = target%(start*6+1) == 0;
    bool five_mod_6 = target%(start*6+5) == 0;
    return one_mod_6 || five_mod_6;
  }

  bool first_half = any_factors(target, start, step/2);
  bool second_half = any_factors(target, start+ step/2, (step+1)/2);

  return first_half || second_half;  
}

Here's an amateur solution that is for positive numbers and done at compile time, but it can't go too far before it breaks due to a recursion limit. I suppose you could add a square root parameter that you calculate manually to allow it to go up to what it does now squared. It does take advantage of C++11's constexpr functions, though, to make the syntax a bit nicer to use without extra work. In any case, it might be a good start and I look forward to seeing answers that work better.

constexpr bool IsPrime(std::size_t N, std::size_t I = 2) {
    return (N !=  2 ? N%I : true) //make 2 prime and check divisibility if not 2
        && (N >= 2) //0 and 1 are not prime
        && (I >= N-1 ? true : IsPrime(N, I+1)); //stop when I is too big
}

You can even make that square root done for you. For this example, I'll make IsPrime into a helper so that IsPrime can only be called with N:

//basically does ceil(sqrt(N))
constexpr std::size_t Sqrt(std::size_t N, std::size_t I = 2) {
    return I*I >= N ? I : Sqrt(N, I+1);
}

//our old IsPrime function with no default arguments; works with sqrt now
constexpr bool IsPrimeHelper(std::size_t N, std::size_t sqrt, std::size_t I) {
    return (N != 2 ? N%I : true) 
        && (N >= 2) 
        && (I >= sqrt ? true : IsPrimeHelper(N, sqrt, I+1));
}

//our new prime function: this is the interface for the user
constexpr bool IsPrime(std::size_t N) {
    return IsPrimeHelper(N, Sqrt(N), 2);
}

For me, this new version works with the number 521 where the other failed. It even works with 9973. The new expected high should be about the square of the old. If you want to go further, you could even modify IsPrimeHelper to increment by 1 when I is 2 and by 2 when I is not 2. That would lead to a new high of about twice this one.