Basicly the goal is to create a functional language using only the C++ Type System. No literals, No standard library, no constexpr, just the Typesystem. In other words, there are 0 (zero) values in the code, just Types.
The goal would be to be able to solve a few Advent of Code exercises using only this.
I was very much inspired by my former colleague's funxx, but wanted to go even further.
This document is merely an overview of the project, if you want to dive deep in the technology, AI redacted a CONTRIBUTING.md that I proof read.
Before the commit adding this mention (a5e9a45), no code was generated by AI.
Now that the language is pretty well formed and works well enough, I intend to
use AI for repetitive tasks that are pretty trivial but needs modifications in
lots of places, that I just don't feel like doing, or find not interesting but
necessary. For intance, the polymorphism refactor (04c8f12) could have been
done by hand on a few examples, and then let the AI do the rest. This refactor
was really not interesting in the code production part, and took a while, I
stepped away from the project for a while because of it, and I don't want to
abandon because of menial tasks like this one. AI use will always be
transparent and documented in the concerned code or in the commit messages.
You have 2 things for an entry point, a prelude.txx, and a body.txx file.
The prelude is functions you may need before the body of your main function,
it's like the context of your program. The body is the content of the struct Main implementation. It is akin to the body of a main function in other
languages.
You ought to change the paths to files in main.hh to your paths to
prelude.txx and body.txx.
Note: These files can be named anything really, just include them accordingly in
main.hh.
For example, the factorial sample can be compiled with the following command:
42sh$ # -std=c++23 -pedantic: C++ standard to use, pendantic to not use language
42sh$ # extension (you can also enable most warnings)
42sh$ # -D_TXX_SKIP_TESTS: Tests take (at the time I write this) about 1.5s to
42sh$ # run, better skipping them if you don't need them
42sh$ # -D_TXX_MAIN: Enable Main function, those last 2 flags might have their
42sh$ # defaults flipped later on, but whilst I'm still in
42sh$ # development phase, I'd rather have those defaults
42sh$ # -D_TXX_INPUT: the value of the `Input` type in `body.txx`
42sh$ # -Itxx: Include path
42sh$ # -o /dev/null: Executable is pointless
42sh$ g++ -std=c++23 -pedantic -D_TXX_SKIP_TESTS -D_TXX_MAIN -D_TXX_INPUT="bu10" -Itxx main.cc -o /dev/nullThe language currently supports:
- Booleans:
True,False, withAnd,Or,Not,Xor - Numbers:
BigUnsigned: Arbitrary-precision unsigned integers with full arithmeticUnsigned8: 8-bit unsigned integers with full arithmetic
- Lists: Heterogeneous lists with
Map,Filter,Fold,Zip,Take,Drop,Reverse,Concat,Nth,Set, and more - First-class functions:
Apply,Curry,Combine(composition),Flip,Const,Id,On, andTernary(if-then-else)
For a detailed feature roadmap and TODO list, see TODO.md.
Performance was very obviously not the point of this project. That said, even though it was never a priority, things turn out to be surprisingly usable in practice.
The C++ compiler effectively "memoizes" template instantiations: once a
particular Foo<Bar, Baz> has been computed, subsequent uses reuse that result
rather than recomputing it. This means that many recursive patterns, like
Fibonacci_v<bu30>, don't cause exponential blowup despite the naive recursive
definition.
Here are some compile-time benchmarks for the Fibonacci sample:
| Input | Fibonacci Value | Compile Time |
|---|---|---|
bu30 |
832,040 | ~100ms |
bu100 |
3.5 × 10²⁰ | ~500ms |
bu200 |
2.8 × 10⁴¹ | ~3.5s |
bu255 |
2.2 × 10⁵² (53 digits, 177 bits) | ~7s |
The 255th Fibonacci number is computed entirely at compile-time using only the
type system, no runtime values, no constexpr, just template instantiation.
However, if your computation generates a large number of unique instantiations that cannot be shared, you will hit combinatory explosions. The compiler's template cache can't help when every intermediate result is distinct.