Fenner
A linguagem

static_assert e constantes de compilação

Premissas verificadas na compilação: tamanhos, restrições de tipos genéricos, constantes. Falhou, não compila.

O que é e por que existe#

Muita premissa de um programa é verificável antes de ele rodar: "este record tem 16 bytes", "este genérico só aceita tipos pequenos e não gerenciados", "esta tabela tem 10 entradas". static_assert escreve essa premissa no código, e o compilador a confere. Se for falsa, o programa nem compila.

Fenner
static_assert(CLen = 9, 'tamanho do texto');
static_assert((CMax = 4) and (CAbs = 7));          // a mensagem é opcional
static_assert(IsManaged(UString) and IsRecord(TPoint));

Onde vale#

  • Na seção de declarações de program, library e module (depois de var/const/type, que a fecham).
  • Em declarações de rotina e como comando no corpo de uma rotina.
  • No corpo de record e de class.
  • Dentro de genéricos: é reavaliado a cada instanciação, com o T já trocado. É assim que um genérico restringe o argumento:
Fenner
type
  TSmall<T> = record
    static_assert(SizeOf(T) <= 8, 'T grande demais');
    static_assert(not IsManaged(T), 'T não pode ser gerenciado');
    V: T;
  end record;

Constantes ampliadas#

A condição aceita as constantes ampliadas, que valem em qualquer expressão constante, não só no static_assert:

FormaDá
Length('cadeia constante')o tamanho
Abs, Min, Maxinteiros ou reais
Chr(n)cadeia de 1 caractere (n de 0 a 65535)
Low/High de vetor estáticolimite do primeiro índice
SizeOf(T)o tamanho em bytes
Is(A, B), IsManaged, IsString, IsDynArray, IsObject, IsClass, IsRecord, IsManagedRecord, Declaredas funções de tipo do {$if}, como booleano constante
rc-static-assert.fxp
program StaticAssert;
// Demo: `static_assert` verifica uma condição EM TEMPO DE COMPILAÇÃO. Se falhar, o programa nem compila.
// Serve para documentar e garantir premissas: tamanhos, restrições de tipos genéricos, constantes.

type
  TPoint = record
    X, Y: Int32;
  end record;

  TVec = array[3..12] of Int32;

  // Em um genérico, o static_assert é reavaliado a cada instanciação: restringe o argumento T.
  TSmall<T> = record
    static_assert(SizeOf(T) <= 8, 'T grande demais');
    static_assert(not IsManaged(T), 'T não pode ser gerenciado (cadeia, vetor dinâmico...)');
    V: T;
  end record;

const
  // constantes de compilação aceitam várias funções: Length, Max, Abs, Chr, SizeOf, High, Low...
  CText = 'olá mundo';
  CLen = Length(CText);
  CMax = Max(3, 4);
  CAbs = Abs(-7);
  CChr = Chr(65);
  CSize = SizeOf(TPoint) * 2;
  CCount = High(TVec) - Low(TVec) + 1;

// no nível do módulo
static_assert(CLen = 9, 'tamanho do texto');
static_assert((CMax = 4) and (CAbs = 7));              // a mensagem é opcional
static_assert(CChr = 'A');
static_assert(CCount = 10, 'TVec tem 10 elementos');
static_assert(IsManaged(UString) and IsRecord(TPoint));

procedure Run;
begin
  static_assert(SizeOf(Int64) = 8);                   // também vale como comando
  WriteLn('Length=', CLen, ' Max=', CMax, ' Abs=', CAbs, ' Chr=', CChr, ' SizeOf*2=', CSize, ' Count=', CCount);
end procedure;

var
  LSmall: TSmall<Int32>;           // TSmall<UString> ou TSmall<Int128> não compilariam
begin
  LSmall.V := 5;
  Run;
  WriteLn('TSmall<Int32>.V = ', LSmall.V);
end.
Saída
Length=9 Max=4 Abs=7 Chr=A SizeOf*2=16 Count=10
TSmall<Int32>.V = 5

Demo 49. Todas as asserções passam; trocar SizeOf(Int64) = 8 por = 4 ou instanciar TSmall<UString> faria o compilador recusar.

Erros típicos#

  • Condição falsa: FE1484, na posição do static_assert. Com mensagem: static_assert falhou: Int64 tem 8 bytes; sem: static_assert falhou. Numa instanciação genérica, a posição volta ao fonte e a mensagem leva o sufixo [na instanciação de TSmall__Int128].
  • O erro é registrado como um {$message error}: a leitura continua e os demais erros também saem.
  • Condição que não é constante: "a condição não pôde ser avaliada em tempo de compilação". Condição de cadeia é erro; inteiro diferente de zero vale verdadeiro.
  • Limites: nada de função do usuário; Length só de cadeia constante; SizeOf(variável) não é constante. and/or seguem a precedência do avaliador: comparações precisam de parênteses, (A = 1) and (B = 2).
  • static_assert é palavra contextual (início de declaração, comando ou membro, seguida de ().

Veja também#