6. Constant Expressions
A constant expression (sometimes called a constexpr) is an expression that can be fully evaluated by the compiler at compile time. This feature is primarily for specifying the size of statically-sized arrays.
In Gazprea, a constexpr is not a keyword, but a property of a const
variable. A const variable is considered a constexpr if and only if its
initializer expression meets a strict set of criteria:
6.1. Rules for Constant Expressions
An expression is a valid constexpr if it is composed exclusively of:
Literals of base types (
boolean,integer,real,character).The operators
+,-,*,/,not,and,or, between two or moreconstexprs.Constructors for aggregate types, provided that the aggregate is const and all members are
constexprs.Index or field access on
constexpraggregate types.Other variables that are themselves valid
constexprs.
An expression is not a constexpr if it contains:
References to
varvariables.Function or procedure calls.
Any I/O operations (
<-).
The compiler must perform this validation recursively. When checking if a
variable is a constexpr, the compiler must trace its entire dependency
chain. If the chain ever depends on a runtime value, the check fails.
The only expressions that must be constexpr are global constants. Other
constexprs arising from constants inside function scope may also be constexprs
but the implementation does not need to enforce or necessarily identify this.
Students should also note that MLIR has a constant propagation pass built in,
so doing constant folding yourself may not be necessary depending on your
implementation.
Examples:
Note: we will annotate the scope explicitly in these examples. Some ‘illegal’ examples here would be legal within a non-global scope.
// ----------------------------
// in global scope
// ----------------------------
// Legal Global Constant Expressions
const A = 10;
const B = A * 2; // Depends on another constexpr
const C = B + 5; // C is 25
// Illegal Global Constant Expressions
var x = 10;
const Y = x + 5; // Not a constexpr: depends on a 'var'
function get_val() returns integer { return 100; }
const Z = get_val(); // Not a constexpr: depends on a function call
6.2. Constant Expressions with Aggregate Types
Arrays and tuples can also be constexprs if they meet specific criteria,
allowing them to be used to define other constants.
Arrays
A
constarray is aconstexprif:Its size is a valid
constexpr.All of its element initializers are valid
constexprs.
A
vector(the dynamically-sized type) can never be aconstexpraggregate, since its size is determined at runtime. An inferred-size array such asinteger[*] X = [1, 2, 3]must be aconstexpr, meaning its initializer is itself aconstexpr:[*]denotes an inferred size, not a dynamic one.// ---------------------------- // in global scope // ---------------------------- const WIDTH = 5; const integer[WIDTH] LOOKUP_TABLE = [10, 20, 30, 40, 50]; // Legal constexpr array const ELEMENT = LOOKUP_TABLE[3]; // Legal: ELEMENT is a constexpr with value 30 integer[ELEMENT] my_array = 0; // Legal: static array of size 30, zero-filled const integer[2] BAD_TABLE = [10, get_val()]; // Illegal: initializer is not a constexpr // also illegal if a procedure since // procedure calls are not allowed // within declarationsA
constexprcan appear anywhere aconstdeclaration is legal, including inside functions, procedures, and control-flow blocks. However, not everyconstvariable is aconstexpr.constmeans only that the variable is immutable within its scope;constexpris the stronger property that the value is fully known at compile time. For example:// ---------------------------------- // in local/function/non-global scope // ---------------------------------- var integer x; x <- std_input; const integer y = x; // Legal: y is immutable, but NOT a constexpr // because its value depends on runtime input. integer[y] arr; // Illegal: an explicit array size must be a // constexpr, and y is not a constexpr. vector<integer> v; // Legal: a vector is the dynamically-sized type; // use it when the size is only known at runtime.The compiler propagates the constexpr property through local scopes normally; there is no restriction on where in a block the declaration appears, as long as its entire dependency chain satisfies the rules above.
Tuples
A
consttuple is aconstexprif all of its fields are initialized with valid constant expressions.// ---------------------------- // in global scope // ---------------------------- const CONFIG = (true, 10 * 2); // Legal constexpr tuple const IS_ENABLED = CONFIG.1; // Legal: IS_ENABLED is a constexpr with value 'true' const VALUE = CONFIG.2; // Legal: VALUE is a constexpr with value 20