v0.7.3
What's Changed: Highlights
Note: The first two were actually in 0.7.2 but I forgot to mention them in the 0.7.2 release notes.
Allow is
constraints on wildcarded parameters and locals.
The following now works (and the last line would be an error if not commented out):
print: (r: _ is std::regular) = {
std::cout << "satisfies std::regular\n";
}
print: (_) = {
std::cout << "fallback\n";
}
irregular: type = {}
main: () = {
print(42); // prints: satisfies std::regular
print(irregular()); // prints: fallback
ok : _ is std::regular = 42;
//err: _ is std::regular = irregular();
}
Always report file/line/function for bounds/null violations when <source_location>
is available
Bounds/null violations and other contract check failures now always report the violating file and line number and function name, if the Cpp1 compiler supports header <source_location>.
This means that on most Cpp1 compilers this code now gives the specific-line-attribution error by default:
main: () = {
vec: std::vector = (1, 3, 5, 2, 4, 6);
for 1 ..= vec.ssize() do (i) { // oops, off-by-one error
std::cout << vec[i]; // bounds violation caught
}
}
// On MSVC 2022, prints:
// 35246demo.cpp2(4) int __cdecl main(void): Bounds safety
// violation: out of bounds access attempt detected -
// attempted access at index 6, [min,max] range is [0,5]
// On GCC 14, prints:
// 35246demo.cpp2(4) int main(): Bounds safety violation:
// out of bounds access attempt detected - attempted access
// at index 6, [min,max] range is [0,5]
Support latest C++26 headers: <inplace_vector>
Cppfront's -import-std
and -include-std
options to import/include the entire C++ standard library tracks the latest headers added to ISO C++, and <inplace_vector>
was adopted at the June 2024 meeting..
Added integer division-by-zero checks
These work similarly to the null-dereference and bounds checks: They're on by default but you can turn them off with a -no
switch. Note the check happens only if the numerator and denominator are both integers. For example:
divide_42_by: (i) 42/i;
main: () = {
std::cout << divide_42_by(2); // ok
std::cout << divide_42_by(0); // causes div-by-0 in callee (line 1)
}
// On MSVC 2022, prints:
// 21demo.cpp2(1) auto __cdecl divide_42_by<int>(const int &):
// Type safety violation: integer division by zero attempt detected
// On GCC 14, prints:
// 21demo.cpp2(1) auto divide_42_by(const auto:256&) [with
// auto:256 = int]: Type safety violation: integer division by
// zero attempt detected
As with the null-deref and bounds checks, these are integrated with Cpp2 contracts. For this case, a violation is treated as a cpp2::type_safety
violation. The default semantics for cpp2::type_safety
are to terminate, but you can install a violation handler using .set_handler()
to make it do whatever you need (including to integrate into an existing logging/reporting framework you might have).
Made range operators explicit about whether the last value is included
The original design was to support ...
(implicitly half-open, last value is excluded) and ..=
(explicitly closed, last value is included). The former syntax has been changed to ..<
(explicitly half-open, last value is excluded) and the latter remains ..=
(explicitly closed, last value is included). So now both are visually explicit.
Rationale: Originally I made ...
the default because it's the safe default, for common cases like iterator ranges where including the last element is a bounds violation error. It would be creating a terrible pitfall to make the "default" syntax be an out-of-bounds error on every use with iterators.
However, feedback on Reddit and elsewhere quickly pointed out that for numeric ranges ...
not including the last value is also surprising. What to do?
One way out is to not support iterators. However, that would be a needless loss of functionality if there was a better answer.
And there is: A better way out is to simply embrace that there should not be a default... just make it convenient for programmers to be explicit every time about whether the last element is included or not. Supporting ..<
and ..=
as the range operators achieves that goal, I think. This change avoids all user surprise (WYSIWYG) and it avoids overloading the meaning of ...
which is also used for variable-length argument lists / fold-expressions / pack expansions.
I appreciate the feedback, and I think it led to a superior design for a C++-compatible ecosystem that heavily uses iterators today. Thanks to everyone who gave feedback!
Restrict unsafe_narrow
, add unsafe_cast
Restrict unsafe_narrow
to narrowing cases and arithmetic types.
Example:
f: (i: i32, inout s: std::string) = {
// j := i as i16; // error, maybe-lossy narrowing
j := unsafe_narrow<i16>(i); // ok, 'unsafe' is explicit
pv: *void = s&;
// pi := pv as *std::string; // error, unsafe cast
ps := unsafe_cast<*std::string>(pv); // ok, 'unsafe' is explicit
ps* = "plugh";
}
main: () = {
str: std::string = "xyzzy";
f( 42, str );
std::cout << str; // prints: plush
}
For operator++
/operator--
, generate return-old-value overload only if the type is copyable
Added to_string
/from_string
and to_code
/from_code
for @enum
and @flag_enum
types
The first is for humans, and uses unqualified names and ( flag1, flag2 )
lists for flag enums.
The second (by request) is to facilitate reflection + code generation in metafunctions, and uses qualified names and ( my_type::flag1 | my_type::flag2 )
lists for flag enums.
Added type_of(expr)
as a convenience synonym for std::remove_cvref_t<decltype(x)>
There was already a CPP2_TYPEOF
macro for that which is heavily used, and this feature lowers to that macro.
Allow function parameter default arguments
I had been experimenting with not allowing default arguments for function parameters, in part because of the potential for creating order-dependent code; the way to find out whether they're necessary is to not support them and see if that leaves a usability hole.
The result of the experiment is: We want it. There's been persistent feedback that default arguments are often useful, and are actually necessary for a few cases including particularly std::source_location
parameters. As for order independence, there are already ways to opt into creating potentially order-dependent code (such as by deduced return types which depend on function bodies). So I think it's time to enable default arguments, and Cpp2 is still order-independent by default.
This example now works (on Cpp1 compilers that also support <source_location>
:
my_function_name: (
fn: *const char = std::source_location::current().function_name()
)
= {
std::cout << "calling: (fn)$\n";
}
main: (args) = {
my_function_name();
}
// On MSVC 2022, prints:
// calling: int __cdecl main(const int,char **)
// On GCC 14, prints:
// calling: int main(int, char**)
Special thanks to PRs from:
- New contributor @tsoj for the enumeration
from_string
PR!
Full Changelog: v0.7.2...v0.7.3