Common programming concepts
main
As always, main
is the entry point of the program. For example:
main
can have either:
-
No parameters:
main: () /*etc.*/
-
One parameter of implicit type named
args
:main: (args) /*etc.*/
-
The type of
args
cannot be explicitly specified. It is alwayscpp2::args_t
, which behaves similarly to aconst std::array<std::string_view>
. -
Using
args
performs zero heap allocations. Everystring_view
is directly bound to the string storage provided by host environment. -
args.argc
andargs.argv
additionally provide access to the raw C/C++main
parameters.
-
// Print out command line arguments, then invoke
// a Qt event loop for a non-UI Qt application
main: (args) -> int
= {
for args do (arg) {
std::cout << arg << "\n";
}
app: QCoreApplication = (args.argc, args.argv);
return app.exec();
}
main
can return:
-
void
, the default return value for functions. Noreturn
statement is allowed in the body. In this case, the compiled Cpp1 code behaves as ifmain
returnedint
. -
int
. If the body has noreturn
statement, the default is toreturn 0;
at the end of the function body. -
Some other type that your Cpp1 compiler(s) supports as a nonstandard extension.
Comments
The usual // line comments
and /* stream comments */
are supported. For example:
// A line comment: After //, the entire
// rest of the line is part of the comment
/*
A stream comment: After /*, everything until the
next * / (without a space between) is part of the
comment. Note that stream comments do not nest.
*/
Lists and commas
All lists use ,
commas between list items, and may be enclosed by
-
(
)
parentheses, for most lists -
[
]
brackets, for calling the subscript operator -
<
>
angle brackets, for template parameter/argument lists
For example:
print: <T,U> (t: T, u: U) = { std::cout << t << u << "\n"; }
main: () = {
array: std::array = ('A', 'B', 'C');
for (0, 1, 2) do (e) {
print( e, array[e] );
}
// Prints:
// 0A
// 1B
// 2C
}
An extra comma at the end of the list, before the closing )
or >
, is always allowed but ignored if present (for details, see Design note: Commas).
For example:
print: <T,U,> (t: T, u: U,) = { std::cout << t << u << "\n"; }
main: () = {
array: std::array = ('A', 'B', 'C',);
for (0, 1, 2,) do (e) {
print( e, array[e,], );
}
// Prints:
// 0A
// 1B
// 2C
}
Contextual keywords
Cpp2 has very few globally reserved keywords; nearly all keywords are contextual, where they have their special meaning when they appear in a particular place in the grammar. For example:
-
new
is used as an ordinary function to do allocation (e.g.,shared.new<widget>(1, 2, 3)
). -
struct
andenum
are used as function names in the metafunctions library. -
type
can be used as an ordinary name (e.g.,std::common_type<T1,T2>::type
). -
Unqualified
type_of(x)
is a synonym for Cpp1std::remove_cvref_t<decltype(x)>
.
In rare cases, usually when consuming code written in other languages, you may need to write a name that is a reserved keyword. The way to do that is to prefix it with __identifer__
, which treats it as an ordinary identifier (without the prefix).
Fundamental data types
Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace cpp2
:
Fixed-width types | Synonym for |
---|---|
i8 |
std::int8_t |
i16 |
std::int16_t |
i32 |
std::int32_t |
i64 |
std::int64_t |
u8 |
std::uint8_t |
u16 |
std::uint16_t |
u32 |
std::uint32_t |
u64 |
std::uint64_t |
Variable-width types (Cpp2-compatible single-word names) |
Synonym for (these multi-word names are not allowed in Cpp2) |
---|---|
ushort |
unsigned short |
uint |
unsigned int |
ulong |
unsigned long |
longlong |
long long |
ulonglong |
unsigned long long |
longdouble |
long double |
For compatibility/interop only, so deliberately ugly names |
Synonym for (these multi-word names are not allowed in Cpp2) |
Notes |
---|---|---|
_schar |
signed char |
Normally, prefer i8 instead |
_uchar |
unsigned char |
Normally, prefer u8 instead |
Type qualifiers
Types can be qualified with const
and *
. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a const
pointer to a non-const
pointer to a const i32
object, write:
// A const pointer to a non-const pointer to a const i32 object
p: const * * const i32;
Literals
Cpp2 supports the same 'c'
haracter, "string"
, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals.
Cpp2 supports using Cpp1 user-defined literals for compatibility, to support seamlessly using existing libraries. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a .
call suffix. For example:
-
You can create a
u8
value by writing eitheru8(123)
or123.u8()
. 1 -
You can write a 'constexpr' function like
nm: (value: i64) -> my_nanometer_type == { /*...*/ }
that takes an integer and returns a value of a strongly typed "nanometer" type, and then create anm
value by writing eithernm(123)
or123.nm()
.
Both 123.nm()
and 123.u8()
are very similar to user-defined literal syntax, and more general.
Operators
Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2.
Unary operators
The operators !
, +
, and -
are prefix, as in Cpp1. For example:
Unary operator | Cpp2 example | Cpp1 equivalent |
---|---|---|
! |
!vec.empty() |
!vec.empty() |
+ |
+100 |
+100 |
- |
-100 |
-100 |
The operators .
, ..
, *
, &
, ~
, ++
, --
, ()
, []
, ..<
, ..=
, and $
are postfix. For example:
// Cpp1 examples, from cppfront's own source code:
// address = &(*tokens)[pos + num];
// is_void = *(*u)->identifier == "void";
// Cpp2 equivalents:
address = tokens*[pos + num]&;
is_void = u**.identifier* == "void";
Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see Design note: Postfix operators.
Note: The function call syntax
f(x)
calls a namespace-scope function only. The function call syntaxx.f()
is a unified function call syntax (aka UFCS) that calls a type-scope function in the type ofx
if available, otherwise calls the same asf(x)
. The function call syntaxx..f()
calls a type-scope function only. For details, see Design note: UFCS.
Unary operator | Cpp2 example | Cpp1 equivalent |
---|---|---|
. |
obj.f() |
obj.f() |
* |
pobj*.f() |
(*pobj).f() or pobj->f() |
& |
obj& |
&obj |
~ |
val~ |
~val |
++ |
iter++ |
++iter |
-- |
iter-- |
--iter |
( ) |
f( 1, 2, 3) |
f( 1, 2, 3) |
[ ] |
vec[123] |
vec[123] |
..< |
v.begin()..<v.end() |
std::ranges::subrange(v.begin(), v.end()) |
..= |
1..=10 |
std::views::iota(1, 11) |
$ |
val$ |
reflection — no Cpp1 equivalent yet |
Note: The
...
pack expansion syntax is also supported.Note: The
(
)
,[
]
,..<
, and..=
operators are treated as postfix unary operators, though they can take additional arguments.Note: Because
++
and--
always have in-place update semantics, we never need to remember "use prefix++
/--
unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling++
/--
. When you write a copyable type that overloadsoperator++
oroperator--
, cppfront generates also the copy-old-value overload of that function to support natural use of the type from Cpp1 code.
Binary operators
Binary operators are the same as in Cpp1. From highest to lowest precedence:
Binary operators grouped by precedence |
---|
* , / , % |
+ , - |
<< , >> |
<=> |
< , > , <= , >= |
== , != |
& |
^ |
| |
&& |
|| |
= and compound assignment |
-
Or
123.cpp2::u8()
if you aren'tusing
the namespace or that specific name. ↩