Templates
The basic “new idea” is, to define templates (classes and functions) mostly the same as they are used.
Similar as in Java, C#, Swift and Rust.
Class Templates
The template parameters (<...>) are given after the class name, so that the definition is similar to the usage (in a variable declaration).
class MyArray<Number T> {
T+ numbers = NullPtr
Int size = 0
}
- Template specialization
class MyUniquePtr<type T> { ... destructor calls delete ... } class MyUniquePtr<type T[]> { ... destructor calls delete[] ... } - Partial template specialization
class KeyValuePair<type TKey, type TValue> { ... } class KeyValuePair<Int, type TValue> { ... } class KeyValuePair<type TKey, String> { ... } - Having to write
typename(in C++) before dependent names in templates is quite annoying.
In Cilia it would betypeinstead, but maybe it is possible to omit it with- two-pass compilation,
- context-based rules (in
T::Value* x->Valueis a type, invar y = T::Value* x->Valueis a value), and/or - different syntax for types (
T::Valueis a type,T.Valueis a value).
Function Templates
- Automatic function templates
- If the type of (at least) one of the function parameters is a concept, then the function is (in fact) a function template. This is like abbreviated function templates in C++ 20, only omitting the
autokeyword.- Concept
Number:func sq(Number x) -> Number { return x * x }- However, the return type could be a different type than
xis (it just needs to satisfy the conceptNumber) - With
func add(Number a, b) -> Numberevenaandbcould be of a different type (but both need to satisfy the conceptNumber)
- However, the return type could be a different type than
- Concept
Real(real numbers asFloat16/32/64/128/256orBigFloat):func sqrt(Real x) -> Real { // ... a series development ... // (with number of iterations determined from the size of the mantissa) }
- Concept
- If the type of (at least) one of the function parameters is a concept, then the function is (in fact) a function template. This is like abbreviated function templates in C++ 20, only omitting the
- Explicit function templates
- When a common type is required:
func add<Number T>(T x, y) -> T { return x + y } - When no deducing of the template type is possible (e.g. when there are no arguments).
- The template parameters (
<...>) are given after the function name,
as they are “type parameters” of the function. - This way the function definition is more similar to the function call.
func getRandom<type T>() -> T { ... } Int random = getRandom<Int>();
- The template parameters (
- When a common type is required:
- For extension function templates specify the type-specific template parameter(s) first. The function-specific template parameters are given after the function name.
extension<type T, Int N> T[N] { size() -> Int { return N } convertTo<type TOut>() -> TOut[N] { ... } }This is a “member function template” of a “extension template”.
So with
Float[3] arrayOfThreeFloat = [1.0, 2.0, 3.0]
we write
Int[3] arrayOfThreeInt = arrayOfThreeFloat.convertTo<Int>().
Requires
Further restrict the type with requires (as in C++):
func sq<Number T>(T x) -> T
requires (T x) { x * x }
{
return x * x
}
class SlidingAverage<type T, type TSum = T>
requires (T x, TSum sum) {
sum = 0 // requires assignment of 0
sum += x // requires addition of type T to type TSum
sum -= x // requires subtraction of type T from type TSum
sum / 1 // requires to divide sum by 1 (i.e. an Int)
} {
T+ numbers
Int size = 0
Int sizeMax = 0
Int index = 0
TSum sum = 0
public:
SlidingAverage(Int size) {
sizeMax = size
numbers = new T[sizeMax]
}
func append(T value) { ... }
func average() -> TSum { ... }
func reset() { ... }
func reset(Int newSize) { ... }
}
Extension Templates
Template type alias with using (not ):typedef
extension<type T> T {
using InParameterType = const T&
}
Template static constants as type traits:
extension<type T> T { static const Bool IsFloatingPoint = False }
extension Float32 { static const Bool IsFloatingPoint = True }
extension Float64 { static const Bool IsFloatingPoint = True }
extension<type T> Complex<T> { static const Bool IsFloatingPoint = T::IsFloatingPoint }
Same Rules for ADL & PTS as in C++
Cilia follows the same rules for Argument Dependent Lookup (ADL, “Koenig Lookup”) and Partial Template Specialization (PTS) as C++. While they are complex, their problems are “well known and understood”, and matching C++ semantics is important for interop with existing C++ APIs and libraries. Having different rules for Cilia and C++ classes would be even more complex, and other rules might introduce other problems.
Argument Dependent Lookup
ADL lets generic code find overloads in the namespace of the argument types, so helps to customize operator<<, unqualified begin()/end(), swap(), etc.
Problems:
- Unexpected overloads, when functions from unrelated namespaces are found unintentionally.
- Ambiguity errors, when ADL finds multiple viable functions.
- Hidden dependencies, when argument types activate their namespace.
- Fragile maintenance, when a new function in a namespace silently changes overload resolution in unrelated code.
The problems with ADL are reduced a bit, as
- modules limit accidental visibility by exporting only explicitly declared functions,
- and with extension functions, member functions are used by default
container.begin() container.end()instead of free functions
begin(container) end(container)
Partial Template Specialization
PTS is a practical way to customize behavior for families of types (for example traits and container-like wrappers) without rewriting full implementations.
Problems:
- Unexpected specialization selection.
- Cryptic and hard-to-debug template errors, when specializations interact in complex ways.
- ODR and visibility issues.