View Issue Details

IDProjectCategoryView StatusLast Update
0036161FPCCompilerpublic2019-10-20 09:41
ReporterMartin FriebeAssigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
Platform64bit IntelOSwin 10OS Version10
Product Version3.2.0Product Build43036 
Target VersionFixed in Version 
Summary0036161: regression: Math.Min and sizeof cause "Error: Can't determine which overloaded function to call"
DescriptionBelow compiles fine with fpc 3.0.4

But 3.2 (tested 32bit) gives an error.

program Project1;
uses Math;
var
  t: Qword;

begin
  writeln( Min(sizeof(t), t) );
end.
TagsNo tags attached.
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files

Activities

Martin Friebe

2019-10-10 13:08

manager   ~0118460

Might just be a case to document on https://wiki.lazarus.freepascal.org/User_Changes_3.2 ?

Akira1364

2019-10-10 16:52

reporter   ~0118465

Last edited: 2019-10-10 18:35

View 6 revisions

This is because in 3.0.4, "Min" did not have an overload for QWord, but does now (added at some point in the "3.1.1" series lifetime, not sure when exactly.)

"SizeOf" returns Int64, so the 3.0.4 compiler would have been calling the Int64 overload, and just silently doing an implicit cast of "T" from QWord to Int64.

The (big) problem with that however was the fact that QWord and Int64 obviously are not capable of storing the same range of values (QWord goes from 0 to 18446744073709551615, while Int64 goes from -9223372036854775808 to 9223372036854775807.)

What that means of course is anytime it was doing the QWord to Int64 cast, it was running the risk of the original value being higher than what Int64 can actually represent and thus potentially giving wrong results.

So I wouldn't call this a regression, really: it's more like "the Math unit no longer supports something that it never should have made possible in the first place."

The obvious solution in your case though is to *explicitly* cast "SizeOf(T)" to QWord so that the compiler chooses the (now existing) QWord overload, since "SizeOf" has predictable, small, non-negative results in all cases.

Marģers

2019-10-10 18:14

reporter   ~0118471

Last edited: 2019-10-10 18:21

View 3 revisions

sizeof(t) is signed integer and t is unsigned, so there is no way to pick right min function. Problem is return type of function, it can not be int64 nor qword. So there is no min ( int64, qword) overload version.

Min ( qword( sizeof(t) ), t ) - works
Min ( sizeof(t), int64(t) ) - works

Akira1364

2019-10-10 21:57

reporter   ~0118481

Last edited: 2019-10-11 19:10

View 6 revisions

Thought I'd post a little example of how you can use records and operator overloading to mostly solve this problem in case it might be of use to anyone:

program Example;

{$mode ObjFPC}
{$modeswitch AdvancedRecords}

uses Math;

type
  TAnyInt = record
    class operator :=(const I: Int64): TAnyInt; inline;
    class operator :=(const Q: QWord): TAnyInt; inline;
    class operator :=(constref A: TAnyInt): Int64; inline;
    class operator :=(constref A: TAnyInt): QWord; inline;
    class operator <(constref A, B: TAnyInt): Boolean; inline;
    class operator >(constref A, B: TAnyInt): Boolean; inline;
    class operator =(constref A, B: TAnyInt): Boolean; inline;
    case Signed: Boolean of
      True: (S: Int64);
      False: (U: QWord);
  end;

  class operator TAnyInt.:=(const I: Int64): TAnyInt;
  begin
    with Result do begin
      Signed := True;
      S := I;
    end;
  end;

  class operator TAnyInt.:=(const Q: QWord): TAnyInt;
  begin
    with Result do begin
      Signed := False;
      U := Q;
    end;
  end;

  class operator TAnyInt.:=(constref A: TAnyInt): Int64;
  begin
    if A.Signed then
      Result := A.S
    else if A.U <= High(Int64) then
      Result := A.U
    else
      Result := 0;
  end;

  class operator TAnyInt.:=(constref A: TAnyInt): QWord;
  begin
    if not A.Signed then
      Result := A.U
    else if A.S >= 0 then
      Result := A.S
    else
      Result := 0;
  end;

  class operator TAnyInt.<(constref A, B: TAnyInt): Boolean;
  begin
    if (A.Signed) and (B.Signed) then
      Result := A.S < B.S
    else if (not A.Signed) and (not B.Signed) then
      Result := A.U < B.U
    else if (A.Signed) and (not B.Signed) then
      Result := (A.S < 0) or (A.S < B.U)
    else if (not A.Signed) and (B.Signed) then
      Result := A.U > B.S;
  end;

  class operator TAnyInt.>(constref A, B: TAnyInt): Boolean;
  begin
    if (A.Signed) and (B.Signed) then
      Result := A.S > B.S
    else if (not A.Signed) and (not B.Signed) then
      Result := A.U > B.U
    else if (A.Signed) and (not B.Signed) then
      Result := A.S > B.U
    else if (not A.Signed) and (B.Signed) then
      Result := A.U > B.S;
  end;

  class operator TAnyInt.=(constref A, B: TAnyInt): Boolean;
  begin
    if (A.Signed) and (B.Signed) then
      Result := A.S = B.S
    else if (not A.Signed) and (not B.Signed) then
      Result := A.U = B.U
    else if (A.Signed) and (not B.Signed) then
      Result := A.S = B.U
    else if (not A.Signed) and (B.Signed) then
      Result := A.U = B.S;
  end;

  function AnyMin(constref A, B: TAnyInt): Int64; inline; overload;
  begin
    if A < B then
      Result := A
    else
      Result := B;
  end;

  function AnyMin(constref A, B: TAnyInt): QWord; inline; overload;
  begin
    if A < B then
      Result := A
    else
      Result := B;
  end;

  function AnyMax(constref A, B: TAnyInt): Int64; inline; overload;
  begin
    if A > B then
      Result := A
    else
      Result := B;
  end;

  function AnyMax(constref A, B: TAnyInt): QWord; inline; overload;
  begin
    if A > B then
      Result := A
    else
      Result := B;
  end;

