
C++ Pattern Matching Proposal [pdf] - je42
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1371r1.pdf
======
mikepurvis
Reading over the examples, it seems like there's a lot of good stuff here,
though I wondered how possible it would be to extend switch with some of this
capability vs adding a new keyword. It looks like this possibility was
considered and is addressed in §7.2— basically the flexibility of case labels
and break statements being able to appear inside control structures leads to
all kinds of chaotic corner cases for switch (Duff's Device and friends) that
are incompatible with the proposed semantics for inspect, with no obvious way
to signal from the source which one you want.

~~~
jupp0r
Seems like `inspect` would turn `switch` into a deprecated practice, which is
preferable imho. This way, it's easier to write clang-tidy rules for
conversion and to raise red flags in PRs.

~~~
edflsafoiewq
switch is an extremely useful control structure, though needed rarely. It's
basically a kind of computed goto, something not found very often outside
C/C++ anymore.

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.

~~~
stabbles
Yeah, it creates a jump table, so basically an O(1) operation in assembly
rather than O(n) comparison worst case for every case.

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";
         }
       }

~~~
beached_whale
The workaround is constexpr hashing

    
    
       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

~~~
rowanG077
Can you guaranty at compile time that any hash you switch on is unique? Else
this is just really dangerous if you don't have perfect hashing for the object
you switch on.

------
0xffff2
Just skimming section 4, this really just looks like _even more_ syntax being
added to the language without enough benefit to justify the additional
complexity. At this point, I think I've made the decision to just stick with
C++11 indefinitely and backport the rare useful feature. I don't want to have
to memorize all of this extra stuff.

~~~
xscott
What we need is a compiler flag that says, "give me the good parts of
C++11/14, but leave out the bad stuff from C++98, and fix some of the
inconsistency quirks from trying to be compatible with C89".

~~~
karlicoss
I think core guidelines [0] are aiming that. Some of it is already supported
by tools like clang-tidy, but as far as I understand the ultimate goal is to
have some sort of flag (e.g. --sane) that would only accept 'reasonable'
subset of c++ and reject legacy stuff.

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](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)

~~~
ATsch
I think the only good way to do this is off of the back of C++ Modules. Having
it as a compiler flag means that all files in a project, including libraries,
must be compliant.

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.

~~~
steveklabnik
This is the idea for the 'epochs' proposal
[https://vittorioromeo.info/index/blog/fixing_cpp_with_epochs...](https://vittorioromeo.info/index/blog/fixing_cpp_with_epochs.html)

------
rb808
Its seems all languages are just copying each other at this point. I know C++
hates to be left out of anything so maybe this is appropriate, but to me its
really destroying the ecosystem.

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.

~~~
vmchale
> all languages are just copying each other at this point. I know C++ hates to
> be left out of anything so maybe this is appropriate, but to me its really
> destroying the ecosystem. It was great to have different languages having
> different paradigms but now you can do everything in everything

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?

~~~
steveklabnik
Here’s what immutable data structures look like in Rust:
[https://docs.rs/im/14.1.0/im/](https://docs.rs/im/14.1.0/im/)

~~~
vmchale
None are lazy, however.

------
greg7mdp
Love this proposal! C++ is getting better in leaps and bounds.

~~~
vunie
It's amazing what credible competition can do. If only C had the same kind of
pressure.

~~~
eps
Don't you even dare. C is near-perfect in the simplicity of its core. You want
bells and whistles, you hook up a library.

~~~
doboyy
Typesafe generics via a better void* would make me super happy. There are
definitely other quality of life improvements that could be added or reworked
that wouldn't affect the simplicity too much.

~~~
sdegutis
I'd say Rust is the improvements to C that I've always been wanting: better
type safety, real generics, first-class closures, and OOP without inheritance,
only using structs to structure data, leaving code execution to just
functions. It's what I hoped Go would become. (Take with grain of salt, I'm
just starting to learn Rust.)

~~~
doboyy
I really don't like the monomorphization approach to generics (I think that's
the concept?), where the function/struct essentially gets duplicated for each
type. It seems to mess with linkage, increase binary sizes, and increase
compile times.

Other than that, Rust does seem to be an improvement and less... stressful to
program in.

~~~
tick_tock_tick
You can always role dynamics dispatch yourself

~~~
twic
Dynamic dispatch is in the language too.

    
    
        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.

------
specialist
I _really like_ the proposal for matching values (integral, strings, tuples).
It's very lispy. Explicit representation (intrinsics?) of a map used as an
expression.

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.

------
toolslive
compare this proposal to the pattern matching you get from Haskell or OCaml
and weep.

for example (OCaml):

    
    
        let imply = function 
           | (true,false) -> false
           |   _          -> true

~~~
macintux
Pattern matching across multiple function heads (ML, Erlang) is incredibly
liberating. I hear Haskell has it but it's not idiomatic, and I think Elm
doesn't support it at all. Frustrating.

