C++ with CamelCase Style
I’d like to have the standard library roughly in the style of Qt. In addition to being my personal favourite, this could also attract many developers currently using Java, JavaScript/TypeScript, Kotlin, Swift.
C++ with Simplified Syntax
Many of C++’s shortcomings stem from the fact that it inherited from C or that backwards compatibility with existing code must be guaranteed.
Cilia can call into C++ (and vice versa), but is a separate language, so its syntax does not need to be backwards compatible with C++.
Cilia is, in my opinion, a collection of quite obvious ideas (and mostly taken from other programming languages, of course):
Int
, Int32
, Int64
, Float
Int x = 42
var x = 42
const x = 42
String[] words
List<String> paragraphs
ContactInfo[String] contactInfoForID
func 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 { ... }
T^
).Int[3] array
instead of Int array[3]
, Int[] array
instead of vector<Int> array
)¯\_(ツ)_/¯
.lang "C++" { ... }
lang "Cilia" { ... }
lang "C" { ... }
{ }
.*.cil
*.hil
or *.cilia
*.hilia
*.cpp
*.hpp
or *.cxx
*.hxx
or *.h
*.h
is a problem, as the header could be C or C++ code.*.hpp
is recommended for C++ code.*.c
*.h
Roughly in the style of Qt, Java, JavaScript, TypeScript, Kotlin, Swift.
Bool
, Int
, Int32
, UInt
, BigInt
, Float
String
, Array
, Map
, ForwardList
, UnorderedMap
, ValueType
Int i
String word
String[] words
ContactInfo[String] contactInfoForID
Matrix M, R, L
str.findFirstOf(...)
vec.pushBack(...)
Thread::hardwareConcurrency()
Pi
, Euler
(feel free to bend/break this rule, e.g. define a constant const e = Euler
)NullPtr
True
, False
NaN
, Infinity
const Int lastIndex = 100
instead of const Int LastIndex = 100
cilia
cilia::lapack
cilia::geometry
For better readability.
When we are at it, after a quick look at Python, Kotlin, Swift, JavaScript, Julia.
\
at end of line,
- it is no whitespace after this continuation-backslash allowed
- (as in Python).(...)
or [...]
- (also as in Python).x += offset; y += offset
Bool
bool
Boolean
Int
== Int64
Int
== Int32
on 32 bit systems only (i.e. old/small platforms).Size
SSize
PtrDiff
Int
instead.Int8
, Int16
, Int32
, Int64
UInt
, UInt8
, UInt16
, UInt32
, UInt64
Byte
== UInt8
(Alias, i.e. the same type for parameter overloading)Float
Float
== Float32
Float16
, Float32
, Float64
(half, single, double precision floating point)Int i
as variable declaration, very much as in C/C++, plus some simplifications/restrictions.
Int i
Int i = 0
Int x, y
Int x = 99, y = 199
Float* m, n
// m and n are pointers (contrary to C/C++)Image image(width, height, 0.0)
const Float* pointerToConstantFloat
const Float const* constPointerToConstantFloat
const
always binds to the right (contrary to C/C++).Complex<Float>& complexNumber = complexNumberWithOtherName
Float* m, &n
Float*m
Image image { width, height, 0.0 }
var
/ const
:
var i = 3
instead of auto i = 3;
const i = 3
instead of const auto i = 3;
const var
would be a contradiction in terms, as there is no such thing as a “constant variable”.)UInt32:1 sign
UInt32:8 exponent
UInt32:23 significand // AKA mantissa
cilia::be::Int
, Int8
, Int16
, Int32
, Int64
cilia::le::Int
, Int8
, Int16
, Int32
, Int64
class MyVectorOfInt {
public:
Int* numbers = NullPtr
Int size = 0
}
struct
class
with no real benefit.
func multiplyAdd(Int x, y, Float z) -> Float {
return x * y + z
}
func
,
func multiply(
Int x, y
) -> Int
// x and y are Int[](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
}
func(Int, Int -> Int)* pointerToFunctionOfIntAndIntToInt
func(Int)* pointerToFunctionOfInt
func(Int, Int -> Int)& referenceToFunctionOfIntAndIntToInt
// Can’t be zero; is that useful?func(Int)& referenceToFunctionOfInt
a^x
for pow(a, x)
(as in Julia),and
, or
, nand
, nor
, xor
in addition to &&
/&
, ||
/|
, …
aBool
and
anotherBool
-> Bool
anInt
and
anotherInt
-> Int
and
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...10
0..<10
1..=10
0..10
1..=10
0..<10
1..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..0
Range(8, 0)
is always empty (it is counting up, not down!)8..<0:-1
, with staticAssert in 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)operator
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 <<<(Int256 a, Int shiftCount) -> Int256 { ... }
operator >>>(Int256 a, Int shiftCount) -> Int256 { ... }
class Int256 {
operator +=(Int256 other) { ... }
operator -=(Int256 other) { ... }
operator *=(Int256 other) { ... }
operator /=(Int256 other) { ... }
operator %=(Int256 other) { ... }
operator <<=(Int shiftCount) { ... }
operator >>=(Int shiftCount) { ... }
operator <<<=(Int shiftCount) { ... }
operator >>>=(Int shiftCount) { ... }
operator &=(Int256 other) { ... }
operator |=(Int256 other) { ... }
}
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 ~
,&&
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) {
...
}
}
No braces around the condition clause.
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 for (... : ...)
(AKA range-for in C++, for each
in C++/CLI, foreach
in C#)
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>
}
with a while-loop:
{
<Initialization>
while <Condition> {
<Body>
<Increment>
}
}
fallthrough
instead of break
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")
}
Create an alias with using
, for:
using var x = data[0]
using var y = data[1]
T& x = data[0]
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 InArgumentType = const StringView
typedef
To add “member like” types, functions/methods, constants (and maybe static variables) to “third party” classes/types.
In case on conflicts, in-class definitions (inside the class) have priority (and a warning is issued).
Int i
i.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::InArgumentType = const StringView
const Bool Float32::IsFloatingPoint = True
const Bool Float64::IsFloatingPoint = True
Int Float32::numOfInstances = 0
Type+ 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*^
and T*+
instead?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 ContactInfo
ContactInfo^ contactInfoSharedPtr = move(contactInfoUniquePtr)
contactInfoUniquePtr
is a NullPtr
afterwards.ContactInfo* contactInfoPtr = new ContactInfo
delete 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>
:
template<type T> using T::SharedPtrType = SharedPtr<T>
using ObjectiveCObject::SmartPtrType = ObjectiveCRefCountPtr
using DotNetObject::SmartPtrType = DotNetGCPtr
DotNetGCPinnedPtr
, that pins the object (so the garbage collector cannot move it during access).using JavaObject::SmartPtrType = JavaGCPtr
T+
is defined via type traits UniquePtrType
.
T+
is UniquePtr<T>
:
template<type T> using T::UniquePtrType = UniquePtr<T>
The basic new idea is, to define templates (classes and functions) mostly the same as they are used.
<...>
) are given after the class name, so that the definition is similar to the usage (in a variable declaration).
class MyVector<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> {
...
}
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
or BigFloat
):
func sqrt(Real x) -> Real {
// ... a series development ...
// (with number of iterations determined from the size of the mantissa)
}
auto
.<...>
) 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
}
template<type T, Int N> func T[N]::size() -> Int { return N }
template<type T, Int N> func 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 typedef
template<type T> using T::InArgumentType = const T&
template<type T> const Bool T::IsFloatingPoint = False
const Bool Float32::IsFloatingPoint = True
const Bool Float64::IsFloatingPoint = True
template<type T> const Bool Complex<T>::IsFloatingPoint = T::IsFloatingPoint
Int[] dynamicArrayOfIntegers
Int[] array = [0, 1, 2]
array[2] = 0
array[3] = 0 // Runtime error, no compile time bounds check
T[] array
is the short form of cilia::Array<T> array
Array<T>
, not Vector<T>
Vector
could too easily collide with the mathematical vector (as used in linear algebra or geometry).T[]
) are handled with T*
instead.std::array
is called cilia::FixedSizeArray
instead.T[]
is not necessary to mean “array of certain (but unknown) size”.
T[N]
and T*
?Int[3] arrayOfThreeIntegers
Int arrayOfThreeIntegers[3]
Int[3] array = [0, 1, 2]
array[2] = 0
array[3] = 0 // Compilation error, due to compile time bounds check
arrayOfThreeIntegers.size()
-> 3
template<type T, Int N> func 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[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[2] = 0
array[3] = 0 // Undefined behaviour, no bounds check at all
delete[] array
}
unsafe {
Int* array = reinterpretCastTo<Int*>(malloc(3 * sizeof(Int)))
array[2] = 0
array[3] = 0 // Undefined behaviour, no bounds check at all
free(array)
}
Int
“properly”:
Int[3]+ arrayPtr = new Int[3]
*arrayPtr[2] = 0
*arrayPtr[3] = 0 // Compilation error, due to compile time bounds check
unsafe
:
unsafe {
Int[3]* arrayPtr = (new Int[3]).release()
*arrayPtr[2] = 0
*arrayPtr[3] = 0 // Compilation error, due to compile time bounds check
delete[] arrayPtr
}
Int[] dynamicArrayOfInt
Int[3] arrayOfThreeInt
Int[3]* pointerToArrayOfThreeInt
Int[3][]* pointerToDynamicArrayOfArrayOfThreeInt
String*[] dynamicArrayOfPointersToString
var 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<T, 2> array
Int[,,] multidimensionalDynamicArray
T[,,] array
is the short form of cilia::MDArray<T, 3> array
cilia::MDArray<T, N>
Int[3, 2, 200]
Int[3, 2, 200] intArray3D
intArray3D[2, 1, 199] = 1
Int[3][,] dynamic2DArrayOfArrayOfThreeInt
Int[3,4][] dynamicArrayOfThreeByFourArrayOfInt
Int
(i.e. signed) as type for *.size()
aUInt - 1 >= 0
is always true (even if aUInt
is 0
)Int
/SSize
/PtrDiff
(i.e. signed integer) anyway.x >= 0
and x < width
may very well be reduced to a single UInt(x) < width
by the compiler in an optimization step.Size
SSize
PtrDiff
Int
instead. Or UInt
in rare cases.TValue[TKey]
as short form of Map<TKey, TValue>
ContactInfo[String] contactInfoForID
as short formMap<String, ContactInfo> contactInfoForID
Map<Int, ...>
is a HashMap
Map<String, ...>
is a SortedMap
The basic idea is to have the most efficient/appropriate parameter passing mode as the default, and to give more the intent than the technical realization.
Taken from Cpp2 / Herb Sutter (who extended/generalized the out
parameters of C#).
in
, inout
, out
, copy
, move
, or forward
.
in
, explicitly to override with one of the other keywords if desired.for ... in
is passed as either in
, inout
, copy
, or move
out
and forward
are not applicable here).
in
, explicitly to override with one of the other keywords if desired.for
loops these keywords describe how the information (i.e. the variable) gets into the body of the loop (or out of it).in
const X&
or const X
(sometimes const XView
)
const X&
as default:
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”:
for i in [1, 2, 3] { ... }
i
is const Int
for 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 StringView
inout
X&
)inout
is also to be given at the caller: swap(inout a, inout b)
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 Int
for copy str in stringArray { ... }
str
is String
for copy str in ["an", "array", "of", "words"] { ... }
str
is String
(not StringView
X
/XView
-copy-trick)move
X&&
)forward
X&&
), too?InArgumentType
to determine the concrete type to be used for in
-passing.
Int
(i.e. up to 16 bytes) are passed by value.template<type T> using T::InArgumentType = const T&
using Bool::InArgumentType = const Bool
using Int8::InArgumentType = const Int8
using Int16::InArgumentType = const Int16
using Int32::InArgumentType = const Int32
using Int64::InArgumentType = const Int64
...
using UInt64::InArgumentType = const UInt64
using Float32::InArgumentType = const Float32
using Float64::InArgumentType = const Float64
using StringView::InArgumentType = const StringView
using ArrayView::InArgumentType = const ArrayView
...
template<type T> using Complex<T>::InArgumentType = T::InArgumentType
Complex<T>
is passed the same way as T
,using Complex<Float128>::InArgumentType = 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
- StringView
Array
- ArrayView
Vector
- VectorView
String
/StringView
:
using String::InArgumentType = const StringView
in 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)
in
String
(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 String
s.for ... in
loop, I would still apply the same rules just to ensure consistency.String[] stringArray = ["a", "b", "c"]
for str in stringArray { ... }
str
is const StringView
Matrix
- MatrixView
Image
- ImageView
MDArray
- MDArrayView
(AKA MDSpan?)XBasicView
instead, explicitly without stride support,Matrix
- MatrixBasicView
Image
- ImageBasicView
MDArray
- MDArrayBasicView
...View
-classes with a size of up to 16 bytes (such as StringView
, ArrayView
, and VectorView
) will be passed by value:
using String::InArgumentType = const StringView
using Array::InArgumentType = const ArrayView
using Vector::InArgumentType = 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::InArgumentType = const MatrixBasicView&
using Image::InArgumentType = const ImageBasicView&
using MDArray::InArgumentType = const MDArrayBasicView&
CopyArgumentType
T
typically simply is T
using<type T> T::CopyArgumentType = T
View
-types it is the corresponding “full” type:
using StringView::CopyArgumentType = String
using ArrayView::CopyArgumentType = Array
using VectorView::CopyArgumentType = Vector
using MatrixView::CopyArgumentType = Matrix
using MatrixBasicView::CopyArgumentType = Matrix
using ImageView::CopyArgumentType = Image
using ImageBasicView::CopyArgumentType = Image
using MDArrayView::CopyArgumentType = MDArray
using MDArrayBasicView::CopyArgumentType = MDArray
View
.for copy str in ["an", "array", "of", "words"] { ... }
str
is String
(not StringView
StringView
literal. They simply write copy
to get a String
with a copy of the content of the StringView
.True
, False
are Bool,
NullPtr
is the null pointer,
NullPtrType
,Int
.123
is an integer literal of arbitrary precision
Int8 a = 1
// Works because 1
fits into Int8
Int8 b = 127
// Works because 127
fits into Int8
Int8 c = 128
// Error because 128 does not fit into Int8
Int8 d = -128
// Works because -128
fits into Int8
Int8 e = -129
// Error because -129
does not fit into Int8
UInt8 f = 255
// Works because 255
fits into UInt8
UInt8 g = 256
// Error because 256
does not fit into UInt8
UInt8 h = -1
// Error because -1
does not fit into UInt8
Int16 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 Int32
UInt 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)
// Works123
are interpreted as Int
- in case of type inferring, parameter overloading and template matching.
Int64
, Int128
, Int256
, BigInt
, if required due to the size.Int
123u
is UInt
- -123u
is an error.-123
is always Int
(signed)0xffffffff
is UInt
in hexadecimal0b1011
is UInt
in binary0o123
is UInt
in octal
0123
, as that is confusing/unexpected, even if it is C++ standardInt
vs. Bool
Int a = True
Bool
is not an Int
Bool
should not be accidentally interpreted as an Int
Int a = Int(True)
Bool a = 1
Int
is not a Bool
Int
should not be accidentally interpreted as a Bool
Bool a = Bool(1)
1.0
is a floating point literal of arbitrary precision
Float16(3.1415926)
Int
), plus the exponent as as arbitrary precision integer (i.e. array of Int
, most always only a single Int
)1.0
are interpreted as Float
Float64
, Float128
, Float256
, BigFloat
, if required due to the size/precision.1.0f
is always Float32
1.0d
is always Float64
Infinity
/-Infinity
is a floating point literal of arbitrary precision for infinity values
Float
NaN
is a floating point literal of arbitrary precision for NaN (“not a number”) values
Float
"Text"
is a StringView
"Text\0“
orStringZ("Text")
."""
First line
Second line
"""
"""(.* )whatever(.*)"""
$"M[{i},{j}] = {M[i, j]}"
"Text"utf8
(but UTF-8 is the default anyway)"Text"utf16
"Text"utf32
"Text"ascii
"Text"latin1
"Text"sz
is a zero terminated string (as used in C)"..."ascii
and "..."sz
?"Text\0"ascii
instead[1, 2, 3]
is an array (here an Int[3]
),
{1, "Text", 3.0}
is an initialization list
Tuple
String[String]
(AKA Map<String,String>
) is initialized with
{
"Key1": "Value1"
"Key2": "Value2"
"Key3": "Value3"
"Key4": "Value4"
}
// if a < b {
// TODO
// }
/* This
/* (and 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 auto
func
instead of auto
type
instead of typename
await
instead of co_await
yield
instead of co_yield
return
instead of co_return
and
, or
, xor
instead of &&
/&
||
/|
!=
/^
not
in addition to !
Int32
instead of int32_t
or qint32
,
int var
-> Int __variable_var
class func
-> class __class_func
yield()
-> func __function_yield()
Array
, Image
, Vector
, or Matrix
with many elements) takes a noticeable amount of time, so we don’t always want to initialize everything.
noinit
to avoid that warning/error.
Int i // Warning
Int j noinit // No warning
Int j = 1 // No warning
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 (as 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
/Int64
safe::Uint
safe::UInt8
/UInt16
/UInt32
/UInt64
is
, as
, Castingis
(type query)
obj is Int
(i.e. a type)objPtr is T*
instead of dynamic_cast<T*>(objPtr) != NullPtr
obj 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) 3
castToMutable<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::string
Map
instead of map
Dictionary
as alias with deprecation warning, as a hint for C# programmers.ForwardList
instead of forward_list
UnorderedMap
instead of unordered_map
ValueType
instead of value_type
Array
instead of vector
StringStream
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_back
cilia::Vector<T = Float, Int size>
cilia::Vector2<T = Float>
cilia::Vector3<T = Float>
cilia::Vector4<T = Float>
cilia::Matrix<T = Float, Int rows, Int columns>
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<T = Float, Int dimensions>
MDSpan
cilia::Image<T = Float>
cilia::Matrix
, but stored row-major, like:
0 1 2
3 4 5
6 7 8
ArrayView
VectorView
MatrixView
ImageView
MDArrayView
cilia::String
with basic/standard unicode support.
String
or StringView
by:
StringView
.String
or StringView
for graphemeCluster in "abc 🥸👮🏻"
for graphemeCluster in text.asGraphemeClusters()
?UInt32
,
for codePoint in "abc 🥸👮🏻".asCodePoints()
Char
for String
Char
==Char8
==Byte
==UInt8
and String
==UTF8String
Char16
for UTF16String
Char32
for UTF32String
for aChar8 in "abc 🥸👮🏻".asArray()
for codeUnit in "abc 🥸👮🏻"utf8.asArray()
for codeUnit in UTF8String("abc 🥸👮🏻").asArray()
for aChar16 in "abc 🥸👮🏻"
utf16
.asArray()
for aChar16 in UTF16String("abc 🥸👮🏻").asArray()
for aChar32 in "abc 🥸👮🏻"
utf32
.asArray()
for aChar32 in UTF32String("abc 🥸👮🏻").asArray()
string.toUpper()
, string.toLower()
toUpper(String) -> String
, toLower(String) -> String
stringArray.sort()
sort(Container<String>) -> Container<String>
compare(stringA, stringB) -> Int
ByteString
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
==Byte
for aChar in "abc"ascii
for aChar in ASCIIString("abc")
String
==UTF8String
.
Latin1String
, a string containing only Latin-1 (ISO 8859-1) characters.
Latin1String
or Latin1StringView
by Char
==Char8
==Byte
for aChar in "äbc"latin1
for aChar in ASCIIString("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) -> String
stringArray.sort(locale)
sort(Container<String>, locale) -> Container<String>
compare(stringA, stringB, locale) -> Int
Signed + - * / Unsigned
is an error
UInt
-> Int
Int
-> UInt
Int
(i.e. signed) is almost always used anyways.if aUInt < anInt
if aUInt < 0
<= 0
1 * aFloat
is possible
Float32
/64
anInt * aFloat
is possible
Float32
/64
,aFloat32 * anInt8
// OKaFloat32 * anInt16
// OKaFloat32 * anInt32
// Warning
aFloat32 * Float32(anInt32)
// OKaFloat32 * anInt64
// Warning
aFloat32 * Float32(anInt64)
// OKaFloat64 * anInt8
// OKaFloat64 * anInt16
// OKaFloat64 * anInt32
// OKaFloat64 * anInt64
// Warning
aFloat64 * Float64(anInt64)
// OKInt128
, Int256
UInt128
, UInt256
e.g. for SHA256BigInt
– Arbitrary Precision Integer
BFloat16
(Brain Floating Point)Float128
- 1 bit sign, 15 bit exponent, 112 bit significand
- Float256
?DDFloat
, TDFloat
, QDFloat
BigFloat<>
for arbitrary precision float,
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)
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
/Int64
saturating::Uint
saturating::UInt8
/UInt16
/UInt32
/UInt64
Null
parallel
struct
for some variant of C++ strcuts/classesinterface
for pure abstract base classes or similar constructstemplate
#define
#if
#else
#endif