L.A.
L.A.

Reputation: 253

Constexpr class c++

I'm trying to create a class that is constructed at compile time and then can be used, of course without any method that modifies it. I'm new with the keywords static and constexpr, so probably I'm not using them properly.

Here is my code:

#include <array>
#include <iostream>

using namespace std;

template<typename unsigned_integer, unsigned_integer max_n>
class ClassicSieve {
  private:
    static array<bool, max_n+1> prime;
    //unsigned_integer max_n;
  public:
    constexpr ClassicSieve() {
      // 1 is not prime
      prime[1] = false;
      // even numbers
      for(unsigned_integer i = 4; i <= max_n; i+=2) {
        prime[i] = false;
      }
      // all the others
      for (unsigned_integer p = 3; p*p <= max_n; p++) {
        if (prime[p]) {
          for(unsigned_integer j = 2*p; j <= max_n; j+=p) {
            prime[j] = false;
          }
        }
      }
    }

    inline bool is_prime(unsigned_integer n) {
      return prime[n];
    }
};

int main() {
  constexpr auto s = ClassicSieve<unsigned int, 10000000>();
  cout << s.is_prime(2) << endl;
  cout << s.is_prime(3) << endl;
  cout << s.is_prime(4) << endl;
  cout << s.is_prime(5) << endl;
  cout << s.is_prime(6700417) << endl;
}

Once max_n is fixed all the computation needed can be evaluated at compile-time, so for sure, it can be possible to do what I'm trying. The method is_prime just read the data_member prime without modifying it.

When I compile I get:

note: a constant expression cannot modify an object that is visible outside that expression

referred to the constructor. I see that this is a problem in general, but in my case I won't modify the object anymore. How can I tell it to the compiler?

Upvotes: 0

Views: 1015

Answers (1)

AndyG
AndyG

Reputation: 41220

There are many issues with this code:

  • Your std::array is uninitialized, and only some values are set to false afterwards; you need to initialize all values to true
  • You'll want to mark is_prime const
  • You are trying to allocate (and loop) 10 million elements at compile-time, which is outside most compiler limits (you'll need to find the appropriate flag for your compiler to increment that limit)
    • most (all?) implementations of std::array attempt to perform stack-allocation, and this will take up a significant amount of that
  • (minor) You don't need to declare is_prime to be inline; it already is by virtue of being defined in the class declaration

Here's an implementation that will do the trick (I've restricted the size to 10 instead of 10 million):

template<typename unsigned_integer, unsigned_integer max_n>
class ClassicSieve {
  private:
    static constexpr const std::array<bool, max_n+1> prime = ClassicSieve::init_primes();
    static constexpr auto init_primes()
    {
        std::array<bool, max_n+1> prime{};
        prime.fill(true);
        // 1 is not prime
        prime[1] = false;
        // even numbers
        for(unsigned i = 4; i <= max_n; i+=2) {
            prime[i] = false;
        }
        // all the others
        for (unsigned p = 3; p*p <= max_n; p++) {
            if (prime[p]) {
                for(unsigned j = 2*p; j <= max_n; j+=p) {
                    prime[j] = false;
                }
            }
        }
        return prime;
    }
  public:
    bool is_prime(unsigned_integer n) const {
      return prime[n];
    }
};

(*This code assumes at least a 32 bit unsigned integer)

Usage:

constexpr auto s = ClassicSieve<unsigned int, 10>();
std::cout << std::boolalpha << s.is_prime(2) << std::endl; // true
std::cout << std::boolalpha << s.is_prime(3) << std::endl; // true
std::cout << std::boolalpha << s.is_prime(4) << std::endl; // false
std::cout << std::boolalpha << s.is_prime(5) << std::endl; // true

Demo

Upvotes: 4

Related Questions