I've found half-hearted pattern matching solutions like Scala quite
disappointing.

~~~
nv-vn
OCaml, F#, ReasonML don't support that, I think it's unique to SML in the ML
family.

~~~
macintux
Interesting, thanks. SML is my only direct exposure to the family, and I
recall a conference speaker mentioning that ML (perhaps SML) programmers
weren't fond of the feature.

------
LessDmesg
No amount of new features will ever vindicate C++. The language is so complex
and so flawed on every level that it can only be deprecated and migrated away
from. The only way to save developer time and woes is not to produce any new
lines of C++.

~~~
mantap
Move away from to what? People use C++ because they want templates. There's a
very small number of languages with templates.

------
thurn
anything in here about exhaustiveness checking? In my mind that's half the
value of pattern matching.

~~~
SloopJon
From the Exhaustiveness and Usefulness section:

"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"

~~~
jobstijl
Why wouldn't they make it [[strict]] by default?

~~~
atilaneves
Because it's C++, and the defaults must suck (std::vector::at anyone?)

------
typon
Why call it inspect instead of match?

~~~
Sharlin
Inspect breaks less existing code.

~~~
kevin_thibedeau
They ought to use "_Inspect".

------
quizotic
This seems so much more clear to read, with less typing.

------
gumby
I would wish for it to work with concepts and state. Then you could write a
simple production system in the spirit of Prolog, Erlang, et al. The multi-
argument generics is partway down that path, so why not go all the way.

------
abjKT26nO8
More syntax, more semantics - because that's exactly what C++ needed. At this
point I think they may be competing in the most complex languages
championship. When are the next Olympic Games?

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.

------
oaiey
To my understanding, this capability set is roughly the same as what C# 8.0
support and what Java also targets. Just with a different keyword.

------
amelius
Does the D language have pattern matching?

~~~
atilaneves
No.

But it can be done as a library. Granted, it's not the same as 1st class
support, but it's pretty good:

[https://github.com/pbackus/sumtype](https://github.com/pbackus/sumtype)

------
rightbyte
Does this proposal have any support?

At first it seems nice, but is it realistic to add so much new syntax to the
language?

~~~
de_watcher
Structured bindings need to get rid of the mandatory dummy variables in case
of the unused parts of the structure. So something has to be added.

~~~
gumby
Something has to be added but it could just be an empty comma, which IIRC
parallels initialization: use `auto& [a, , c]` to match the first and third
elements of a tuple.

------
crazypython
How does this work compared to ML's signatures?

------
antiquark
Their example #4.3 won't always produce the same result as the "if" version,
because x==x and y==y could return false due to operator overloading.

~~~
wereHamster
If you overload the == operator such that x==x is false then I call UB.
Reading and understanding such a codebase would be a nightmare.

~~~
fyp
NaN == NaN is always false (at least in other languages I know).

~~~
kibwen
This will be the case in any language that confirms to IEEE 754 (so,
hopefully, all of them (because the only thing more confusing than properly
implementing IEEE 754 is improperly implementing your own bespoke subset)).
This property of NaN is required by the spec (and is, for example, why Rust's
default HashMap refuses to accept floating point numbers as keys).

------
kabdib
"It must be our great task, gentlemen, to keep the monkeys away from the
typewriters."

\-- H. L. Mencken

------
ape4
I know this is different than the regex library but just thought I'd
mention...

Declaring regex's like javaScript with be cool.

    
    
        var myre = /^hello\w*/;
        std::regex myre("^hello\\\w*");

~~~
bstamour
You can add a literal operator:

    
    
        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;
        }

------
jokoon
I don't understand why some people don't like C++.

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".

~~~
dullgiulio
Thankfully C++ is far from the only language that compiles to native.

Many others don't even have the same insane "features".

~~~
jokoon
There are not that many languages with good support that compile to native.

~~~
pjmlp
Java (yes it does, since around 2000), .NET (NGEN since v1.0, Mono AOT,
IL2CPP, .NET Native), D, Eiffel, FreePascal, Delphi, RemPascal, Swift, OCaml,
Haskell, Lisp, Ada, Scheme, Basic, Go, Fortran.

Just a couple of possible languages, there are many more if we take out the
good support constraint.

~~~
jokoon
Garbage collected languages are not really native languages.

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.

~~~
umanwizard
Depending on your definition of "enough support", Rust might be what you want.

> 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.

~~~
jokoon
If you already know C, C++ is easier. You can still pick what you like in C++.

Still ,there aren't that many languages that compile to native.

~~~
pjmlp
Yeah the list is indeed quite small.

[https://en.m.wikipedia.org/wiki/Compiled_language](https://en.m.wikipedia.org/wiki/Compiled_language)

~~~
jokoon
When you cross that list with language usage, support etc, you don't have a
lot left.

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.

