In C++ there are two kinds of things you can use as template arguments. One is the obvious one, types, but less well known is that integers (and some other values) can also be used. Early on in the design of ALFE's type system I decided to simplify things by having template arguments all be type constructors. If you really need to use an integer (or indeed any other value) as a template argument you can wrap it in a type.
However, I recently thought of another feature I'd like ALFE fixed-length arrays to have - the ability to be indexed by a type other than Integer
. For example, we might want to have an array indexed by a Boolean
:
Boolean baz; ... Foo bar = barData[baz]; |
Now we could just convert the Boolean
to an Integer
when indexing the array:
Foo bar = barData[baz ? 1 : 0]; |
But that seems ugly especially if we're indexing barData
in a lot of places. We could turn it into a helper method but then it's less obvious to callers that it's an array access. Also this is rather more unwieldy if our array is indexed by an Enumeration type.
What is the name of the type of an array that takes Boolean
and returns Foo
? By analogy with the function type Foo(Boolean)
we would expect it to be Foo[Boolean]
which is quite nice (though makes the sequence type [Boolean]
even more irregular - maybe that should be renamed as Boolean[]
?)
So if Foo[Boolean]
is a array of Foo
indexed by Boolean
then Foo[2]
should be an array of Foo
indexed by a type called "2
", which must then be an integral type with values 0
and 1
. So perhaps integers are types after all. Integer literal type names can't be used everywhere that normal type names can occur, though - it would lead to too many parsing ambiguities. So we'd need another name for the type "2
", perhaps "LessThan<Class{Int n=2;}>
", "()[2].Indexer
" or "Zero.Successor.Successor
".
Of course, if you want that you'll almost certainly also want ranges. We can kill both those birds (and many others) while still keeping the same meaning for Foo[2]
by creating difference types: "A-B
" is a type whose values are those that are in A
but not in B
. So the type "3-1
" would allow the values 1
and 2
. Unfortunately 3-1
is not the same as 2
(though the two types do have the same cardinality).
Alternatively, the idea that the type "2
" should be a type with a single value (the value 2
) also has some beauty especially when combined with sum types so that you could create a type that can contain some arbitrary subset of integers and have that checked at compile time.