The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,262 other subscribers

Delphi compile time assertions

Posted by jpluimers on 2021/02/24

My post on Delphi intrinsic functions that evaluate to consts as a step up to Delphi compile time assertions.

This is a corner case of Delphi language use, which can come in very handy when your code is changed in the future, and you want to be prepared to ensure that some changes do not violate some predefined boundaries.

Hopefully a future post will elaborate a bit more on actual usage, but for now, lets first show some examples, then some other languages that have a richer set of compile time assertions.

My original goal was to see if I could come up with a mechanism that allowed for better validation of generic types because Delphi generic constraints – still – are quite limited: Delphi Constraints in Generics – RAD Studio XE documentation wiki, so limiting or verifying the aspects of the concrete type often cannot be done by constraints.

C# had a similar limitation for constraining to enum, which finally got added some 13 years after adding generics, in 2018: [WayBack] Unmanaged, delegate and enum type constraints – C# 7.3 in Rider and ReSharper – .NET Tools Blog.NET Tools Blog.

Let’s start simple:

const
  // forbidden const values to check compile time assert:
  A = 0;
  B = 1;
  C = -1;
  // The below expressions all each generate a "[dcc32 Error] E2098 Division by zero" (so multiple errors in one compile)
  // Asserting at compile time using boolean expressions:
  BooleanAssertAIsNotZero =  1 div Ord(A <> 0);
  BooleanAssertBIsNotOne = 1 div Ord(B <> 1);
  // Asserting at compile time using numeric expressions:
  AssertAIsNotZero = 1 div A;
  AssertBIsNotOne =  1 div (B - 1);
  AssertBIsNotAbsOne =  1 div (Abs(B) - 1);
  AssertCIsNotAbsOne =  1 div (Abs(C) - 1);

This is all centered around generating a compile time error "[dcc32 Error] E2098 Division by zero", of which multiple can occur in one compile go (after compilation, the cursor focus will be at the first error) and which has been in the language for a very long time [WayBack] E2098: Division by zero.

The conversion of Boolean to Integer is done using Ord, a very powerful compile time intrinsic that evaluates to a constant.

You can use this for other intrinsics as well, for example:

type
  TDigits = 0..9;

const
  DigitsAreInteger = GetTypeKind(TDigits) = tkInteger;
  DigitsAreIntegerIsTrue = 1 div Ord(DigitsAreInteger);
  DigitsAreEnumeration = GetTypeKind(TDigits) = tkEnumeration; // compiles fine
  DigitsAreEnumerationIsTrue = 1 div Ord(DigitsAreEnumeration); // [dcc32 Error] E2098 Division by zero

The above learns that integer subranges are not enumerations, but stay integers.

You can now extend this to check longer boolean expressions, for instance to check if a record size matches certain criteria. For this we create records having zero to four bytes in size (yes, you can have empty record in Delphi, it in fact the only data structure that can be zero bytes in length, though the documentation [WayBack] Structured Types: record types does not state this is in fact possible ), then validate the sizes:

type
  TRecord0 = record
  end;

  TRecord1 = packed record
    FByte0: Byte;
  end;

  TRecord2 = packed record
    FByte0: Byte;
    FByte1: Byte;
  end;

  TRecord3 = packed record
    FByte0: Byte;
    FByte1: Byte;
    FByte2: Byte;
  end;

  TRecord4 = packed record
    FByte0: Byte;
    FByte1: Byte;
    FByte2: Byte;
    FByte3: Byte;
  end;

const
  AssertTRecord0SizeOf0 = 1 div Ord(SizeOf(TRecord0) = 0); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOf1 = 1 div Ord(SizeOf(TRecord1) = 1); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOf2 = 1 div Ord(SizeOf(TRecord2) = 2); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOf3 = 1 div Ord(SizeOf(TRecord3) = 3); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOf4 = 1 div Ord(SizeOf(TRecord4) = 4); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOfMultipleOf4 = 1 div Ord(SizeOf(TRecord0) mod 4 = 0); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord4SizeOfMultipleOf4 = 1 div Ord(SizeOf(TRecord4) mod 4 = 0); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord0SizeOfMultipleOf4AndGreaterThan0 = 1 div Ord((SizeOf(TRecord0) mod 4 = 0) and (SizeOf(TRecord0) > 0)); // When expression is false: [dcc32 Error] E2098 Division by zero
  AssertTRecord4SizeOfMultipleOf4AndGreaterThan0 = 1 div Ord((SizeOf(TRecord4) mod 4 = 0) and (SizeOf(TRecord4) > 0)); // When expression is false: [dcc32 Error] E2098 Division by zero

That’s how far I got in my first experiments using this mechanism. Hopefully it gave you some inspiration too, so I welcome any usages you made with it.

Inline use of intrinsics can lead to no generated code at all

Since Delphi has no macro language, you cannot create your own intrinsic functions that evaluate to const. You could use a pre-processor though, as described in [WayBack] How to write Delphi compile-time functions – Stack Overflow.

The answer by Johan there however mentions clever use of in-line functions that do not generate any code at all (so effectively evaluate to a const). More on that in a future post.

Compile time assertions in other languages

Many languages support a form of [WayBack] Compile-time calculation – Rosetta Code. If such a language can errors out on compiling such a calculation, then you can have compile time assertions.

Compile time assertions are very much used in C and C++, where they are often called static assertions. Often they depend on macros, but C11 (C standard revision 11) has it built-in.

Since I also do quite a bit of .NET: [WayBack] Can C# Provide a static_assert? – Stack Overflow

Some links on how they work in C and C++, and what you can do with them:

–jeroen

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.