This is a collection of ideas for a language that is based on C++, but with
Cilia is mainly a new syntax for C++, so it has the same core features:
Furthermore it is a collection of – in my opinion – quite obvious ideas. And mostly taken from other programming languages, of course.
Currently it is more of a wish list, a “thought experiment”. But a transpiler seems to be feasible (like Herb Sutter is doing it with Cpp2). In the long run one could imagine a Cilia parser/frontend, producing an AST for the common backend of an existing C++ compiler (like clang).
Int, Int32, Int64, FloatInt x = 42
var x = 42const x = 42String[] wordsSet<String> namesContactInfo[String] contactInfoForIDfunc multiply(Int a, b) -> Int { return a * b }
func print(ContactInfo a) { ... }func concat(String a, b) -> String { return a + b }for i in 1..10 { ... }
for i in 0..<words.size() { ... }for i in [5, 7, 11, 13] { ... }for word in words { ... }Roughly in the style of Qt and Java (or JavaScript, TypeScript, Kotlin, Swift).
Bool, Int, Int32, UInt, BigInt, FloatString, Array, Map, ForwardList, UnorderedMap, ValueTypeInt iString wordString[] wordsContactInfo[String] contactInfoForIDMatrix M, R, Lstr.findFirstOf(...)arr.pushBack(...)Thread::hardwareConcurrency()Pi, SpeedOfLight, Euler (feel free to bend/break this rule, e.g. define a constant const e = Euler)True, FalseNullPtrNaN, Infinityconst Int lastIndex = 100 instead of const Int LastIndex = 100ciliacilia::gui, cilia::clicilia::lapack, cilia::geometryFor better readability.
When we are at it, after a quick look at Python, Kotlin, Swift, JavaScript, Julia.
\ at end of line,
(...) or [...]
x += offset; y += offsetBool
boolBooleanInt == Int64
Int == Int32 on 32 bit systems only (i.e. old/small platforms),
Int == Int16 on 8 and 16 bit systems (i.e. very old/small microcontrollers with 16 bit addresses, like AVR / Atmel ATmega328 / Arduino Uno, or old home computers with 6502, Z80).Int8, Int16, Int32, Int64UInt, UInt8, UInt16, UInt32, UInt64Int,
SizeSSizePtrDiffLongLongLongByte == std::byte (i.e. not the same type as UInt8 for parameter overloading)Float
Float == Float64, i.e. double precision is “standard”
float is single precision.Float == Float32 on old/small platforms only (i.e. those with hardware support for Float32 but not for Float64),Float16, Float32, Float64 (half, single, double precision floating point)Int i as variable declaration, very much as in C/C++ (and Java, C#).
TypeName variableNameconst always binds to the right (contrary to C/C++).Int iInt i = 0Int x, yInt x = 99, y = 199Int[10] highScoreTable // Array of ten integers (instead of Int highScoreTable[10]Float* m, n // m and n are pointers (contrary to C/C++)Float& m, n // m and n are references (contrary to C/C++)Image image(width, height, 0.0)const Float* pointerToConstantFloatconst Float const* constPointerToConstantFloatFloat const* constPointerToFloatComplex<Float>& complexNumber = complexNumberWithOtherNameFloat* m, &nFloat*mImage image { width, height, 0.0 }Int64 -> Int32Float64 -> Float32Int -> UIntUInt -> Int{ }),func.InitializerList<T> as parameter (i.e. for “list-initialization” and “copy-list-initialization”).var / const:
var i = 3 instead of auto i = 3;const i = 3 instead of const auto i = 3;const var / “constant variable” is a bit of a contradiction in terms.)UInt32:1 sign instead of UInt32 sign : 1class MyArrayOfInt {
Int* numbers = NullPtr
Int size = 0
}
public.structclass with no real benefit.
recordfunc multiplyAdd(Float x, y, Int z) -> Float {
return x * y + Float(z)
}
func,
TypeName parameterName, as with variable declarations.TypeName parameter1, parameter2, as with variable declarations.
func multiply(Int x, y) -> Int // x and y are Int-> ReturnType),
->func print(String line) { ... }, i.e. no -> Void[](Int i) -> Float { i * 3.14 }constexpr and consteval
constexpr multiply(Int x, y) -> Int {
return x * y
}
consteval multiply(Int x, y) -> Int {
return x * y
}
constfunc be the better keyword?func(Int, Int -> Int)* pointerToFunctionOfIntAndIntToIntfunc(Int)* pointerToFunctionOfIntfunc(Int, Int -> Int)& referenceToFunctionOfIntAndIntToInt // Can’t be zerofunc(Int)& referenceToFunctionOfIntNo braces around the condition clause (as in Python, Swift, Go, Ruby).
if a > b {
// ...
}
if a > b {
// ...
} else {
// ...
}
if a > b {
// ...
} else if a > c {
// ...
} else {
// ...
}
if 1 <= x <= 10 { ... }
while a > b {
// ...
}
do {
// ...
} while a > b
for str in ["a", "b", "c"] {
// ...
}
instead of AKA range-for in C++, for (... : ...) in C++/CLI, or for each in C#.foreach
for i in 1..10 { ... }for (Int i = 1; i <= 10; ++i) { ... }for i in Range(1, 10) { ... }.for i in 0..<10 { ... }for (Int i = 0; i < 10; ++i) { ... }for i in RangeExclusiveEnd(0, 10) { ... }.for i in 10..1:-1 { ... }for (Int i = 10; i >= 1; --i) { ... }for i in RangeByStep(10, 1, -1) { ... }.var, but only with the options in (the default), inout, copy, move),for i in 0..<10 { <Body> } is equivalent to:
{
var i = 0
while i < 10 {
<Body>
++i
}
}
for (<Initialization>; <Condition>; <Increment>) {
<Body>
}
can be written as
{
<Initialization>
while <Condition> {
<Body>
<Increment>
}
}
<Condition> is empty, then it needs to be replaced with True,
for (;;) { ... } is translated to while True { ... }.breakbreak is the default, and it is not necessary to explicitly write it,fallthrough if necessary.switch i {
case 1:
print("1")
// implicit break
case 2, 3:
print("Either 2 or 3")
// implicit break
case 4:
// do something
fallthrough
case 5:
// do something more
print("4 or 5")
// implicit break
default:
print("default")
}
switch i fallthrough {
case 1:
case 2:
case 3:
case 4:
case 5:
print("1, 2, 3, 4, or 5")
break
default:
print("default")
}
Int (i.e. signed) as type for *.size()
aUInt - 1 >= 0 is always true (even if aUInt is 0)Int/SSizePtrDiffx >= 0 and x < width may very well be reduced to a single UInt(x) < width by the compiler in an optimization step.SizeSSizePtrDiffInt instead.UInt is used in rare cases (i.e. hardware registers, masks, flags), surely not for sizes.size_t in the C++ STL containers: “They are wrong”, “We are sorry”True, False are Bool,
NullPtr is the null pointer,
NullPtrType,Int.123 is an integer literal of arbitrary precision
123456 are interpreted as Int
Int64, Int128, Int256, BigInt, if required due to the size.127 -> Int832'767 -> Int162'147'483'647 -> Int329'223'372'036'854'775'807 -> Int64/IntInt8 a = 1 // Works because 1 fits into Int8Int8 b = 127 // Works because 127 fits into Int8Int8 c = 128 // Error because 128 does not fit into Int8Int8 d = -128 // Works because -128 fits into Int8Int8 e = -129 // Error because -129 does not fit into Int8UInt8 f = 255 // Works because 255 fits into UInt8UInt8 g = 256 // Error because 256 does not fit into UInt8UInt8 h = -1 // Error because -1 does not fit into UInt8Int16 i = 32767 // WorksInt32 j = 2'147'483'647 // WorksInt64 k = 9'223'372'036'854'775'807 // WorksInt l = a // Works because Int8 fits into Int32UInt m = l // Error because Int does not always fit into UInt
UInt m = UInt(l) // WorksInt n = m // Error because UInt does not always fit into Int
Int n = Int(m) // WorksInt123u is UInt
-123u is an error.123i8, 123i16, 123i32, 123i64,123u8, 123u16, 123u32, 123u64 (as in Rust)-123 is always Int (signed)0x7f -> Int80x7fff -> Int160x7fffffff -> Int320x7fffffffffffffff -> Int64/IntInt mostNegativeInt = Int(0x8000000000000000).0xffffffff is UInt in hexadecimal0b1011 is UInt in binary0o123 is UInt in octal
0o as in Python,0123, as that IMHO is confusing/unexpected, even though it is C++ standard.Int vs. Bool
Int a = TrueBool is not an IntBool should not be accidentally interpreted as an IntInt a = Int(True)Bool a = 1Int is not a BoolInt should not be accidentally interpreted as a BoolBool a = Bool(1)1.0 is a floating point literal
Float64 (AKA Float)Float128Float256BigFloat
Int), plus the exponent as as
arbitrary precision integer (i.e. array of Int, most always only a single Int)1.0 is a Float (AKA Float64), so the precision is the same as in C++, but there 1.0 is called a double while 1.0f is called a (single) float.Float16(3.1415926)0.1 as Float64 has the significand 1001100110011001100110011001100110011001100110011010, so this can not implicitly be converted to Float32 or Float16.0.1f16, 0.1f32, 0.1f64, 0.1f128, 0.1f256 (as in Rust)
0.1h, 0.1s, 0.1d, 0.1q, 0.1o for half, single, double, quadruple, octuple precision.0.1fFloat AKA Float64 would be confusing, as in C++ 0.1f means single float AKA Float32.Float128/Float256/BigFloat precision you may add trailing zeros (0.1000000000000000…).Infinity/-Infinity is a Float literal for infinity values,
NaN is a Float literal for NaN (“not a number”) values,
"Text" is a StringView with UTF-8 encoding.
"Text"sz, "Text\0“ orStringZ("Text")."Text" -> u8"Text"sv"Text"sv as to avoid null termination, and u8"Text" as to have UTF-8 encoding.)"""
First line
Second line
"""
""" is on a separate line)""" is on a separate line)"""(.* )whatever(.*)"""f"M[{i},{j}] = {M[i, j]}"
{}) are used in std::format already.f"..." as in format.$"M[{i},{j}] = {M[i, j]}" like in C#?u"..." and u'...' for UTF-16U"..." and U'...' for UTF-32u8"..."u8'...'a"..." for ASCII and l"..." for Latin-1."..."s for std::string."..."svstd::string_view, as that is the default in Cilia."..."sz for null terminated strings.
"..."sz is Char*."...\0" is a StringView of a zero terminated string.[1, 2, 3] is an array (here an Int[3]),
{1, "Text", 3.0} is an initialization list,
Tuple or Pair.[ 1: "one", 2: "two", 3: "three", 4: "four" ] is a String[Int] (AKA Map<Int, String>).
String[Int] keywords = [
1: "one"
2: "two"
3: "three"
4: "four"
]
ContactInfo[String] contactInfoForID = [
"John Doe": {"John", "Doe", "03465 452634"}
"Jane Doe": {"Jane", "Doe", "03245 687534"}
]
// if a < b {
// TODO
// }
/* This
/* (and this) */
(and still this)
is a comment */
C++ has a “tradition” of complicated names, keywords or reuse of keywords, simply as to avoid compatibility problems with old code, which may have used one of the new keywords as name (of a variable, function, class, or namespace). Cilia can call into C++ (and vice versa), but is a separate language, so its syntax does not need to be backwards compatible.
var instead of autofunc instead of autotype instead of typenameawait instead of co_awaityield instead of co_yieldreturn instead of co_returnand, or in addition to &&/&, ||/|xor in addition to !=/^not in addition to !Int32 instead of int32_t or qint32,
int var -> Int __variable_varclass func -> class __class_funcyield() -> func __function_yield()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.
<...>) 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
}
class MyUniquePtr<type T> {
... destructor calls delete ...
}
class MyUniquePtr<type T[Int N]> {
... destructor calls delete[] ...
}
class KeyValuePair<type TKey, type TValue> {
...
}
class KeyValuePair<Int, type TValue> {
...
}
class KeyValuePair<type TKey, String> {
...
}
type (typename in C++) before dependent names in templates is quite annoying, but seems hard/impossible to change/fix.auto keyword.
Number:
func sq(Number x) -> Number {
return x * x
}
x is (but it needs to satisfy the concept Number)func add(Number a, b) -> Number even a and b could be of a different type (but both need to satisfy the concept Number)Real (real numbers as Float16/32/64/128/256 or BigFloat):
func sqrt(Real x) -> Real {
// ... a series development ...
// (with number of iterations determined from the size of the mantissa)
}
<...>) are given after the function name, so that the function definition is similar to the function call.
func add<Number T>(T x, y) -> T {
return x + y
}
func<type T, Int N> T[N]::size() -> Int { return N }func<type T, Int N> T[N]::convertTo<type TOut>() -> TOut[N] { ... }
func T[N]::convertTo<type T, Int N, type TOut>() { ... }Float[3] arrayOfThreeFloat = [1.0, 2.0, 3.0] we want to writeInt[3] arrayOfThreeInt = arrayOfThreeFloat.convertTo<Int>() (not ...convertTo<Float, 3, Int>()T and N belong to the type of the object arrayOfThreeFloat and are determined already. It would not be possible to change them in the call of convertTo<>(), so it is not desired to specify them here at all.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) { ... }
}
using (not typedefusing<type T> T::InParameterType = const T&const<type T> Bool T::IsFloatingPoint = False
const Bool Float32::IsFloatingPoint = True
const Bool Float64::IsFloatingPoint = True
const<type T> Bool Complex<T>::IsFloatingPoint = T::IsFloatingPoint
Each function parameter in Cilia has a “parameter passing mode” that defines how its argument is passed and used — whether it’s input-only, mutable, output, copied, moved, or forwarded.
The basic idea is to have the most efficient/appropriate parameter passing as the default, and to give more the intent than the technical realization.
Taken from Cpp2 / Herb Sutter (an extension/generalisation of the out parameters of C#).
in-parameter.
in parameter passing is used.in, inout, out, copy, move, or forward.
for ... in is passed as either in, inout, copy, or moveout and forward are not applicable here).
for loops these keywords describe how the information (i.e. the variable) gets into the body of the loop (or out of it).in
print(Int count, String text).const X& (a constant reference) or const X (a constant copy), sometimes const XView (a view type, e.g. a slice).
const X& as default, suitable for most, medium to large types:
add(BigInt a, BigInt b)
add(const BigInt& a, const BigInt& b)BigInt[] bigIntArray = ...for i in bigIntArray { ... }
i is const BigInt&const X for “small types” (like Int, Float, etc.):
for i in [1, 2, 3] { ... }
i is const Intfor str in ["a", "b", "c"] { ... }
str is const StringView (a string-literal like "a" forms a const StringView, therefore ["a", "b", "c"] is a const StringView[3])const XView for types X that have a corresponding view type:
concat(String first, String second)
concat(const StringView first, const StringView second)String[] stringArray = ...for str in stringArray { ... }
str is const StringViewinout
X&)swap(inout Int a, inout Int b).inout is also to be given at the caller: swap(inout a, inout b)Span<T>) allows change even without inout.for inout str in stringArray { ... }
str is String&for inout i in intArray { ... }
i is Int&out
inout, a non-const/mutable reference (X&), but without prior initialization.out is also to be given at the caller:
String errorDetails
if not open("...", out errorDetails) {
cout << errorDetails
}
if not open("...", out String errorDetails) {
cout << errorDetails
}
if open("...", out String errorDetails) {
// ...
} else {
cout << errorDetails
}
copy
X), sometimes the “full class” X of a view class XView.for copy i in [1, 2, 3] { ... }
i is an Intfor copy str in stringArray { ... }
str is a Stringfor copy str in ["an", "array", "of", "words"] { ... }
str is a String (not a StringViewX/XView-copy-trick)move
X&&)forward
X&&), too?InParameterType to determine the concrete type to be used for in-passing.
Int (i.e. up to 16 bytes on 64 bit platforms) are passed by value.using<type T> T::InParameterType = const T&using Bool::InParameterType = const Bool
using Int8::InParameterType = const Int8
using Int16::InParameterType = const Int16
using Int32::InParameterType = const Int32
using Int64::InParameterType = const Int64
...
using UInt64::InParameterType = const UInt64
using Float32::InParameterType = const Float32
using Float64::InParameterType = const Float64
using StringView::InParameterType = const StringView
using ArrayView::InParameterType = const ArrayView
...
using<type T> Complex<T>::InParameterType = T::InParameterType
Complex<T> is passed the same way as T,using Complex<Float128>::InParameterType = const Complex<Float128>&sizeof(Complex<Float128>) is 32 bytes (so pass by reference), despite sizeof(Float128) is 16 bytes (so pass by value would be the default).X that have an XView counterpart where
X can implicitly be converted to XView,XView can (explicitly) be converted to X, andXView has the same “interface” as const X (i.e. contiguous memory access).String - StringViewArray - ArrayViewVector - VectorViewString/StringView:
using String::InParameterType = const StringViewin String in fact a const StringView is used as parameter type.String (AKA in String) parameter would implicitly accept
String (as that can implicitly be converted to StringView)StringView (that somehow is the more versatile variant of const String&),StringView).StringView. They simply write String and still cover all these cases.concat(String first, String second)
concat(in String first, in String second)concat(const StringView first, const StringView second)inString (whether it is a const String& or a const StringView) is not suitable anyway. And all other parameter passing modes (inout, out, copy, move, forward) are based on real Strings.for ... in loop, I would still apply the same rules just for consistency.String[] stringArray = ["a", "b", "c"]for str in stringArray { ... }
str is const StringViewMatrix - MatrixViewImage - ImageViewMDArray - MDArrayView (AKA MDSpan?)XBasicView instead, explicitly without stride support,Matrix - MatrixBasicViewImage - ImageBasicViewMDArray - MDArrayBasicView...View-classes with a size of up to 16 bytes (such as StringView, ArrayView, and VectorView) will be passed by value:
using String::InParameterType = const StringView
using Array::InParameterType = const ArrayView
using Vector::InParameterType = const VectorView
...View-classes with a size of more than 16 bytes (such as MatrixBasicView, ImageBasicView, and MDArrayBasicView) will be passed by reference:
using Matrix::InParameterType = const MatrixBasicView&
using Image::InParameterType = const ImageBasicView&
using MDArray::InParameterType = const MDArrayBasicView&
const& simply is the standard for user defined types.)CopyParameterType
T typically simply is Tusing<type T> T::CopyParameterType = TView-types it is the corresponding “full” type:
using StringView::CopyParameterType = String
using ArrayView::CopyParameterType = Array
using VectorView::CopyParameterType = Vector
using MatrixView::CopyParameterType = Matrix
using MatrixBasicView::CopyParameterType = Matrix
using ImageView::CopyParameterType = Image
using ImageBasicView::CopyParameterType = Image
using MDArrayView::CopyParameterType = MDArray
using MDArrayBasicView::CopyParameterType = MDArray
View.for copy str in ["an", "array", "of", "words"] { ... }
["an", "array", "of", "words"] is an StringView[],str is a String (not a StringViewStringView literal. They simply write copy to get a String with a copy of the content of the StringView.Create an alias with using, for:
using var x = data[0]using Int y = data[1]using T z = data[2]
T& z = data[2] unfortunately memory is created for the reference (the pointer).using func f(String) = g(String) to alias the function g(String).using func f = g to alias all overloads of the function g.using InParameterType = const StringViewtypedefa^x for pow(a, x) (as in Julia),and, or, nand, nor, xor in addition to &&/&, ||/|, …
aBoolandanotherBool -> BoolanIntandanotherInt -> Intand and or IMHO are a bit clearer than &&/& and ||/|, so they are recommended.& and | for bitwise operation,
&= and |= anyway.&& and || for boolean operation,
not in addition to ! (for boolean negation)
not is a bit clearer than ! (especially as many modern languages like Rust and Swift use ! also for error handling).! for negation (in addition to not), as we keep != for “not equal” anyways.<> instead of !=, but that’s really not familiar to C/C++ programmers.)~ for bitwise negation,
~T for the destructor anyway.xor instead of ^^ for the power function.operator==
operator!= (if defined), oroperator<=> (if defined), or==
operator==.operator!=
operator== (if defined), oroperator<=> (if defined), oroperator==... and ..<
1..10 and 0..<10 are ranges
1...100..<101..=100..101..=100..<101..3 – 1, 2, 3
Range(1, 3)0..<3 – 0, 1, 2
RangeExclusiveEnd(0, 3)for loop)
1..6:2 – 1, 3, 5
RangeByStep(1, 6, 2)0..<6:2 – 0, 2, 4
RangeExclusiveEndByStep(0, 6, 2)for loop).8..0:-1 – 8, 7, 6, 5, 4, 3, 2, 1, 0
RangeByStep(8, 0, -1)8..0Range(8, 0) is always empty (it is counting up, not down!)8..<0:-1
RangeExclusiveEndByStep that step > 0:..<) is not compatible with negative increments, because when counting downwards it would be necessary/logical to write ..> and that is not available.”8..1:-1 instead.start >= end with step > 0)...2 – …, 1, 2
RangeTo(2)..<3 – …, 1, 2
RangeToExclusiveEnd(3)0.. – 0, 1, 2, …
RangeFrom(0)..
RangeFull()..2:2 – RangeToByStep(2, 2)..<3:2 – RangeToExclusiveEndByStep(3, 2)0..:2 – RangeFromByStep(0, 2)..:2 – RangeFullByStep(2)>> Shift right (logical shift with unsigned integers, arithmetic shift with signed integers)<< Shift left (here a logical shift left with unsigned integers is the same as an arithmetic shift left with signed integers)>>> Rotate right (circular shift right, only defined for unsigned integers)<<< Rotate left (circular shift left, only defined for unsigned integers)To add “member like” types, functions/methods, constants (and maybe static variables) to “third party” classes/types.
In case of conflicts, in-class definitions (inside the class) have priority (and a warning is issued).
Int ii.toString()func Int::toString() -> String { ... } // as in Kotlinusing) for members:
using var Vector2::x = Vector2::data[0]using var Vector2::y = Vector2::data[1]using func Array::pushBack(String) = Array::push_back(String) to alias the function push_back(String).using func Array::pushBack = Array::push_back to alias all overloads of the function g.using StringView::InParameterType = const StringViewconst Bool Float32::IsFloatingPoint = True
const Bool Float64::IsFloatingPoint = True
Int ContactInfo::numOfInstances = 0Type+ pointer
T+ is short for UniquePtr<T> (i.e. a unique pointer to a single object)T[0]+ is short for UniquePtr<T[0]> (i.e. a unique pointer to a C/C++ array of fixed but unknown size, 0 is just a dummy here)
UniquePtr<T[]> seems possible in C++ (it is an “incomplete type”). But in Cilia T[] is an Array<T>, so we use T[0] instead.makeUnique<T>(...),
ContactInfo+ contactInfoUniquePtr = makeUnique<ContactInfo>().ContactInfo[0]+ contactInfoUniqueArrayPtr = makeUnique<ContactInfo[10]>()ContactInfo+ contactInfoUniqueArrayPtr = makeUnique<ContactInfo[10]>()UniquePtr, as that is a necessary distinction in its type).Type^ pointer
T^ is short for SharedPtr<T>T^ for Rust-style references in Circle (so there may be a conflict in the future).T* pointer is dereferenced with *pointer.T^ pointer is dereferenced also with *pointer (not ^pointer).T*^, T*+T^*, T+*Tx̂, Tx̄, Tẋ (but with * instead of x)makeShared<T>(...),
ContactInfo^ contactInfoSharedPtr = makeShared<ContactInfo>().ContactInfo^ contactInfoSharedArrayPtr = makeShared<ContactInfo[10]>()ContactInfo[0]^ contactInfoUniqueArrayPtr = makeUnique<ContactInfo[10]>()T^/SharedPtr<T> can take over the pointer from rvalue T+/UniquePtr<T> and T[0]+/UniquePtr<T[0]> (as in C/C++):
ContactInfo^ contactInfoSharedPtr = new ContactInfoContactInfo^ contactInfoSharedPtr = move(contactInfoUniquePtr)
contactInfoUniquePtr is a NullPtr afterwards.ContactInfo* contactInfoPtr = new ContactInfodelete contactInfoPtr (with classical/raw pointers you need to free the objects yourself)ContactInfo* contactInfoPtr = new ContactInfo[10]delete[] contactInfoPtr (you need to distinguish single-element- and array-pointers yourself)T^ and T+ for special cases / interoperability with other languages:
T^ is defined via type traits SharedPtrType,
T^ is SharedPtr<T>:
using<type T> T::SharedPtrType = SharedPtr<T>using ObjectiveCObject::SharedPtrType = ObjectiveCRefCountPtrusing DotNetObject::SharedPtrType = DotNetGCPtrDotNetGCPinnedPtr, that pins the object (so the garbage collector cannot move it during access).using JavaObject::SharedPtrType = JavaGCPtrT+ is defined via type traits UniquePtrType.
T+ is UniquePtr<T>:
using<type T> T::UniquePtrType = UniquePtr<T>Int[] dynamicArrayOfIntegers
Int[] array = [0, 1, 2]
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0 // Runtime error, no compile time bounds check
T[] arr is the short form of cilia::Array<T> arr
[T] arr, as in Swift or Rust, has some merits.[3 T] arr for fixed sized arrays would be fine for me (I don’t like [T;3] arr), but I’ll stick with the more traditional T[] arr (like C# and Java).Array<T>, not Vector<T>Vector could too easily collide with the mathematical vector (as used in linear algebra or geometry).T* instead.std::array is called cilia::StaticArray instead.T[] means “array of certain (inferred) size”,
T* and T[N].Int[3] arrayOfThreeIntegersInt arrayOfThreeIntegers[3]Int[3] array = [0, 1, 2]
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0 // Compilation error, due to compile time bounds check
arrayOfThreeIntegers.size() -> 3
func<type T, Int N> T[N]::size() -> Int { return N }T+/UniquePtr<T> for “raw” C/C++ arrays of arbitrary size.Int+ is unsafe.
Int+ array = new Int[3] // Array-to-pointer decay possible
unsafe {
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0 // Undefined behaviour, no bounds check at all
}
Int* for arrays is possible but generally unsafe.
Int+ uniquePtrToArray = new Int[3] // Array-to-pointer decay possible
unsafe {
Int* array = uniquePtrToArray.release()
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0 // Undefined behaviour, no bounds check at all
delete[] array
}
unsafe {
Int* array = reinterpretCastTo<Int*>(malloc(3 * sizeof(Int)))
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0 // Undefined behaviour, no bounds check at all
free(array)
}
Int “properly”:
Int[3]+ arrayPtr = new Int[3]
*arrayPtr[0] = 0
*arrayPtr[1] = 0
*arrayPtr[2] = 0
*arrayPtr[3] = 0 // Compilation error, due to compile time bounds check
unsafe:
unsafe {
Int[3]* arrayPtr = (new Int[3]).release()
*arrayPtr[0] = 0
*arrayPtr[1] = 0
*arrayPtr[2] = 0
*arrayPtr[3] = 0 // Compilation error, due to compile time bounds check
delete[] arrayPtr
}
Int[] dynamicArrayOfIntInt[3] arrayOfThreeIntInt[3]& referenceToArrayOfThreeIntInt[3]* pointerToArrayOfThreeIntInt[3][]& referenceToDynamicArrayOfArrayOfThreeIntString*[] dynamicArrayOfPointersToStringvar subarray = array[1..2]var subarray = array[1..<3]array[start..end] (or one of the exclusive counterparts).
var subarray = array[..2]var subarray = array[..]Int[,] dynamic2DArray
T[,] array is the short form of cilia::MDArray<2, T> arrayInt[,,] multidimensionalDynamicArray
T[,,] array is the short form of cilia::MDArray<3, T> arraycilia::MDArray<N, T>Int[3, 2, 200]
Int[3, 2, 200] intArray3D
intArray3D[2, 1, 199] = 1
cilia::StaticMDArray<Int, 3, 2, 200> intArray3DInt[3][,] dynamic2DArrayOfArrayOfThreeIntInt[3,4][] dynamicArrayOfThreeByFourArrayOfIntTValue[TKey] as short form of Map<TKey, TValue>
ContactInfo[String] contactInfoForID as short formMap<String, ContactInfo> contactInfoForID,Map<Int, ...> is a HashMapMap<String, ...> is a SortedMapInt8 ->
Int8, Int16, Int32, Int64, Int128, Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32, Float64, Float128, Float256, BigFloatUInt8 ->
UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntInt8,Int16, Int32, Int64, Int128, Int256, BigIntFloat16, Float32, Float64, Float128, Float256, BigFloatInt16 ->
Int8,Int16, Int32, Int64, Int128, Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16,Float32, Float64, Float128, Float256, BigFloatUInt16 ->
UInt8,UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntInt8, Int16,Int32, Int64, Int128, Int256, BigIntFloat16,Float32, Float64, Float128, Float256, BigFloatInt32 ->
Int8, Int16,Int32, Int64, Int128, Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32,Float64, Float128, Float256, BigFloatUInt32 ->
UInt8, UInt16,UInt32, UInt64, UInt128, UInt256, BigUIntInt8, Int16, Int32,Int64, Int128, Int256, BigIntFloat16, Float32,Float64, Float128, Float256, BigFloatInt64 ->
Int8, Int16, Int32,Int64, Int128, Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32, Float64,Float128, Float256, BigFloatUInt64 ->
UInt8, UInt16, UInt32,UInt64, UInt128, UInt256, BigUIntInt8, Int16, Int32, Int64,Int128, Int256, BigIntFloat16, Float32, Float64,Float128, Float256, BigFloatInt128 ->
Int8, Int16, Int32, Int64, Int128,Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32, Float64, Float128,Float256, BigFloatUInt128 ->
UInt8, UInt16, UInt32, UInt64,UInt128, UInt256, BigUIntInt8, Int16, Int32, Int64, Int128,Int256, BigIntFloat16, Float32, Float64, Float128,Float256, BigFloatInt256 ->
Int8, Int16, Int32, Int64, Int128,Int256, BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32, Float64, Float128, Float256,BigFloatUInt256 ->
UInt8, UInt16, UInt32, UInt64, UInt128,UInt256, BigUIntInt8, Int16, Int32, Int64, Int128, Int256,BigIntFloat16, Float32, Float64, Float128, Float256,BigFloatBigInt ->
Int8, Int16, Int32, Int64, Int128, Int256,BigIntUInt8, UInt16, UInt32, UInt64, UInt128, UInt256, BigUIntFloat16, Float32, Float64, Float128, Float256,BigFloatBigUInt ->
UInt8, UInt16, UInt32, UInt64, UInt128, UInt256,BigUIntInt8, Int16, Int32, Int64, Int128, Int256,BigIntFloat16, Float32, Float64, Float128, Float256,BigFloatUsing signed Int as size
Array, Image, Vector, or Matrix with many elements) takes a noticeable amount of time, so we don’t want to always initialize everything.
noinit to avoid that warning/error.
Int i // Error
Int j noinit // Ok
Int j = 1 // Ok
noinit when they do not initialize their values, so noinit should be used when calling them consciously.class Array<type T> {
Array(Int size) noinit { ... }
Array(Int size, T value) { ... }
}
Array<Float> anArray(10) // Warning
Array<Float> anArray(10) noinit // No warning
Array<Float> anArray(10, 1.0) // No warning
var arrayPtr = new Array<Float>(10) // Warning
var arrayPtr = new Array<Float>(10) noinit // No warning
var arrayPtr = new Array<Float>(10, 1.0) // No warning
safe as default, unsafe code blocks as escape.
unsafe is not regularly used, normally you just use the already existing, carefully developed and tested abstractions (like Array, Vector, Matrix, …).reinterpretCastTo<T>(...),unsafe,noinit.unsafe code is necessary to implement certain abstractions (like container classes):
operator[Int i] -> T& {
if CHECK_BOUNDS and (i < 0 or i >= size) {
terminate()
}
unsafe {
return data[i]
}
}
unsafe itself.
unsafe is a marker for those parts (subfunctions or code blocks) that are not safe (i.e. dangerous) and need to be checked carefully.unsafe block do not have to be marked with unsafe themselves.unsafe block have to be marked with unsafe themselves.unsafe inner function to the outer function), but limited to the scope of unsafe blocks.cilia::safe::Int
cilia::Int, but with overflow check for all operations,
safe::Int8/Int16/Int32/Int64safe::UInt
safe::UInt8/UInt16/UInt32/UInt64operator instead of func.in by default (i.e. const T& or const T).class Int256 {
operator =(Int256 other) { ... }
}
if a = b { ... } is not accidentally allowed.class Int256 {
operator =(move Int256 other) { ... }
}
operator +(Int256 a, b) -> Int256 { ... }
operator -(Int256 a, b) -> Int256 { ... }
operator *(Int256 a, b) -> Int256 { ... }
operator /(Int256 a, b) -> Int256 { ... }
operator %(Int256 a, b) -> Int256 { ... }
operator <<(Int256 a, Int shiftCount) -> Int256 { ... }
operator >>(Int256 a, Int shiftCount) -> Int256 { ... }
operator <<<(UInt256 a, Int shiftCount) -> UInt256 { ... }
operator >>>(UInt256 a, Int shiftCount) -> UInt256 { ... }
class Int256 {
operator +=(Int256 other) { ... }
operator -=(Int256 other) { ... }
operator *=(Int256 other) { ... }
operator /=(Int256 other) { ... }
operator %=(Int256 other) { ... }
operator <<=(Int shiftCount) { ... }
operator >>=(Int shiftCount) { ... }
operator &=(Int256 other) { ... }
operator |=(Int256 other) { ... }
}
class UInt256 {
operator <<<=(Int shiftCount) { ... }
operator >>>=(Int shiftCount) { ... }
}
operator ^=(Int256 other) { ... }class Int256 {
operator ++() -> Int256& { ... }
operator ++(Int dummy) -> Int256 { ... } // post-increment
operator --() -> Int256& { ... }
operator --(Int dummy) -> Int256 { ... } // post-decrement
}
operator ==(Int256 a, b) -> Bool { ... }
operator !=(Int256 a, b) -> Bool { ... }
operator <(Int256 a, b) -> Bool { ... }
operator >(Int256 a, b) -> Bool { ... }
operator <=(Int256 a, b) -> Bool { ... }
operator >=(Int256 a, b) -> Bool { ... }
operator <=>(Int256 a, b) -> Int { ... }
operator and(Bool a, b) -> Bool { ... }
operator or(Bool a, b) -> Bool { ... }
operator nand(Bool a, b) -> Bool { ... }
operator nor(Bool a, b) -> Bool { ... }
operator xor(Bool a, b) -> Bool { ... }
operator not(Bool a) -> Bool { ... }
operator &&(Bool a, b) -> Bool { return a and b }
operator ||(Bool a, b) -> Bool { return a or b }
operator !(Bool a) -> Bool { return not a }
operator ∧(Bool a, b) -> Bool { return a and b }
operator ∨(Bool a, b) -> Bool { return a or b }
operator ⊼(Bool a, b) -> Bool { return a nand b }
operator ⊽(Bool a, b) -> Bool { return a nor b }
operator ⊻(Bool a, b) -> Bool { return a xor b }
Bool (not for integers),!, not ~&& and ||, not & and |operator and(Int256 a, b) -> Int256 { ... }
operator or(Int256 a, b) -> Int256 { ... }
operator nand(Int256 a, b) -> Int256 { ... }
operator nor(Int256 a, b) -> Int256 { ... }
operator xor(Int256 a, b) -> Int256 { ... }
operator not(Int256 a) -> Int256 { ... }
operator &(Int256 a, b) -> Int256 { return a and b }
operator |(Int256 a, b) -> Int256 { return a or b }
operator ~(Int256 a) -> Int256 { return not a }
operator ∧(Int256 a, b) -> Int256 { return a and b }
operator ∨(Int256 a, b) -> Int256 { return a or b }
operator ⊼(Int256 a, b) -> Int256 { return a nand b }
operator ⊽(Int256 a, b) -> Int256 { return a nor b }
operator ⊻(Int256 a, b) -> Int256 { return a xor b }
Bool),~, not !& and |, not && and ||class MyImage<type T> {
// Array subscript
operator [Int i] -> T& {
return data[i]
}
// 2D array (i.e. image like) subscript
operator [Int x, y] -> T& {
return data[x + y*stride]
}
// Functor call
operator (Int a, Float b, String c) {
...
}
}
|x| for abs(x)?
||x|| for norm(x)?
|| as logical or.≪...≫‹...› , «...»⦅...⦆ , 〚...〛 , ⦃...⦄(...), [...], {...}, ⦅...⦆「...」, 『...』, 〈...〉, 《...》, 【...】, 〖...〗, 〔...〕, 〘...〙, ⦗...⦘≫...≪is, as, Castingis (type query)
obj is Int (i.e. a type)objPtr is T* instead of dynamic_cast<T*>(objPtr) != NullPtrobj is cilia::Array (i.e. a template)obj is cilia::Integer (i.e. a concept)as
obj as T instead of T(obj)objPtr as T* instead of dynamic_cast<T*>(objPtr)Variant v where T is one alternative:v as T instead ofstd::get<T>(v)Any a:a as T instead of std::any_cast<T>(a)Optional<T> o:o as T instead of o.value()Float(3)explicit by default, implicit as option.(Float) 3castToMutable<T>(...) or mutableCastTo<T>(...)
constCastTo<>(...)reinterpretCastTo<T>(...)staticCastTo<T>(...)?func getStringLength(Type obj) -> Int {
if obj is String {
// "obj" is automatically cast to "String" in this branch
return obj.length
}
// "obj" is still a "Type" outside of the type-checked branch
return 0
}
func getStringLength(Type obj) -> Int {
if not obj is String
return 0
// "obj" is automatically cast to "String" in this branch
return obj.length
}
func getStringLength(Type obj) -> Int {
// "obj" is automatically cast to "String" on the right-hand side of "and"
if obj is String and obj.length > 0 {
return obj.length
}
return 0
}
Type obj after if obj is ParentA?
Type(obj).functionOfA()cilia Standard LibraryStandard library in namespace cilia (instead of std to avoid naming conflicts and to allow easy parallel use).
cilia::String instead of std::stringMap instead of map
Dictionary as alias with deprecation warning, as a hint for C# programmers.ForwardList instead of forward_listUnorderedMap instead of unordered_mapValueType instead of value_typeArray instead of vectorStringStream instead of stringstream
TextStream, ByteStream, …class cilia::String : public std::string { ... }using var x = data[0]using var y = data[1]using func pushBack = push_backcilia::Vector<Int size, T = Float>
cilia::Vector2<T = Float>cilia::Vector3<T = Float>cilia::Vector4<T = Float>cilia::Matrix<Int rows, Int columns, T = Float>
cilia::Matrix22<T = Float>cilia::Matrix33<T = Float>cilia::Matrix44<T = Float>cilia::Vector<T = Float>cilia::Matrix<T = Float>
0 3 6
1 4 7
2 5 8
cilia::MDArray<Int dimensions, T = Float>
MDSpancilia::Image<T = Float>cilia::Matrix, but stored row-major, like:
0 1 2
3 4 5
6 7 8
ArrayViewVectorViewMatrixViewImageViewMDArrayViewcilia::String with basic/standard unicode support.
String or StringView by:
StringView.String or StringViewfor graphemeCluster in "abc 🥸👮🏻"
for graphemeCluster in text.asGraphemeClusters()?UInt32,
for codePoint in "abc 🥸👮🏻".asCodePoints()
Char for String
Char==Char8==UInt8 and String==UTF8StringChar16 for UTF16StringChar32 for UTF32Stringfor aChar8 in "abc 🥸👮🏻".asArray()
for aChar8 in u8"abc 🥸👮🏻".asArray()for aChar8 in UTF8String("abc 🥸👮🏻").asArray()for aChar16 in u"abc 🥸👮🏻".asArray()
for aChar16 in UTF16String("abc 🥸👮🏻").asArray()for aChar32 in U"abc 🥸👮🏻".asArray()
for aChar32 in UTF32String("abc 🥸👮🏻").asArray()string.toUpper(), string.toLower()
toUpper(String) -> String, toLower(String) -> StringstringArray.sort()
sort(Container<String>) -> Container<String>compare(stringA, stringB) -> IntByteString to represent the strings with single byte encoding (i.e. the classical strings consisting of one-byte characters),
ASCIIString, Latin1String).ASCIIString, a string containing only ASCII characters.
ASCIIString or ASCIIStringView by Char==Char8
for aChar in a"abc"
for aChar in ASCIIString("abc")
String==UTF8String.
Latin1String, a string containing only Latin-1 (ISO 8859-1) characters.
Latin1String or Latin1StringView by Char==Char8
for aChar in l"äßç"
for aChar in Latin1String("abc")
String==UTF8String.
Char8, Char16, Char32
UInt8, UInt16, UInt32,import icu adds extension methods for cilia::String
for word in text.asWords()for line in text.asLines()for sentence in text.asSentences()string.toUpper(locale), string.toLower(locale)
toUpper(String, locale) -> String, toLower(String, locale) -> StringstringArray.sort(locale)
sort(Container<String>, locale) -> Container<String>compare(stringA, stringB, locale) -> Intlang "C++" { ... }lang "Cilia" { ... }lang "C" { ... }{ }.*.cil, *.hil*.cpp, *.hpp or *.cxx, *.hxx
*.h, but that is a problem, as the header could be C or C++ code.*.hpp is recommended for C++ code.*.c *.hSigned + - * / Unsigned is an error,
UInt -> IntInt -> UIntInt (i.e. signed) is almost always used anyways.if aUInt < anInt,
if Int(aUInt) < anInt.if aUInt < 0,
<= 0if aUInt <= -1 would be simple, as -1 can not implicitly be converted to an UInt. But 0 can, so how to check for that?1 * aFloat is possible
Float32/64anInt * aFloat is possible
Float32/64,aFloat32 * anInt8 // OKaFloat32 * anInt16 // OKaFloat32 * anInt32 // Error
aFloat32 * Float32(anInt32) // OKaFloat32 * anInt64 // Error
aFloat32 * Float32(anInt64) // OKaFloat64 * anInt8 // OKaFloat64 * anInt16 // OKaFloat64 * anInt32 // OKaFloat64 * anInt64 // Error
aFloat64 * Float64(anInt64) // OKcilia::be::Int, Int8, Int16, Int32, Int64cilia::le::Int, Int8, Int16, Int32, Int64Int128, Int256UInt128, UInt256 e.g. for SHA256BigInt – Arbitrary Precision Integer
BFloat16 (Brain Floating Point) with 1 bit sign, 8 bit exponent, 7 bit significand,
Float128 with 1 bit sign, 15 bit exponent, 112 bit significandFloat256 with 1 bit sign, 19 bit exponent, 237 bit significandBigFloat for arbitrary precision float,
HighPrecisionFloat<Int SignificandBits, Int ExponentBits> as template for custom float types with statically fixed precision, like Float1024, Float2048, …
sizeof(Int) (i.e. multiples of 64).DDFloat, TDFloat, QDFloat
Int128, Int256 etc.)
UInt c = add(UInt a, UInt b, inout Bool carryFlag)
c = bits63..0(a + b + carryFlag)carryFlag = bit64(a + b + carryFlag)a.add(UInt b, inout Bool carryFlag)
a = bits63..0(a + b + carryFlag)carryFlag = bit64(a + b + carryFlag)UInt c = multiply(UInt a, UInt b, out UInt cHigh)
c = bits63..0(a * b)cHigh = bit127..64(a * b)a.multiply(UInt b, out UInt aHigh)
a = bits63..0(a * b)aHigh = bit127..64(a * b)UInt d = multiplyAdd(UInt a, UInt b, UInt c, out UInt dHigh)
d = bits63..0(a * b + c)dHigh = bit127..64(a * b + c)a.multiplyAdd(UInt b, UInt c, out UInt aHigh)
a = bits63..0(a * b + c)aHigh = bit127..64(a * b + c)b = shiftLeftAdd(UInt a, Int steps, inout UInt addAndHigh)a.shiftLeftAdd(Int steps, inout UInt addAndHigh)b = shiftOneLeft(UInt a, inout Bool carryFlag)a.shiftOneLeft(inout carryFlag)cilia::saturating::Int
cilia::Int, but with saturation for all operations.
saturating::Int8/Int16/Int32/Int64saturating::UInt
saturating::UInt8/UInt16/UInt32/UInt64parallel for parallel for ...interface for pure abstract base classes or similar constructs?struct for some variant of C++ strcuts/classes?Null instead of NullPtr? (But actually it always is a pointer in our context.)template, as it still is unclear, if the “new”, shorter template expressions are really better.let for const variable declarations?constfunc instead of constexpr or consteval?#define#if#else#endifString? name = ...
translates to
Optional<String> name = ...
Int? len = name?.length()
translates to
Optional<Int> fileExtension = (name ? Optional((*name).length()) : NullOpt;
Int len = name?.length() ?? 0
translates to
Int len = (name ? Optional((*name).length()) : NullOpt).valueOr(0);
Bool? isJpeg = name?.endsWith(".jpeg")
translates to
Optional<Bool> isJpeg = name ? Optional((*name).endsWith(".jpeg")) : NullOpt;
Bool isJpeg = name?.endsWith(".jpeg") ?? false
translates to
Bool isJpeg = (name ? Optional((*name).endsWith(".jpeg")) : NullOpt).valueOr(false);
Optional<T> and T*, T^, T+, T-
ContactInfo* contactInfo = ...
String? name = contactInfo?.name
translates to
Optional<String> name = (contactInfo ? Optional((*contactInfo).name) : NullOpt);
String name = contactInfo?.name ?? ""
translates to
Optional<String> name = (contactInfo ? Optional((*contactInfo).name) : NullOpt).valueOr("");
class OkDialog : Window {
Label("Message to user")
Button("Ok")
}
instead of
class OkDialog : Window {
Label __anonymousLabel1("Message to user")
Button __anonymousButton1("Ok")
}
Label __anonymousLabel1 and Button __anonymousButton1.class OkDialog : Window {
Label("Message to user").horizontalAlignment(Alignment::Center)
Button("Ok").onClick(&OkDialog::onOk)
}
instead of
class OkDialog : Window {
Label __anonymousLabel1("Message to user")
Button __anonymousButton1("Ok")
OkDialog() {
__anonymousLabel1.horizontalAlignment(Alignment::Center)
__anonymousButton1.onClick(&OkDialog::onOk)
}
}
VStack {
Label("Message to user")
Button("Ok")
}
instead of
class __AnonymousVStack1 : VStack {
Label("Message to user")
Button("Ok")
} __anonymousVStack1
VStack {
Label label("Message to user")
Button okButton("Ok")
} vertical
instead of
class __AnonymousVStack1 : VStack {
Label label("Message to user")
Button okButton("Ok")
} vertical
class OkDialog : Window {
Label("Message to user")
Button("Ok")
}
based on
class Window {
Window() {
registerWidgetMembers()
}
late virtual func registerWidgetMembers() {
// Via static reflection:
// Register all members of the derived class, that are derived from type Widget.
}
}
instead of
class OkDialog : Window<OkDialog> {
Label("Message to user")
Button("Ok")
}
based on
class Window<type T> {
Window() {
registerWidgetMembers<T>()
}
func registerWidgetMembers<type T>() {
// Via static reflection:
// Register all members of T, that are derived from type Widget.
}
}