0

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?

6
  • 2
    Don't make the member static. Commented Mar 29, 2021 at 13:23
  • 1
    is_prime should be const too. Commented Mar 29, 2021 at 13:25
  • 1
    Your prime array is not initialized, and has no true. Commented Mar 29, 2021 at 13:27
  • 1
    10'000'000 doesn't necessary fit in unsigned int. Commented Mar 29, 2021 at 13:28
  • 1
    @Monica why shouldn't use static? Commented Mar 29, 2021 at 13:32

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

5 Comments

@L.A.: You can move the free function inside the class as static member function.
@L.A. Sure, I just seem to like it this way. I'm not a fan of private static member functions; they clutter the class IMHO. I've updated the answer to reflect your request.
@L.A. - you can modify the prime array inside the body of the constructor, as init_primes() does, but you must initialize it before, or with a default in the variable declaration (for example: std::array<bool, max_n+1> prime {}; (I mean: the final {})) or in the initialization list of the constructor (for example: ClassicSieve () : prime{} { /* here you can modify prime */ }).
@L.A. - Anyway... you can isn't you should. An additional function (internal or external) has the additional advantage that can be reused for different constructors.
@L.A. - Frankly, I don't know the reason. I only know that "for the [constexpr] constructor of a class or struct, every base class sub-object and every non-variant non-static data member must be initialized" (see this page). Now I see that this rule is signed as "until C++20"... maybe this rule is relaxed and, starting from C++20, an initialization isn't required anymore.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.