Generic templates

Compile-time generic declarations, specialization, constraints and structural member contracts.

Generic declarations

Functions and structs declare type parameters after their name. A parameter may be unconstrained when the body requires no capabilities that are unavailable on an arbitrary type.

T Identity<T>(T value)
{
    return move value;
}

struct Pair<TKey, TValue>
{
    TKey key;
    TValue value;
}

Generic declarations are specialized for the concrete type arguments used by a program. Functions receive concrete signatures and bodies, and structs receive concrete fields, members and target layouts. These declarations do not use runtime generic dispatch or type erasure.

Generic composition remains semantic until all arguments are known. For example, Pair<T> in Container<T> is an open constructed type; specializing Container<int> recursively produces and reuses the canonical Pair<int> specialization. The same substitution applies through arrays, pointers and references. Recursive forms such as Node<T>* resolve through an in-progress specialization without creating an infinite chain.

Instance field initializers are rebound for each concrete struct specialization. Static fields have separate storage and initialization per specialization, so changing Data<int>.value does not change Data<float>.value. Struct constants are specialized as well; type-dependent expressions such as sizeof(T) are evaluated against the concrete type and target layout.

Specialization is transitive: if a concrete initializer uses another generic specialization, that specialization receives its own instance and static initialization as well.

A generic type parameter is not guaranteed to be copyable, so copying a value of type T is rejected regardless of its constraints. Use move when transferring ownership, as in Identity above. Concrete generic struct specializations determine their copyability after substituting their type arguments.

Constraints

A where clause lists the guarantees available for one type parameter. Parameters may have independent clauses and a clause may combine several constraints.

void Process<TSource, TResult>(TSource source, TResult result)
    where TSource : BaseEntity, IEntity
    where TResult : Constructible
{
    // The body may use capabilities guaranteed by both clauses.
}
  • a struct constraint is nominal and requires actual inheritance;
  • an interface constraint is nominal and requires actual interface implementation;
  • a structural template constraint is satisfied by an exact matching member shape.

These constraints describe available types, members, constructors and structural capabilities. They do not make the generic parameter itself copyable for value-copy analysis.

Constraints also imply weaker guarantees. A base-struct constraint guarantees interfaces implemented anywhere in that base hierarchy. Structural requirements are proven from the complete capability set known for a parameter: accessible members supplied by base structs, interfaces and structural templates may be combined. Thus T : Runnable, Stateful guarantees a template requiring both Run() and State(), and a nominal T : IRunnable may guarantee a structural template with the same exact public method signature.

Structural implication is intentionally asymmetric. Nominal base or interface constraints may prove a structural capability, but a structurally matching template never proves inheritance from a base struct or implementation of an interface. Nominal target constraints still require the corresponding nominal relationship.

Structural templates

A template declaration is a compile-time-only contract. Concrete structs do not explicitly implement it, and it has no values, object layout or runtime dispatch table.

template VectorLike
{
    VectorLike();
    VectorLike(float x, float y, float z);

    float Length();
    float X { get; }
    float this[int index] { get; }
}

Requirements are declarations only. Method, constructor and accessor bodies are rejected. Constructor requirements are named after the template; matching compares constructor kind and parameter signature rather than the template and concrete type names. Constructor initializers such as : base(...) and : this(...) are implementation details and do not participate in matching. A zero-argument requirement is also satisfied by a struct that supports Xenon's implicit default initialization and declares no explicit constructor.

Exact structural matching

Structural conformance compares canonical signatures exactly: member kind, return type, parameter types, reference and pointer form, readonly behavior and accessibility. A normal implicit conversion does not make two requirement signatures equal. Property and indexer accessors are minimum capabilities: a candidate may provide extra accessors, but it must provide every getter or setter required by the template.

Capability composition uses these same exact rules rather than member names. Compatible getter and setter guarantees may jointly prove a read-write property or indexer; their type, index parameters, static/instance form, readonly behavior and accessibility must agree. Separate structural constructor guarantees may likewise provide separate exact constructor signatures. Constructors inherited from a nominal base are not treated as capabilities of every possible derived type.

Recursive generic shapes are validated after the members of their participating specializations are known. This allows valid forms such as a Node<T> containing a pointer to Required<Node<T>>, while a completed node that lacks a required member is rejected.

Constraint failures retain structural details. Diagnostics distinguish missing members, inaccessible matches and signature mismatches, including the required and discovered signatures where available.

An unconstrained type parameter does not expose arbitrary members or constructors. A generic body may use only operations known for every possible argument, plus methods, properties, indexers and constructors guaranteed by its resolved constraints. Constraint propagation uses the same guaranteed-member lookup. Within structural requirements, each template's self name is normalized to the common constrained type, so Equalable plus Hashable can guarantee HashEqualable even though their self-relative Equals parameter names differ.