www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Mutability issue

reply Menjanahary R. R. <megnany afaky.com> writes:
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?
Mar 23
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Mar 23
parent Menjanahary R. R. <megnany afaky.com> writes:
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.
Mar 24
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
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: ```d const int candidate = const int(5); ``` Then, at the end of each loop iteration, it does: ```d 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.
Mar 23
parent Menjanahary R. R. <megnany afaky.com> writes:
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: ```d const int candidate = const int(5); ``` Then, at the end of each loop iteration, it does: ```d 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.
Mar 24