C++ is surprisingly close to being a usable functional language.
The two missing pieces are -
* structural pattern matching
* uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
With the kitchen sink approach of design I’d not be surprised if these get into the language eventually. These ideas have been proposed a few times but haven’t been seriously considered as far as I know.
I'm resolutely opposed to such a thing because, having had to actually wade through C++'s name lookup and overload resolution rules in the past, they're a dense, confusing morass of logic that you can only stand to stare at for a half-hour or so before your brain turns to mush and you need to do something else, and anything that adds to that complexity--especially in "this makes things two things nearly equivalent"--is just a bad idea.
(For an example of C++ overload resolution insanity, consider this:)
// Given these overloads...
void f(std::float32_t);
void f(double);
// Which one does this line call? Assume float/double are standard IEEE-754 types.
void f((float)1);
I'm going to guess f(double) because floats have always been promoted to doubles for function calls since K&R. But I'm not sure by any means. I'd be ready to get more explicit if I needed some specific behaviour.
By mentioning K&R you seem to imply that C also promotes floats to doubles in function calls? But that is not the case, floats are passed as floats, as you'd expect.
You can try it yourself on godbolt all the way back to GCC 3, test(float x) has always emitted movss and test(double x) will result in movsd/movlpd.
Unless you're calling a variable-argument function--floats are promoted to doubles for variable-argument functions:
> The arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter, if present. The integer promotions are performed on each trailing argument, and trailing arguments that have type float are promoted to double. These are called the default argument promotions. No other conversions are performed implicitly
It's a difference of whether the function arguments are declared or not. If you declare a `void foo()`, and then call `foo((float)f)`, the `foo()` function is actually passed a `double` as the first argument rather than a `float`. If you instead change the declaration to `void foo(float)` then it gets passed as a `float`.
The justification will, of course, be that the other options break digraph support on some ancient platform that has no public spec and will never sunset - likely one IBM AS/400 in a basement with a 45 year uptime.
Out of "respect for existing deployments," the syntax must accommodate the relic even though no one has seen it outside of the folklore of one 1993 Usenet post.
Every tool chain will begrudgingly implement it and then years later when someone proposes removing it the counter argument will be, "we can't remove it now someone is using it!" The someone, of course, is a hobbyist on the mailing list whose project depends critically in the feature.
Well, they're still in use. I've encountered 3 in the last 5 years of consulting. Just because your startup doesn't have one doesn't mean they don't exist in the manufacturing world. Are they ubiquitous? No. Are they rare? Depends on the industry.
Perfect! The standards committee will be able to reference this reply the next time they justify eye-bleeding syntax instead of having to go mine Usenet. Thank you.
Isn't structural pattern matching basically the same as creating an anonymous struct whose fields are references, with a default assignment operator? Syntax ideas should spring to mind.
Achieving uniform call syntax is easy, compilers just need to implement a new form of symbol resolution called "Kaiser lookup". It follows 14 easy to understand rules. It first looks up methods, then searches for template definitions in /tmp, then for a matching phase of any Saturn moon at time of compilation, then looks of definitions std::compat, and then in the namespaces of the types of any variables in scope anywhere on the call stack. If none of those work, it tries to interpret the call syntax as a custom float literal, and if even that fails, as a Perl 4 compatible regex. It's really intuitive if you think about it.
> uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
Ive written a lot of c++ in the past but I'm not particularly knowledgeable about fp, so I'm wondering why this is important. Is it syntactic sugar or something more significant?
The second piece (uniform call syntax) looks convenient, though I don’t see a realistic way to integrate it into modern C++. The first (structural pattern matching) is, for me, more of a dividing line between low- and high-level languages. I tend to avoid it in my C++, just as I avoid inheritance, virtual functions, and exceptions… or `<functional>` header contents.
Still, it’s always fun to stumble on corners of the STL I’d never paid attention to, even if I won’t end up using them. Thought it was worth sharing :)
> C++ has a philosophy of not doing anything with the language if it can be done in library.
Then why is the language so ridiculously complicated? I had the most fun working with it back in around 2000, and even then it was quite insane. A lot has been added since then.
There are actually a few functional programming in C++ books out there. The language has changed a lot since C++98, when this would be unthinkable. Alexander Granin maintains a curated list of functional programming C++ resources [1].
I don't find it surprising. My impression is that people like Herb Sutter and Alexander Stepanov actively pushed in that direction in the early days. `<functional>` was, AFAIK, part of the STL before it got incorporated into the C++ standard library.
Another missing piece is a good syntax. C++ has, as you said, most of the capabilities already, but actually using them will quickly turn the code into symbol vomit.
* Some standards such as ISO 6709 (Standard representation of geographic point location by coordinates) describe points as (latitude, longitude).
* Others such as RFC 7946 (GeoJSON) describes points as (longitude, latitude).
Using (hypothetical) std::flip to reify these APIs seems like a loaded footgun - someone is bound to accidentally use it {zero, two} times to convert between orders when it needs to be used once and wreak havoc.
No more of a loaded footgun than trying to do it manually, I don't think.
Geographic points should probably be represented as a labeled structure to prevent confusion and passed into functions as such. Using two separate libraries with mutually incompatible error-prone APIs as described is the real loaded footgun IMO. If you can't find better libraries, write wrappers; if you don't have time to write/maintain wrappers, pray. Anything else is just a bandaid.
Probably no better or worse than the alternative. That aside, the example doesn't understand the standards in question.
The governing standard for geospatial data representation is ISO 19125, which defines (longitude, latitude) order. GeoJSON naturally conforms to ISO 19125 since it is a format for processing data on computers.
ISO 6709 is essentially a print formatting standard and orthogonal to storing geospatial data on computers. That some data file formats happen to be human readable does not make ISO 6709 apply.
If you are processing geospatial data on computers the correct order is always (longitude, latitude).
I love Haskell but when writing C++ I always avoid functional style gibberish. I feel like this style of programming only works in languages properly designed for that.
> Interestingly enough, most of these implementations only flip the first two parameters of whichever function they are passed, though it seems to be because most of them are based on the Haskell prelude, and handling arbitrary arity can be tricky in that language.
Probably because the use case for it with higher arity is hard to imagine. (Indeed, TFA gives only examples with binary operations.)
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
(I leave keyword arguments alone because there's no clearer semantic for "flipping" them.)
The `toolz.functoolz.flip` implementation (being restricted to binary functions) is an even simpler one-liner (https://toolz.readthedocs.io/en/latest/_modules/toolz/functo...), though accompanied by a massive docstring and admittedly simplified through a heavyweight currying decorator (which accomplishes much more than simply getting a function that does the right thing).
Of course. But if I had to care about things on that level, and I was willing to sit through the C++ compilation process (and everything else that goes along with that), I wouldn't be using Python in the first place.
Or just (flip .), which also allows ((flip .) .) etc. for further flips.
In Smullyan's "To Mock a Mockingbird", these combinators are described as "cardinal combinator once/twice/etc. removed", where the cardinal combinator itself defines flip.
I skimmed the post. I have absolutely no idea what std::flip is supposed to do. All the sample code looks awful and undesirable. And that’s coming from someone who writes C++ every day. Yes I read the plot twist at the end, made me lol.
You write C++ every day, and you didn’t understand the is_descendant_of/is_ancestor_of example? Or how you can use it to reverse a relation like std::less?
I don’t understand why I should care about this. It doesn’t appear to solve real problems. The examples are all dumb toys and simply writing a wrapper by hand is perfectly fine and easier to read.
C++ is surprisingly close to being a usable functional language.
The two missing pieces are -
* structural pattern matching
* uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
With the kitchen sink approach of design I’d not be surprised if these get into the language eventually. These ideas have been proposed a few times but haven’t been seriously considered as far as I know.
> * uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
Herb Sutter has proposed this: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p30... (twice, even, there was an older version of the paper several years ago that didn't pass).
I'm resolutely opposed to such a thing because, having had to actually wade through C++'s name lookup and overload resolution rules in the past, they're a dense, confusing morass of logic that you can only stand to stare at for a half-hour or so before your brain turns to mush and you need to do something else, and anything that adds to that complexity--especially in "this makes things two things nearly equivalent"--is just a bad idea.
(For an example of C++ overload resolution insanity, consider this:)
I'm going to guess f(double) because floats have always been promoted to doubles for function calls since K&R. But I'm not sure by any means. I'd be ready to get more explicit if I needed some specific behaviour.
By mentioning K&R you seem to imply that C also promotes floats to doubles in function calls? But that is not the case, floats are passed as floats, as you'd expect.
You can try it yourself on godbolt all the way back to GCC 3, test(float x) has always emitted movss and test(double x) will result in movsd/movlpd.
Unless you're calling a variable-argument function--floats are promoted to doubles for variable-argument functions:
> The arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter, if present. The integer promotions are performed on each trailing argument, and trailing arguments that have type float are promoted to double. These are called the default argument promotions. No other conversions are performed implicitly
It's a difference of whether the function arguments are declared or not. If you declare a `void foo()`, and then call `foo((float)f)`, the `foo()` function is actually passed a `double` as the first argument rather than a `float`. If you instead change the declaration to `void foo(float)` then it gets passed as a `float`.
Ex: https://godbolt.org/z/TKjz3Tqqr
I think there have been proposals to add universal call syntax since before C++98, some of them by Stroustrup himself.
> With the kitchen sink approach of design I’d not be surprised if these get into the language eventually.
Based on the history of C++, they will, but with extremely bizarre syntax. Instead of a.foo(b), it will be something like a@<foo>::&{b}.
The justification will, of course, be that the other options break digraph support on some ancient platform that has no public spec and will never sunset - likely one IBM AS/400 in a basement with a 45 year uptime.
Out of "respect for existing deployments," the syntax must accommodate the relic even though no one has seen it outside of the folklore of one 1993 Usenet post.
Every tool chain will begrudgingly implement it and then years later when someone proposes removing it the counter argument will be, "we can't remove it now someone is using it!" The someone, of course, is a hobbyist on the mailing list whose project depends critically in the feature.
Well, they're still in use. I've encountered 3 in the last 5 years of consulting. Just because your startup doesn't have one doesn't mean they don't exist in the manufacturing world. Are they ubiquitous? No. Are they rare? Depends on the industry.
Perfect! The standards committee will be able to reference this reply the next time they justify eye-bleeding syntax instead of having to go mine Usenet. Thank you.
When you say they're still in use, do you mean AS/400s or digraphs?
So true. For me C++ syntax is unreadable, but the ideas behind it are familiar
“ a@<foo>::&{b}”
This made me smile. So true
See new reflection syntax.
Isn't structural pattern matching basically the same as creating an anonymous struct whose fields are references, with a default assignment operator? Syntax ideas should spring to mind.
Achieving uniform call syntax is easy, compilers just need to implement a new form of symbol resolution called "Kaiser lookup". It follows 14 easy to understand rules. It first looks up methods, then searches for template definitions in /tmp, then for a matching phase of any Saturn moon at time of compilation, then looks of definitions std::compat, and then in the namespaces of the types of any variables in scope anywhere on the call stack. If none of those work, it tries to interpret the call syntax as a custom float literal, and if even that fails, as a Perl 4 compatible regex. It's really intuitive if you think about it.
I wish C++ name lookup and overload resolution were that simple.
Yep, and there are new ways with modules, and reflection, we can't have enough. :)
> uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
Ive written a lot of c++ in the past but I'm not particularly knowledgeable about fp, so I'm wondering why this is important. Is it syntactic sugar or something more significant?
It allows existing method-heavy code to be used in a functional style without bending the knee to more OOP inspired patterns.
Think of a fluid API, but instead of chaining method calls you'd pass data to several functions "chained" together similar to how UNIX pipes work.
With this type of API, you can pass one argument into the function and pipe the other such that:
Is a more FP friendly version of:Makes sense. Thank you!
Shouldn't that be?
Or maybe:The second piece (uniform call syntax) looks convenient, though I don’t see a realistic way to integrate it into modern C++. The first (structural pattern matching) is, for me, more of a dividing line between low- and high-level languages. I tend to avoid it in my C++, just as I avoid inheritance, virtual functions, and exceptions… or `<functional>` header contents.
Still, it’s always fun to stumble on corners of the STL I’d never paid attention to, even if I won’t end up using them. Thought it was worth sharing :)
There was an experimental implementation for uniform function call syntax in a clang fork, so it’s clearly doable.
> * structural pattern matching
Yes, but that has to come with proper sum types. std::variant doesn’t quite cut it.
I’d also absolutely require a simpler lambda syntax. The current one is terrible for one-liner lambdas.
C++ has a philosophy of not doing anything with the language if it can be done in library.
Now the question is : what can we improve in the language so it can allow you to define a sum type better and more usable compared to std::variant?
This is a surprisingly difficult question to answer, hence we haven’t had progress there.
> C++ has a philosophy of not doing anything with the language if it can be done in library.
Then why is the language so ridiculously complicated? I had the most fun working with it back in around 2000, and even then it was quite insane. A lot has been added since then.
> * structural pattern matching
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p26...
There are actually a few functional programming in C++ books out there. The language has changed a lot since C++98, when this would be unthinkable. Alexander Granin maintains a curated list of functional programming C++ resources [1].
[1] https://github.com/graninas/cpp_functional_programming
I don't find it surprising. My impression is that people like Herb Sutter and Alexander Stepanov actively pushed in that direction in the early days. `<functional>` was, AFAIK, part of the STL before it got incorporated into the C++ standard library.
Another missing piece is a good syntax. C++ has, as you said, most of the capabilities already, but actually using them will quickly turn the code into symbol vomit.
No more of a loaded footgun than trying to do it manually, I don't think.
Geographic points should probably be represented as a labeled structure to prevent confusion and passed into functions as such. Using two separate libraries with mutually incompatible error-prone APIs as described is the real loaded footgun IMO. If you can't find better libraries, write wrappers; if you don't have time to write/maintain wrappers, pray. Anything else is just a bandaid.
Probably no better or worse than the alternative. That aside, the example doesn't understand the standards in question.
The governing standard for geospatial data representation is ISO 19125, which defines (longitude, latitude) order. GeoJSON naturally conforms to ISO 19125 since it is a format for processing data on computers.
ISO 6709 is essentially a print formatting standard and orthogonal to storing geospatial data on computers. That some data file formats happen to be human readable does not make ISO 6709 apply.
If you are processing geospatial data on computers the correct order is always (longitude, latitude).
> someone is bound to accidentally use it {zero, two} times
That risk is inherent to the problem at hand, and has nothing to do with std::flip.
I love Haskell but when writing C++ I always avoid functional style gibberish. I feel like this style of programming only works in languages properly designed for that.
> Interestingly enough, most of these implementations only flip the first two parameters of whichever function they are passed, though it seems to be because most of them are based on the Haskell prelude, and handling arbitrary arity can be tricky in that language.
Probably because the use case for it with higher arity is hard to imagine. (Indeed, TFA gives only examples with binary operations.)
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
> [snip 114 lines of code]
Meanwhile, in Python:
(I leave keyword arguments alone because there's no clearer semantic for "flipping" them.)The `toolz.functoolz.flip` implementation (being restricted to binary functions) is an even simpler one-liner (https://toolz.readthedocs.io/en/latest/_modules/toolz/functo...), though accompanied by a massive docstring and admittedly simplified through a heavyweight currying decorator (which accomplishes much more than simply getting a function that does the right thing).
Your python code allocates an array and inverts it every function call.
The C++ code has no overhead and is equivalent to a compile time transformation.
Of course. But if I had to care about things on that level, and I was willing to sit through the C++ compilation process (and everything else that goes along with that), I wouldn't be using Python in the first place.
For higher arity there is a combinatorial explosion of all the possible permutations.
But if you want to flip the 2nd and 3rd argument in Haskell it can be done by flip itself:
flip23 foo = (\x -> flip (foo x))
Or just (flip .), which also allows ((flip .) .) etc. for further flips.
In Smullyan's "To Mock a Mockingbird", these combinators are described as "cardinal combinator once/twice/etc. removed", where the cardinal combinator itself defines flip.
this blog post will be a great barometer of commenters who read the post vs those who don't
THUITFHNGL
Fortunately almost all the functional features in the article, like range folds and negation wrappers, do exist.
> THUITFHNGL
Tragic Harvest Underwrites Infrastructure That Frobs Haptic Network Generation Load?
When in doubt, UD it:
https://www.urbandictionary.com/define.php?term=THUITFHNGL
Oh I did. I just couldn't resist the temptation to have some fun with it. :-)
I skimmed the post. I have absolutely no idea what std::flip is supposed to do. All the sample code looks awful and undesirable. And that’s coming from someone who writes C++ every day. Yes I read the plot twist at the end, made me lol.
You write C++ every day, and you didn’t understand the is_descendant_of/is_ancestor_of example? Or how you can use it to reverse a relation like std::less?
I don’t understand why I should care about this. It doesn’t appear to solve real problems. The examples are all dumb toys and simply writing a wrapper by hand is perfectly fine and easier to read.
It's as useful (or not) as you find this:
https://cppreference.com/w/cpp/utility/functional/not_fn.htm...
If you don't see any value in that, you wouldn't see any value in the similar `flip` function or other combinators.
> I have absolutely no idea what std::flip is supposed to do.
Just reverse parameter order. It seems very silly.
You do realize it's not meant for silly situations like that, right?
Should be using empty base optimization or [[no_unique_address]] for that implementation
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
I hope not.
⍨