Thread overview
March 23

The next code works as is but ...

import std.stdio;
import std.traits;

bool isPrime(T)(T n) if (isIntegral!T) {
    if (n <= T(3)) return n > T(1);

    if (n % T(2) == T(0) || n % T(3) == T(0)) return false;

    for (T candidate = T(5); candidate * candidate <= n; candidate += T(6)) {
        if (n % candidate == T(0) || n % (candidate + T(2)) == T(0)) {
            return false;
        }
    }

    return true;
}

T nextPrime(T)(T n) if (isIntegral!T) {
    if (n < T(2))
        return T(2);

    T candidate = (n % T(2) == T(0)) ? n + T(1) : n + T(2); // Start from next Odd

    for (;; candidate += T(2)) { // Skip even
        if (isPrime(candidate)) {
            return candidate;
        }
    }
}

void main() {
    int num = 10; // Example starting number
    writeln("\nNext prime after ", num, " is ", nextPrime(num));
}

... it doesn't at all once I change int num = 10; to const int num = 10;. I'm confused

How to fix it?

March 23
On Saturday, March 23, 2024 1:30:29 PM MDT Menjanahary R. R. via Digitalmars- d-learn wrote:
> The next code works as is but ...
>
> ```
> import std.stdio;
> import std.traits;
>
> bool isPrime(T)(T n) if (isIntegral!T) {
>      if (n <= T(3)) return n > T(1);
>
>      if (n % T(2) == T(0) || n % T(3) == T(0)) return false;
>
>      for (T candidate = T(5); candidate * candidate <= n;
> candidate += T(6)) {
>          if (n % candidate == T(0) || n % (candidate + T(2)) ==
> T(0)) {
>              return false;
>          }
>      }
>
>      return true;
> }
>
> T nextPrime(T)(T n) if (isIntegral!T) {
>      if (n < T(2))
>          return T(2);
>
>      T candidate = (n % T(2) == T(0)) ? n + T(1) : n + T(2); //
> Start from next Odd
>
>      for (;; candidate += T(2)) { // Skip even
>          if (isPrime(candidate)) {
>              return candidate;
>          }
>      }
> }
>
> void main() {
>      int num = 10; // Example starting number
>      writeln("\nNext prime after ", num, " is ", nextPrime(num));
> }
> ```
>
> ... it doesn't at all once I change `int num = 10;` to `const int num = 10;`. I'm confused
>
> How to fix it?

Well, when nextPrime is instantiated, the type of T is inferred from the function argument. So, if num is int, then T is int, whereas if num is const int, then T is const int. The same with isPrime.

And so, when you declare candidate to be T, it'll be const int when num is const int, in which case, you can't use += on it, because that would mutate it.

One way to fix this would be to do something like

    alias U = Unconst!T;

at the top of each of your functions (Unconst is from std.traits and removes const, inout, and immutable from the type), and then instead of using T within the function, you use U - though if you want to return a mutable value, then you'd want to change nextPrime to return auto instead of T.

Another approach that does basically the same thing would be to change the function signatures to

    bool isPrime(T : const U, U)(T n) if (isIntegral!T)

    T nextPrime(T : const U, U)(T n) if (isIntegral!T)

https://dlang.org/spec/template.html#argument_deduction in the spec talk about that trick a bit, but it's basically a way to declare a second template parameter based on the first one. It's more obtuse if you're not used to reading that sort of thing, but it infers U based on the fact that T has to be implicitly convertible to const U, which in effect means that U is mutable whether T was mutable, const, immutable, or inout. Either way, isIntegral!T still restricts T to be an integral type. Ultimately, the effect is the same as declaring

    alias U = Unconst!T;

inside the function, but it's then part of the function signature, and you didn't need to instantiate Unconst. And of course, like the first solution, you would need to change the internals of those functions to use U instead of T. So, which you go with is a matter of personal preference.

On the other hand, if you don't absolutely need to retain the original type, an even simpler solution is to just use long.

    bool isPrime(long n)

    long nextPrime(long n)

since then any of the built-in integral types will work with it - but of course, the result is then long, not int or whatever it is that you passed in.

Regardless, the key thing to understand here is that when templated functions infer the types of their arguments, any qualifiers on the type are normally left on the type. The one exception is dynamic arrays. They're inferred as having the type you get when slicing them - which is tail-const. E.G.

immutable string s;
static assert(typeof(s[]) == string);

or

const(int[]) arr;
static assert(typeof(arr[]) == const(int)[]);

For better or worse, arrays do that to avoid requiring that you explicitly slice them to pass them to range-based functions in the cases where you did something like declare a string to be immutable. But nothing like that happens with any other types, so if you pass a const Foo - or a const int - to a templated function, it's instantiated with that exact type.

- Jonathan M Davis



March 23

On Saturday, 23 March 2024 at 19:30:29 UTC, Menjanahary R. R. wrote:

>
for (T candidate = T(5); candidate * candidate <= n; candidate += T(6)) {

When T is const int, the above code declares and initializes a constant variable:

const int candidate = const int(5);

Then, at the end of each loop iteration, it does:

candidate += const int(6);

So you are trying to modify a constant. Constants can only be initialized, never assigned.

>
T candidate = (n % T(2) == T(0)) ? n + T(1) : n + T(2); // Start from next Odd

for (;; candidate += T(2)) { // Skip even

Same here, you declare a constant then try to assign to it at the end of each loop iteration.

March 24
On Saturday, 23 March 2024 at 20:38:40 UTC, Jonathan M Davis wrote:
> On Saturday, March 23, 2024 1:30:29 PM MDT Menjanahary R. R. via Digitalmars- d-learn wrote:
>> [...]
>
> Well, when nextPrime is instantiated, the type of T is inferred from the function argument. So, if num is int, then T is int, whereas if num is const int, then T is const int. The same with isPrime.
>
> [...]

Thanks for your prompt answer. It works like a charm.

It's always a pleasure to learn from the Dlang community.

March 24

On Saturday, 23 March 2024 at 20:49:14 UTC, Nick Treleaven wrote:

>

On Saturday, 23 March 2024 at 19:30:29 UTC, Menjanahary R. R. wrote:

>
for (T candidate = T(5); candidate * candidate <= n; candidate += T(6)) {

When T is const int, the above code declares and initializes a constant variable:

const int candidate = const int(5);

Then, at the end of each loop iteration, it does:

candidate += const int(6);

So you are trying to modify a constant. Constants can only be initialized, never assigned.

>
T candidate = (n % T(2) == T(0)) ? n + T(1) : n + T(2); // Start from next Odd

for (;; candidate += T(2)) { // Skip even

Same here, you declare a constant then try to assign to it at the end of each loop iteration.

Thanks for your prompt answer.