51 comments

[ 3.1 ms ] story [ 113 ms ] thread
(comment deleted)
Look at the 3rd to last code block, especially; I claim it self-evident that C++ is irrevocably broken.
How is it self-evident?
This is not what one would call 'sane C++'. I think it's more of an exercise than production code. If it is production code, may Knuth have mercy on whoever maintains that codebase.
Except that nobody in their right mind would actually write code like this on a day-to-day basis. All languages allow the writing of insane nonsense. The question is how much nonsense is actually required in everyday work, and on that point I would argue that C++ is moving in the right direction since C++11.
Wow. I think I'm a decent programmer, but that is some hardcore shit right there.

Some of those syntax constructs are really hard to wrap my head around. In a lot of cases `x | f` is more readable than `f(x)` (especially when you want to chain several functions), but there's no way I can remember how to extend the language this way unless I'm a full-time C++ programmer. I don't feel the same in Haskell, for example.

Also, function composition should be similar to this, anyone can shed some light how it would be implemented? (I mean `x | f . g` would look awesome.)

You can't overload the `.` operator. However, something like this would work: `x | (f , g)`.
(comment deleted)
How can any sane person think this is a reasonable way to program?

While perhaps stated with slight hyperbole, this is actually a serious question. I don't see how, for example, these pipable functions give any additional real expressive power, and they seem incredibly hacky in how you have to build them. I simply don't understand why anyone would use this kind of thing. C makes sense; I can see an argument for C++03; this is madness.

Well this can help when you have many nested functions that are chained together, for example, like this:

    auto r = select(where(numbers, [](int x) { return x > 2; }), [](int x) { return x * x; });
Because of nesting this can be hard to read(and even write). If we can make the function pipable, the above can be written like this:

    auto r = numbers | where([](int x) { return x > 2; }) | select([](int x) { return x * x; });
Which it is easier to see what this does. So this blogpost goes over how to implement an utility that will take care of the boilerplate involved in writing a function that can be pipable.

It does take advantage of a lot of C++14 features to implement this such as such as vardiac templates, rvalue reference, generic lambdas, and auto type deduction, but those feature actually make it simpler. I have in the past implemented something similar for C++03 and the code looks awful with way too much noise.

> this is madness.

Why do you consider this madness?

Maybe my mind has already been warped by C++.

I really like your above example. For people coming from Ruby on Rails or Python Django, such a notation makes it simple to understand what the code will do, even without knowing how to implement it.

Thanks

To me, the first is much easier to read, but perhaps my mind has been warped by functional programming languages.

At any rate, the thing that's strange about it is how verbose and hackish everything is. It's obvious the language wasn't designed to do anything like this, so the only way you can incorporate features like this is with unnecessarily obtuse and complex blocks of code.

> To me, the first is much easier to read, but perhaps my mind has been warped by functional programming languages.

Actually, pipable is very similiar to haskell's `$` function. So a similiar thing is done to improve readability of chaining functions in functional languages as well.

> At any rate, the thing that's strange about it is how verbose and hackish everything is. It's obvious the language wasn't designed to do anything like this, so the only way you can incorporate features like this is with unnecessarily obtuse and complex blocks of code.

How is the code obtuse and complex? I am using fairly straightforward techniques. I understand that I am working around the lack of perfect capturing for lambdas, but it is not complicated or clever the workaround.

The first one looks like you're chaining functions together, while the second one looks like you're taking multiple bitwise or operations over the result of each function.

I think it's fascinating to see how the language can be extended. On the other hand I would not want to be the poor bastard stuck maintaining either construct after discovering some compiler edge case. If I've learned anything it's that just because you can do something doesn't mean you should.

The issue with extending operators in this fashion is that you're violating the principle of least surprise. | is an operator that should only really do exactly what it always does--bitwise or. If composition of functions is fundamentally analogous to taking the bitwise or of two values then I would concede that your second syntax is a bit clearer.

Personally I feel that this is not the case.

> The issue with extending operators in this fashion is that you're violating the principle of least surprise.