begin
  // ↓↓↓ correctly prints -5
  WriteLn(AnyMin(-5, High(QWord) - 7));

  // ↓↓↓ wraps around and prints 18446744073709551608
  WriteLn(Math.Min(-5, High(QWord) - 7));

  // ↓↓↓ Correctly prints 8
  WriteLn(AnyMin(SizeOf(QWord), QWord(12)));

  // ↓↓↓ "Error: Can't determine which overloaded function to call"
  // WriteLn(Math.Min(SizeOf(QWord), QWord(12)));
end.

Marģers

2019-10-11 01:09

reporter   ~0118486

B := High(QWord) -7; //-- Min(A, B) == 0

Akira1364

2019-10-11 02:17

reporter   ~0118487

Woops! I missed a check in the "less than" operator overload.

Just edited my comment to fix it, and changed the example a bit to show that you don't even need to use explicit "TAnyInt" variables at all.

Martin Friebe

2019-10-11 12:37

manager   ~0118493

@Everyone: This is not a forum or mail-list, where I am looking for solutions.

This is an issue tracker.
The issue here being that
a) previous working code no longer works (even though intentionally / never mind that it is easy to fix). And the issue
b) It is (as far as my search went) not documented in the appropriate place.

Florian

2019-10-20 09:41

administrator   ~0118720

@Martin: What kind of documentation do you expect?

Issue History

Date Modified Username Field Change
2019-10-10 12:54 Martin Friebe New Issue
2019-10-10 13:08 Martin Friebe Note Added: 0118460
2019-10-10 16:52 Akira1364 Note Added: 0118465
2019-10-10 16:54 Akira1364 Note Edited: 0118465 View Revisions
2019-10-10 16:56 Akira1364 Note Edited: 0118465 View Revisions
2019-10-10 16:59 Akira1364 Note Edited: 0118465 View Revisions
2019-10-10 18:14 Marģers Note Added: 0118471
2019-10-10 18:19 Marģers Note Edited: 0118471 View Revisions
2019-10-10 18:21 Marģers Note Edited: 0118471 View Revisions
2019-10-10 18:31 Akira1364 Note Edited: 0118465 View Revisions
2019-10-10 18:35 Akira1364 Note Edited: 0118465 View Revisions
2019-10-10 21:57 Akira1364 Note Added: 0118481
2019-10-10 22:21 Akira1364 Note Edited: 0118481 View Revisions
2019-10-11 01:09 Marģers Note Added: 0118486
2019-10-11 02:15 Akira1364 Note Edited: 0118481 View Revisions
2019-10-11 02:17 Akira1364 Note Added: 0118487
2019-10-11 02:20 Akira1364 Note Edited: 0118481 View Revisions
2019-10-11 02:39 Akira1364 Note Edited: 0118481 View Revisions
2019-10-11 12:37 Martin Friebe Note Added: 0118493
2019-10-11 19:10 Akira1364 Note Edited: 0118481 View Revisions
2019-10-20 09:41 Florian Note Added: 0118720