C++ Pattern Matching Proposal [pdf]
open-std.org
open-std.org
You almost wonder if they could do something like how `enum` got fixed up with proper scoping as `enum class`. Like, could `switch` retain the legacy functionality with the new stuff triggered when you do `switch from`, `switch if`, or similar. Or there could be new keyword added after the parenthesis, something like:
switch(<thing>) cases {
<opt1> : ...;
<opt2> : ...;
__ : ...;
}
This has a clear connection back to the legacy command in terms of a switch always having either case labels or a cases structure. And zero chance of breaking existing code, given that the `cases` keyword would only be valid in that position and could still be used as an identifier elsewhere (though obviously that would be discouraged).As for efficiency, we have decades of experience of compiling pattern matching, so i would be surprised if it was always just an if-else ladder. The authors of the proposal to add it to Java think that, compared to explicit if-elses, "it is more optimizable too; in this case we are more likely to be able to do the dispatch in O(1) time":
https://openjdk.java.net/jeps/8213076
I imagine this is through approaches which convert the cases to integers, and then do a traditional jump table etc.
If there's a real inspect, people will be able to stop treating switch as a deficient approximation to one, and maybe appreciate it for what it is.
However, this means switch _only_ works with integers, which is rather limited and confuses newcomers. For instance this does not compile:
void f(std::string const &str) {
switch(str) {
case "option_1":
std::cout << "First option\n";
break;
case "option_2":
std::cout << "Second option\n";
break;
default:
std::cout << "None of the above\n";
}
} void f(std::string const &str) {
switch(hash(str)) {
case hash("option_1"):
std::cout << "First option\n";
break;
case hash("option_2"):
std::cout << "Second option\n";
break;
default:
std::cout << "None of the above\n";
}
}
It could be as little as converting the final 8 bytes to a uint64_t above and using that. That just happens to be all of them in this case, but there are a bunch of them. Or using minimal perfect hashing. None of this is the language though[1] https://github.com/dvx/lofi/blob/master/src/renderer/compone...
Here's a poor-man's coroutine (with serializable state!) where switch plays a more interesting role.
void move_robot(state* s) {
switch (s->resume_at) {
case 0:
#define SUSPEND(resume_pt) s->resume_at = resume_pt; return; case resume_pt:;
#define PAUSE(resume_pt) s->pause_time = 20; while (s->pause_time) { SUSPEND(resume_pt) }
for (;;) {
/* three knight moves, then forward */
for (s->i = 0; s->i != 3; s->i++) {
forward(s);
PAUSE(1);
forward(s);
PAUSE(2);
turn_left(s);
forward(s);
PAUSE(3);
}
forward(s);
}
}
}However this document is a bit raw, lots of todos, so I'm not sure how close are we to such flag.
[0] https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
With C++ modules putting an end to the naive #include, translation units could specify the C++ specification they are compliant with, and hence gracefully remove or add new features on a translation-unit level.
I am compiling my C++ codebase with gcc, clang, emscripten to wasm, visual studio. Impossible if all dependencies are not present in a "third-party" folder.
Google's Abseil library is intended to be used this way.
Otherwise, use the C ABI between dependencies.
All major OS have a standard, very stable C++ ABI. Some of them also guarantee a stable ABI for the library components.
Of course you cannot use a library built for, say, Windows on Linux but that's true for all languages.
And then there are other flags - for instance when gcc/libstdc++ broke std::string in order to switch from a recounted string to a non-refcounted with small string optimisation in order to be C+11 compatible ...
Sure, there are system defaults, but things vary and often recompiling everything and statically linking avoids those issues.
Further without buyin from the actual standard there’s no gurantee a breaking change won’t be forced by the standard at some point.
That's only because they changed the standard library. The ABI itself didn't change much if at all, I believe you can still get the latest compiler to link with the far more compatible MSVCRT.DLL that's been there since the first Win32 OS (Win95).
Starting with VS 2015 Microsoft promised to make these changes much less frequent but AFAIK didn't promise to never make changes here. MSVCRT.DLL has kept stability but Microsoft makes no official promises about it. It's considered undocumented.
Source: https://docs.microsoft.com/en-us/cpp/build/reference/decorat...
Undecorated name Decorated name
int a(char){int i=3;return i;}; ?a@@YAHD@Z
void __stdcall b::c(float){}; ?c@b@@AAGXM@Z
OK, Microsoft.That's because it doesn't need to be said. Countless numbers of existing applications would break if it changed, and MS still cares enough about backward compatibility and knows that many huge and important customers depend on them to not do that.
Microsoft promises backward compatibility but not forward compatibility. There is no promise you can link new code with old DLLs. Just that older binaries will still execute unmodified. Very recently with VS2015 they promised "some" forward compatibility and to make fewer changes.
In practice there were things you could do to increase the chances of this working. But to call this "stable" would render the term meaningless.
> The decorated naming conventions have changed in various versions of Visual Studio
Although one might argue that is only a C++ subset.
COM is based on the way Visual C++ lays out the VMT, and only masochists use it from bare bones C.
COM is a C ABI in a sense that its spec defines everything in these terms. Yes, in practice it translates nicely to C++ vtables, but the stability guarantees are defined on the lower level.
On the other hand, the other group values stability and not feature proliferation. They don't want to have a constantly changing foundation, but something stable they can build on. They want a small language which they can more easily master and then use to solve problems with. They think code should not ever need to change again as long as the problem it solves is the same. Their attitude can be summed up as "do what you can with what you have."
The C++ and a lot of newer programming languages (especially web stuff) seem to mainly be composed of people from the former group, which is much larger than the latter, but I think we really need more of the latter group in a lot of the tech industry. My go-to language is still C89 and I don't need anything more, because it gets the job done and it's simple enough to be very portable.
Conflict builds when developers exist in the same ecosystem with different opinions on what this art means to them. It feels like everyone is accomplishing different goals because we really are.
Some people want to continually iterate their code until it matches a perfect vision in their head; It's less (if even at all) about the functionality and more about how we got there. That obviously frustrates people who care about the functionality far more.
Thinking of programming as an art is entirely the wrong direction. Pattern matching, especially exhaustive polymorphic type checking will categorically reduce the amount of logic errors possible with the program. This is logic over art at work.
The main issue for me with c++ is that its overall design has largely been artistic over logical. So tacking on pattern matching makes it an even bigger mess than it already is...
Another person had a similar notion to you and my response to him is more in depth: https://news.ycombinator.com/item?id=21952737
In this case the language we designed is assembly. The parameter for success is: less possibility for bugs. Exhaustive type matching is a definitive and logical improvement because it shrinks the codomain of possible erroneous states, thus less bugs.
My favorite example is the Requests library for Python. It dubs itself "HTTP for Humans" -- I find that quite nice from a human perspective, but it may not be the most logical networking choice in all cases. It's an artistic choice by the developer to make it more human-friendly.
At any rate I think the original discussion is, unfortunately, not the greatest example to make this comment on. Comparing pattern matching to switch-cases is like comparing a high-quality brush to a low-quality one.
Whenever something is "user defined" that means you make it up because you're actually exiting the world of the simple program. These things are akin to business requirements or UX/UI.
A popular word that's often used to figure out these things is the word "design" as opposed to "calculate." When we "calculate" something we are performing a mechanical operation designed to solve a problem with the most "optimal" solution. When we "design" something we are pulling a solution out of our imagination with no way to verify that it is the most "optimal" solution. Note that the word "optimal" is something we make up and define ourselves.
For example: What is the optimal way to get from point A to point B? One definition of "optimal" in mathematics (a small universe similar to programming) is the shortest distance. For this we have a calculation: a line. Another definition of "optimal" in the reality we occupy as humans (a large universe much bigger than math or the simulation in our computer systems) is the shortest time it takes for a human to move from A to B. The solution to this is "designed" we have several options to choose from but to fulfill the requirement of shortest time... we currently tend to choose a plane as it is the fastest vehicle available. However, we have no way of knowing whether the plane we took is the best possible solution humanity has ever come up with. The amount of possibilities here is so large we can't calculate a solution.
Programming within the bounds of "design" requirements and user specified definitions of "optimal" lives in a small universe similar to a mathematical universe of axioms and theorems meaning that we can very well calculate the most optimal programs rather then design a sub optimal one. There is much research on this topic, I'd look into Prolog, category theory, dependent types, formal methods and that kind of thing to learn more. It's very deep and is basically a whole different topic.
So given a portion of the definition of "optimal" that most people can agree on: "less bugs at zero performance cost," pattern matching is a calculated improvement over if statements and switch cases. Thanks to exhaustive matching you must handle every possible instantiation of a type or the program cannot compile. This is a definitive and logical improvement over all other case handling methodologies.
There is no need for an artistic analogy to illustrate a point, the improvement is definitive and logical.
Whenever a human turns to "art" to solve a problem it literally means they don't have the knowledge on how to find the most "optimal" solution. Also very likely they don't even have a clear definition of what they want as "optimal."
For how many generations?
C++ is a good demonstration of both the costs and benefits of adding new features. The costs are obvious: the language and standard library are absolutely full of duplicate features, where the old thing is deprecated in favor of the new thing, but the old thing still has to be supported for compatibility's sake.
But so are the benefits. C++ hasn't always moved so quickly, after all. For 13 years, from its initial standardization in 1998 to its first major revision in 2011, the language was effectively frozen. It was never a small language, but it was stable, not a constantly changing foundation.
It was also absolutely horrid.
This is how you iterate over a std::vector in C++98:
for (std::vector<int>::iterator it = vec.begin();
it != vec.end();
++it) {
int number = *it;
// use `number`
}
One of the features added in C++11 is the range-based for loop, which simplifies that to just: for (int number : vec) {
// use `number`
}
The old style was perfectly working, and still works. The problem it solves has not changed. It's just that it is, and has always been, a terrible solution! The noisiness hurts not only ease of use (obviously), but also robustness and understandability: it tends to obscure the actual logic the programmer intended, making code harder to read.Adding the range-based for loop did burden everyone with additional complexity – but it was clearly worth it. The new style is not just "more 'modern'", it's straight-out better. Sure, code that uses the old style does not need to change. Often it's best to leave it alone. But if so, that doesn't mean the existing code is perfect, just that the benefits of refactoring it don't outweigh the costs (such as the risk of introducing bugs). When writing new code, on the other hand, there's no reason not to use the new style.
Not all features are such a slam dunk. Usually the benefits are not so clear, and often the amount of complexity added is higher. The result is a difficult tradeoff, a competition between different people with different interests, just as you say. People are justified in thinking the benefits are not worth the cost.
But there are benefits, almost always. This pattern matching proposal, for example, adds a ton of syntax and duplicates a feature (the switch statement), and overall it seems more complex than it needs to be. I'm not a fan: I'm not sure whether C++ needs pattern matching at all, and if it does get it then I'd prefer a different design. But if this design were accepted, I'd still enjoy using it in my code! After all, pattern matching itself has decades of history in functional programming languages, and has proven to be an elegant and expressive way to write certain kinds of algorithms and use certain kinds of data structures – things that in today's C++ are rather awkward.
for(int i = 0; i < vec.size(); i++)
// do something with vec[i]
That's even simpler and more in line with what vectors are usually used for, and when it comes time to debug, you don't have to deal with the template-hell that is the standard library iterator-framework. The for : may look simpler at first glance, but quick turns into a nightmare when you're trying to do some deep debugging (and if you haven't had to debug by reading the Asm, I think you haven't done enough C++...) because it obscures a significant amount of complexity which really shouldn't be necessary anyway.You would have to write
for(std::size_t i = 0, N = vec.size(); i < N; i++)
// do something with vec[i]
instead if you still insist in raw loops.But if you look at https://gcc.godbolt.org/z/8JwJbw, you will notice that the range-based loop is actually the one which will produce the smaller code which may translate into more efficiency, and is also more readable.
We haven't even brought const- and reference-correctness into the picture here. That makes the number of potential errors in the it-example even worse. There are also iterators where the size is not known up front, streaming input for example.
The point is, some features in a language can reduce complexity and improve readability (like foreach) while others can increase it (move assignment constructors?). This is only looking at it from a developers point of view though, compiler authors might disagree.
Exactly. Admittedly with enough modern C++ experience and having used pattern matching I find the syntax pretty readable and clear. But even if it weren't I'd probably still be +1 pattern matching.
It was great to have different languages having different paradigms but now you can do everything in everything and code bases I'm working on are a confusing mess.
Languages are effectively immutable. Unless you can reach out and edit every single piece of code that breaks when you remove a feature, you can't remove a feature from a language. You can only create a new, almost identical language, and then spend a decade migrating people. This is what happened to Python2/3.
Very few things have been successfully deprecated in C or C++. Even massive security holes in the standard library.
Thats demonstrably false. Python was a loud transition, but most of the language warts and syntax is still there. Same as the Php and perl transitions and the wacky es6 or java. The list goes on.
> > new, almost identical language,
Emphasis added.
Either it means it's a different language or it's a new language. The ambiguity in interpretation leads to a statement of non-meaningful change (tautology) or a statement of important change. The gracious interpretation is the only thing worth responding to, not quibbling over the possible non-statement.
Nitpick (and also proving your point): there is no language Python2; you mean Python vs Python3.
There are other languages on less-shaky foundations, but people don't use them for various legitimate and illegitimate reasons.
Think of programming languages as a seminar where designers are crafting that Ur-language between themselves, and folks like me in the peanut gallery look on.
C++ wasn't the first multi-paradigm language, and it won't be the last.
AFAIK Lisp is not that low-level, Object Pascal is okay but generics/codegen are 20 years too late for example, PL/I is a pile of every known feature at the time by design but it's hard to tell now.
Honestly I’ll be deeply ashamed as a Python programmer writing cascading ifs when even C++ gets pattern matching. (Yeah, I know all the arguments against it.)
What I meant is the language does not force this on you as The Right Way To Do Things™ and thus it mostly boils down to organizing with peers and agreeing on a consistent set of guidelines (which goes well beyond language features...)
C++ is a huge, complex language. It's probably its greatest downside. Making it even more complex, should only be done with very good reason.
If you doubt this, consider the continued popularity of C, a thoroughly anaemic language by today's standards, with no clear advantages over C++ except for its simplicity, minimalism, and that the language changes very slowly. Well, that and its existing adoption levels. It's not quite the case that every feature C has is also in C++, but it's very close, and I can only name one exception: variable-length arrays (an unpopular addition to C) are not officially supported in C++.
C++'s complexity means that:
* It's very difficult to learn. It's extremely difficult to learn well. It's just about impossible to learn in its entirety. This isn't an exaggeration. (Andrei Alexandrescu might be the closest we have to someone who knows all of C++. He's a world famous C++ expert. Mortals don't stand a chance.)
* Different C++ programmers know (and write in) different subsets of the language. Good C programmers know essentially all of C. (I'll admit I'm weak on C's bitfields, and I couldn't tell you every subtlety of its memory model, but when it comes to C++, there may be areas of the language I've never even heard of.)
* C++ style guides (such as Google's one, or LLVM's one) are long and complex documents, by necessity
* We will never have a fully complete C++ compiler that truly matches the language spec. This undermines the spec; the language you're really using depends on your C++ compiler. To put that another way: portability is harmed because different C++ compilers cannot be relied upon to support the same language features.
* C++ compilers are more prone to arcane bugs, than C compilers
* Different C++ compilers have different arcane bugs, harming portability
* It's far easier to develop tools for C than for C++ (compilers, IDEs, etc)
* There are more C compilers out there than C++ compilers, especially for targets like PIC, or for very obscure platforms, or for particular needs such as safety-critical work. There's even a formally verified C compiler ('CompCert') with near-complete support for C99's features. I doubt there will ever be a formally verified C++ compiler.
* C is easier to mechanically reason about; there are more static-analysis tools for C than for C++
* If C++ were simpler it might have given rise to stable ABIs, the way C has. Instead, even different versions of the same C++ compiler might not be interoperable.
* It's less predictable regarding performance. Template metaprogramming can bloat your binaries for seemingly no reason. In C however, all features of the language map naturally to assembly; the programmer can generally predict roughly what assembly will be generated. This matters to those working with operating systems, graphics, high-performance programming, or where side-channel attacks are a security concern.
C++ is also faster-moving than C, meaning:
* It takes more work to maintain your skills for reading other people's code
* Code can age. Old code looks different from new code, unless it's actively maintained, which means work and risks new bugs. If the language rarely changes, this problem goes away.
* If you're developing a compiler, you'd rather a stable language like C, so that you don't have to make a career out of keeping up with the latest additions to the language. Keeping up with the additions to C++ is more than any one compiler-engineer could hope to do.
There are very few 'conservative' languages like C (there are also Scheme and Forth), but it can be a language's greatest strength. Zig is hoping to be another such language, but we'll have to see if it succeeds.
With all of that said, I tend to favour C++ over C, and I really like pattern-matching. It's something that cannot really be 'faked' with templates or macros. (Another personal favourite feature of mine, named arguments, is similar in that regard.) I think it's rather silly that the major OOP languages have until recently completely ignored this brilliant language feature from the functional programming world, as if it adds nothing over switch/case. Even D, an extremely feature-rich language, still lacks pattern matching.
Go is a simple language, no?
It's higher-level than C, but no historical baggage, so probable comes out to the same complexity to fully understand the language.
I don't think you can do everything in everything. Try working with lazy immutable data structures in Rust, for one.
Or look at Dart - now has "non-nullable types," monadic error handling (sort of), but of course the whole thing is on shaky foundations so what's it worth?
I don’t think this is that hard; you “just” need to construct the data structures as elements of an object-graph data structure, and then retrieve your data through an explicit thunk of the object-graph itself.
In lazy languages the object-graph data structure is implicit, but that doesn’t mean that the Rust version of the call site code needs to be any more verbose. It’s just the definitions of the data structures themselves that would be more unwieldy. (And you could probably build some generic lazy container types and mostly work with those.)
Think: what Objective-C does with autorelease-pool objects.
Why is that an issue?
If you can implement lazy finger trees with iterators I'd love to see it :)
Hiding the fact that a lazy value needs to be mutated to initialise it is a little more work (since a nice API lets the user treat the value as if it doesn’t need to be mutated), but is certainly possible.
It's not proper laziness as defined in Okasaki's thesis/book. One needs more complex things like lazy finger trees.
The language is not.
Maybe think of language design as a search thru an n-dimension problem space.
There's the perennial trilemma of functional (LISP), imperative (APL), and object oriented (Simula), where your new language lands somewhere within that triangular design space.
Then add extra dimensions for type systems. Nominal vs structural vs whatever.
Then add some more for ideas swiped from declarative (SQL, VRML, LINQ), stack-based (Forth), REPLs (Logo), constraints (Prolog), and whatever else people cook up.
Ya, the churn is nutty making. But it's also awesome at the same time.
by each other you mean the ml world right ? :p
Some subset of features inspired in them are used in some languages, but that has nothing to do with functional programming taking over.
Where'd that come from? I thought that was monadic-do from Haskell.
Sure, you can still build object components, but it's informally frowned upon.
I think we have wildy different definitions of FP.
No one with old C++ code is going to switch to these future hipster standards, let alone even C++14.
I think this standard bloat is digging its own grave. It will have the opposite effect. People who want the new shiny stuff will use the new shiny thing (Rust). The old timers will stay on C++11 all the way to 2070 when a man or machine finally rewrites everything in $(new lang).
If there's a good idea, why not use it?
When you have a syntax that you like for whatever reasons (including because you have a lot of legacy code written in it) it makes sense to extend the syntax where possible to make it more convenient if you can do so within the constraints of performance.
Other than that, Rust does seem to be an improvement and less... stressful to program in.
use core::fmt::Display;
fn show_monomorphic<T: Display>(first: T, second: T) {
println!("{} then {}", first, second);
}
fn show_polymorphic(first: &dyn Display, second: &dyn Display) {
println!("{} then {}", first, second);
}
pub fn main() {
show_monomorphic(17, 23);
show_monomorphic("fnord", "slack");
show_polymorphic(&42, &"quirkafleeg"); // mixed types!
}
There are things you can do with each that you can't do with the other, but they are often both viable choices.To be fair, I'm only considering basic data structures and algorithms where you can get away with something like foo(void *ptr, size_t size). Maybe generics is too broad a term for that.
but not in C because C has no scope
When you write "a + b", you won't end up with kilobytes of machine code just because someone in some header overloaded the + operator.
Ditto for template-style generics and overloaded functions. If you call a function and it doesn't exist, it won't get auto-generated for you on the fly. You get an error. You want a function, you define that function. That's not the flaw of the language. That's its strength.
Ditto for switch-case constructs.
Ditto for closures.
All of these constructs, should they be added to the language, will result in a compiler generating heaps of code from a simple code snippet. But this will no longer be C, because in C what you see is what you get.
This gets said a lot, but unless you're writing C code for a microcontroller (or a PDP11), that isn't even close to being true.
> When you write "a + b", you won't end up with kilobytes of machine code just because someone in some header overloaded the + operator.
You can't overload "+" for integers or floats. If you add two numbers together, you get what you expect, always. If they're not numbers, and somehow you expected `a + b` to work without an overloaded operator, then I don't know what to say.
> You want a function, you define that function.
Then you define it again for another type, then again, then again...
Then you write a macro and now your colleagues hate you.
> All of these constructs, should they be added to the language, will result in a compiler generating heaps of code from a simple code snippet
Not necessarily.
You can't overload the + operator in C. If you see a+b in code you know it is adding two numbers (or a compile error). In C++ it could be doing anything. In C you may question the size or type (fp vs int) of the variables, but not the operator. + does addition. Always.
No, this actually doesn't get said a lot, however the simplicity of C compilation semantics is one of its biggest strengths.
> ... but unless you're writing C code for a microcontroller (or a PDP11), that isn't even close to being true.
Do humor us with an example of a C code that compiles into something that is "not even close" to what you'd reasonably expect.
That said, I think your entire criteria for determining whether a language or feature is good boils down to its intuitiveness to experienced C programmers, which seems like a particularly poor, subjective criteria and it probably doesn't actually even hold for C considering all of the security issues and undefined behaviors that are introduced by and continue to surprise experienced C programmers.
For my money, closures, pattern matching, and templates (not any implementation in particular) are easy enough to reason about and much better than the corresponding bugs they address, but to each his own.
I see this tired argument over and over. It is especially thrown out when some esoteric language syntax is criticized.
In this case it seems to miss the authors point entirely. My understanding of his argument is that the C language maps reasonably well to machine code. Adding new features to the language could hamper that intuitive mapping between the language syntax and the actual machine code generated.
A valid criticism (which others have made) is that the actual mapping between C code and machine code on modern machines is far less intuitive than one might expect. Another valid response would be a demonstration that "closures, pattern matching, and templates" can be intuitively mapped directly to machine code.
I would be happy if I never see the "you are too blinded by your own language to understand" argument ever again. It borders on ad-hominem and extends no benefit of doubt to the original author.
Then write C++ only using closures, pattern matching and templates outside of the C feature set.
People want these features and the end up making them (badly) using Macros. Also, as to "a + b", it's either trivial enough to be inlined immediately by the compiler or obvious enough that it's your fault.
> If you call a function and it doesn't exist, it won't get auto-generated for you on the fly
As opposed to you writing the function manually every time and then it guaranteeing your binary gets cluttered?
C is also not portable assembler as some people claim, modern compilers are way to complicated for it to be that simple (unless you're working on microcontrollers)
But even regardless of that you still get a linking error.
ATS :p
When I learned it I wondered why would somebody use it instead of Rust or Idris.
Though I would prefer the keyword 'choose' over 'inspect'.
--
I have noob questions about pattern matching on (polymorphic) types. Please humor:
It's syntactic sugar for std::is_base_of<> (Java's instanceof), right?
This is a consequence of C++ (and Java's) nominal type system, right?
So is pattern matching on types necessary (useful) for structural type systems?
I ask because a friend has been advocating structural typing. He's more of a language theorist whereas I'm more of a language mechanic (I don't even rate as a language engineer). While I'm totally on board with whatever he says, I just hope to better grasp the changes.
for example (OCaml):
let imply = function
| (true,false) -> false
| _ -> trueI've found half-hearted pattern matching solutions like Scala quite disappointing.
e.g. https://github.com/macintux/advent-of-code-2019/blob/master/...
check_each :: Int -> [Int] -> Bool -> Int -> (Bool,Bool,Int)
check_each x (y:_) _ last | y==x && x==last = (True,False,x)
check_each x (y:_) _ last | y>x && x==last = (True,True,x)
check_each x (y:_) found _ | y>x = (True,found,x)
check_each x _ found _ = (False,found,x)
is_valid :: (Bool,Bool,Int) -> [Int] -> Bool
is_valid (False,_,_) _ = False
is_valid (True,True,-1) [_] = False
is_valid (True,True,_) [_] = True
is_valid (True,found,shortDup) (h:t) = is_valid (check_each h t found shortDup) t
which is ugly, certainly, but compiles fine (ghc). The uglyness mainly comes from the data structures, and I don't know enough about the domain to suggest better ones (there might not be better ones)."inspect can be declared [[strict]] for implementation-defined exhaustiveness and usefulness checking."
"having a __: case makes any inspect statement exhaustive."
"any case that comes after a __: case would be useless"
And it's not like we could just abstain from using new parts of the language. For every feature there is going to be a person who will be excited to use it in an open source project which your company depends on, and sooner or later you're going to have to pay for every inch of complexity introduced to the language. But the people in charge seem to think that there is no cost to it.
But it can be done as a library. Granted, it's not the same as 1st class support, but it's pretty good:
At first it seems nice, but is it realistic to add so much new syntax to the language?
-- H. L. Mencken
Declaring regex's like javaScript with be cool.
var myre = /^hello\w*/;
std::regex myre("^hello\\\w*"); auto operator "" _r(char const* str, unsigned long) {
return std::regex{str};
}
To avoid all the backslashes, you can use a raw string literal: int main() {
auto myre = R"(^hello\w*)"_r;
} std::regex myre(R"(^hello\w*)");It almost seems like they forget what it means to compile to machine code.
I can say I have some sort emotional attachment to python, but I can't deny that C++ is just a more capable language, but at least I can admit that I'm just not always competent to always write C++.
Like most people say "just use the parts of C++ you need".
Many others don't even have the same insane "features".
This is standard in C (and allegedly C++ and rust), awkward but somewhat doable in Java, C#, and some of the weaker LISPs, damn near impossible in Haskell, Javascript, etc, and completely impossible in "real" LISPs (with first-class macros and/or fexprs).
It's obviously always possible to "compile" a (interpreter,somewhat-preprocessed-program) tuple for any language (give or take turing-hard/easy-ness) and you can usually inline/specialize/optimise the interpreter a lot for one particular program (Haskell/ghc is a good example of this), but that's not what people mean when they talk about designing a language to compile to machine code.
Just a couple of possible languages, there are many more if we take out the good support constraint.
I don't see good enough alternatives to C++, which have enough support, not a steep learning curve, good backward compatibility with existing ecosystems, not owned by a single company, etc.
> not a steep learning curve
well, it's steeper than most languages, but less so than C++ IMO, especially if you're already familiar with C++ and therefore understand the problems Rust is intended to solve.
Still ,there aren't that many languages that compile to native.
My point is that maybe most of those languages are not suitable, and that nobody really like those languages. Just cross all the arguments I gave about those languages, how they score, etc: C/C++ are the most used language that compiles to native and it gets criticized.
"There are languages people complain about, and languages people nobody uses" Meaning we only complain about language we use.
If having a GC makes a language not native, then C++ isn't a native language as well.
ISO C++11 introduced the GC API, C++/CLI and C++/CX dialects use a GC, Unreal C++ has a GC, regular C++ with Boehm GC is another option, C++ Builder has also a GC.
ISO C++ is basically driven by Microsoft, Google, Apple, IBM, given the amount of employees that go to ISO meetings and submit papers.
While it isn't a single corporation, it isn't a community driven process either.
Who can put money on ISO get the features.