It is really common for the `|` operator to be used for piping functions together. There are several libraries that support it: [Boost.Range](http://www.boost.org/doc/libs/1_56_0/libs/range/doc/html/ind...), [PStade Oven](http://p-stade.sourceforge.net/oven/doc/html/index.html), [Linq](http://pfultz2.github.io/Linq/), and [Streams](http://jscheiny.github.io/Streams/). Plus, its even being proposed as part of the ranges library in future C++: https://github.com/ericniebler/range-v3/blob/master/doc/D412...

So just as the bit shift operators in C++ also have the meaning of streaming in and out, the bitwise or also has the meaning of pipe, so this is should not at all be a suprise.

Interesting. I was not aware of this, so thank you.
I'd say JavaScript-style chaining is more (or atleast as) readable as that.

E.g. numbers.where().select();

This kind of complexity is only really needed for library writers... e.g. someone writing a package for Boost. Your run-of-the mill application code in C++ will probably stay away from this kind of wizardry.
I've been programming in C++ for close to 20 years now and the only description for what I feel when looking at this sort of thing is "morbid fascination". It's cool, but it's disappointing at the same time. The C++ I started with looked and felt significantly more logical and easier to grasp than what we have now and this is a very unfortunate way for a language to evolve.
Taking a huge risk by saying this (I do not use C++ full-time), but every time I look at C++11 and C++14 I get the feeling that I should just stick with Haskell or <insert-popular-fp-lang-here>. It is insanely more natural to do stuff like this in Haskell. Defer to those with more experience in 'modern C++'.
I use C++ more than full time (gamedev -_-;;) out of necessity (consoles, existing codebase, C++ APIs...)

If I could swing it, I'd absolutely code everything in C#. Okay, maybe that fails the "fp-lang" bit, but my reasons are basically the same ;). Unity's finally getting around to covering the "consoles" bases, although I really wish they weren't using old buggy compilers and writing similarly buggy APIs. Maybe MonoGame will port to Xbox One eventually...

Somehow I knew the top comment was gonna be "C++ is insane!!"

How would you implement pipe functions in Javascript? And I mean literally using the pipe operator, and not just chained. You can't, so you don't.

In Java? You can't, do you don't.

In C++ you can. You probably won't. You probably shouldn't so others can maintain your code in the future. But still, you can do very advanced things.

Thanks, pfultz2, for flexing C++'s muscles.

Just because a language allows you to morph its syntax doesn't make it any more powerful. I'm not sure how this is considered an "advanced" thing. It's certainly a convoluted thing, but that's not at all the same.
There are three reasons why a program would look like this:

  1. It is something simple implemented in a complicated way.
  2. It is something complicated implemented in a simple way.
  3. It is something complicated implemented in a complicated way.
I believe this is an example of case 3, meaning even though it is something advanced, it should not take code that looks insane to express it.
I disagree. It makes it very simple to make a pipable function, you just wrap the function object in pipable. There's nothing complicated about using it.

Futhermore, doing it by hand is not as simple, but still not very complicated. This just helps alleviate the boilerplate in defining pipable functions.

There's nothing complicated about using it.

I'm not talking about using the pipe operator, I'm talking about implementing it. The C++ version is ridiculously complicated.

Let's compare your code with an analogous implementation in Common Lisp.

  (defun pipe (val &rest fns)
    (reduce (lambda (acc f)
              (funcall f acc))
            fns :initial-value val))
Just this allows for some pretty similar code:

  (pipe 99 #'1+ #'sqrt #'1-) which evaluates to 9.0
It is possible to implement it in C++, but the implementation winds up being ridiculously complicated. I just implemented something very similar in Common Lisp and it wound up being incredibly simple. Why doesn't C++ allow for a definition nearly as nice as the Common Lisp one?
> It is possible to implement it in C++, but the implementation winds up being ridiculously complicated.

It may be more verbose in C++, but its not more complicated, and its definitely not "ridiculously" complicated.

> I just implemented something very similar in Common Lisp and it wound up being incredibly simple.

Maybe for you as a lisp programmer, but for me I find the lisp code incomprehensible.

>How would you implement pipe functions in Javascript? And I mean literally using the pipe operator, and not just chained.

>But still, you can do very advanced things.

Why do you think "advanced" means the ability to overload operators? I'd personally define "advanced" to mean something to do with semantics, not syntax.

I've been quite disillusioned with C++ myself as of late. I am currently writing a wrapper class around shared pointers to objects with inheritance (which embed their own private implementations, which also utilize inheritance) so that I can pass them by value and use operator. for method chaining. The SFINAE used for construction from base objects to these shared pointer wrappers is something out of the ninth circle of hell.

    template<typename T> struct Shared : std::shared_ptr<T> {
      template<typename U> struct is_compatible {
        typedef char yes[1], no[0];

        template<typename V> static yes& test1(typename std::enable_if<std::is_base_of<std::shared_ptr<T>, V>::value>::type*);
        template<typename V> static no& test1(...);

        template<typename V> static yes& test2(typename std::enable_if<std::is_base_of<element_type, typename V::element_type>::value>::type*);
        template<typename V> static no& test2(...);

        static constexpr bool value = sizeof(test1<U>(0)) == sizeof(yes) || sizeof(test2<U>(0)) == sizeof(yes);
      };
And from the function binder:

      //value = true if R L::operator()(P...) exists
      template<typename L> struct compatible {
        template<typename T> static constexpr typename std::is_same<R, decltype(std::declval<T>().operator()(std::declval<P>()...))>::type exists(T*);
        template<typename T> static constexpr std::false_type exists(...);
        static constexpr bool value = decltype(exists<L>(0))::value;
      };
      template<typename L> function(const L& object, typename std::enable_if<compatible<L>::value>::type* = nullptr) { callback = new lambda<L>(object); }
And yet ... when actually using the library, it is a thing of beauty.

    struct TextEditor : Window {
      MenuBar menuBar = {this};
        Menu menuFile = {&menuBar, "File"};
          MenuItem menuQuit = {&menuFile, "Quit"};
    
      VerticalLayout layout = {this};
        TextEdit editor = {&layout, Size{~0, ~0}};
    
      TextEditor() {
        StatusBar statusBar{this};
        statusBar.setFont(Font::sans(8, "Bold")).setText("Line 1, Column 1");

        HorizontalLayout findBar(&layout);
        findBar.append(Label(&findBar, "Find:"));
        findBar.append(LineEdit(&findBar).setBackgroundColor(Color::Yellow)
        .onChange([&] { searchFor(text()); }));
        findBar.append(Button(&findBar, "Clear")
        .onActivate([&] { findBar.widget(1).setText(""); }));
    
        menuQuit.onActivate(&Application::quit);
        edit.onChange([&] { updateStatusBar(); });
      }
    };
There is no need for any memory management, or any usage of pointers. We build UIs, and everything gets automatically released safely when nothing is referring to it anymore. We can declare named objects that we can use later, or we can create dynamic objects and pop them right inside of other objects. We can destroy and unparent things whenever we want. And it's deterministic, reference-counted GC. No pauses for a tracer. No dynamic typing anywhere, all errors are at compile-time.

I highly suspect that C++ is unreasonably complicated and that all of this rvalue-reference, variadic template, meta-programming, dynamic-casting polymorphism, is all just voodoo that isn't applicable to general programming. And yet, being able to do it gives me amazing expressive power to write awesome libraries that I could never hope to accomplish in another language.

Until I find a language that's even in the same ballpark as C++ in terms of performance, and offers similar expressiveness, it really doesn't even matter how bad C++ can be for library authors. There's no other viable option right now. D ...

A side note: I think you may be able to simplify `is_compatible` by getting rid of hand-rolled `yes` and `no` constants (and subsequent manual size-testing for `value` computation) and using `std::true_type` and `std::false_type`, respectively: http://en.cppreference.com/w/cpp/types/integral_constant

Edit: noticed you're using these in another place (`compatible` implementation), so perhaps there's a reason for a different approach?

Thanks, the is_compatible test certainly gave me a good bit of trouble.

Ideally you'd want to do enable_if< conditionA || conditionB >, but of course if one of the conditions fails to evaluate, the overload is ignored. So you have to split out the conditions and them merge them back together later on.

We could use true_type / false_type, but they have equivalent sizes. So unlike the function version that only needs one test and can just take the return type directly, the test at the end would then have to become std::is_same<decltype(test1<U>(0)), std::true_type>::value | std::is_same<decltype(test2<U>(0)), std::true_type>::value.

I still think we can do better than even this, so I'll have to keep working at it.

Just because you can use some ambiguous syntax construct does not make it good or even clever.

This sort of thing can be done a lot clear with functional constructs like compose and curry.

Common Lisp example (easily done with any language that supports higher order functions): (funcall (compose 'add-one 'add-one) 1)

And you can roll that a number of ways; adding in a curry of the identity of 1 if you just want a nullary function.

(funcall (compose 'add-one 'add-one (curry 'identity 1))

And if you are interested in typing less, maybe you are a dreadful typist...try Haskell.

Interestingly, you can in Powershell, where piping objects works very naturally.
This is beautiful! Should be part of all languages. I love this in Jinja, but never thought that this should be native... until today!
I'd really like it if they added D's uniform function call syntax to C++ (http://www.drdobbs.com/cpp/uniform-function-call-syntax/2327...). Then this code could be:

    auto r = numbers().where([](int x) { return x > 2; }).select([](int x) { return x * x; });
Without the need and potential ambiguity that arises from overloading the pipe operator, you'd just use simple free functions instead and it'd just work.
Potentially ambiguous, but I assume worth it?
In practice, I've yet to see a problem.
By ambiguous, I mean that operator|'s precedence is very different than operator.'s (std::operator<< has similar issues); which could lead to surprises if you don't have the precedence tables memorized.

operator. is at the optimal precedence for method chaining, and has well understood behavior, so I think it'd be preferred over hijacking things like the bitwise-or operator.

Heh, didn't even notice that you mentioned ambiguity too. I was refering to the D's version with the dot operator: A programmer needs to differentiate between function calls and method calls. I wonder which ambiguity @pfultz2 was referring to.

But now that you mention it, an operator (such as pipe) with a lower precedence could actually be useful and spare having to jump back in code to open brackets.

I feel they could have stopped with C++03. C++11 certainly introduced very few necessary features but this is just going too far. This is not solving any problems but in reality is causing new ones as introducing syntactical redundancies ultimately leads to everyone having their own preference, which in turn is a source for inconsistent and dirty code bases and bugs.

They should have taken a hint from all of those coding standards coming from companies such as Google, which basically constrain the programmer to a small subset of the language features.

One rule of thumb I use for code is: Can you step through it in a debugger, at the source level, and have insight into what is going on? Because with really, um, "clever" code, you'll probably need to. And so will your cow-orkers. And if the code doesn't work or has limitations -- quite likely with creeping "clever" machinations like this one -- you'll get Spoken To.

(Yeah, this depends on how good your debugger is. Visual Studio and XCode are pretty decent).

This is the only metric worth paying attention to, in my experience. Whatever you do, your product will end up with defects, you'll need to get rid of them, and towards the end of the project there will always be a huge pile of them. (And, thanks to natural selection, only the hard ones are left. All the easy problems are long extinct.) So why not optimize for this stage? Even if you don't end up turning a time/effort profit overall - which is unlikely - you'll at least benefit from being under far less stress during what's always a fraught period.

And for whatever reason, C++ debuggers and debug info formats don't seem to be doing a great job of keeping up with the language. Even basic stuff like nested function calls (as easily found with any smart pointer/iterator or array class...) tends not to work very well, to say nothing of single stepping in to std::function, or watching STL types, or using iterators or smart pointers in the watch window. So this necessarily means being a bit conservative about which features you use.

(You might have to go through the shipping-a-product process a couple of times before you really internalize this. But once you've learned it, it really does stick.)

> C++11 certainly introduced very few necessary features

I have to ask whether you've actually done any significant amount of programming in C++, both C++11 and pre-C++11.

Honestly, it seems like the people who complain loudest about C++ have barely used it. C++11 is fantastic. C++14 and beyond are quite exciting as well.

I'd avoid Alexandrescu-style template trickery unless you really truly need it, though. You can write good C++ that more or less resembles Java, and that's fine.

> You can write good C++ that more or less resembles Java

That doesn't sound like good C++. Making everything a pointer or implementing polymorphism through pointers makes the code harder to reason about, and makes tighter coupling between interfaces and implementations.

Not to act too serious about nicks here, but his/er handle is "C--", after all.
This is a standard construct in functional languages, probably why it's getting into C++14. This "|" is $ in Haskell with its arguments reversed. F# even has it in this particular order of arguments "|>" IIRC.
The techniques he used are very similar to "Expression templates". If you are a C++ programmer and haven't heard of it, look it up. There are not many libraries actually making use of it, but Boost uBLAS is an example. I think expression templates give you a good idea what one can achieve with C++ (whether this is a good idea is another question).

OT: That's also why I love the language - there are plenty of ways to solve a problem and C++, unlike other languages, does not force me to solve a problem "the right way".