
You can't make C++ not ugly, but you can't not try (2010) - soundsop
http://apenwarr.ca/log/?m=201007#18
======
gumby
This decade old article describes an ancient and obsolete language (C++03
probably though some of the text suggests it might be C++98). It's not worth
reading in 2020.

Modern C++ is a very different language though it can still almost completely
interoperate with quite old code. C++11 and C++14 already addressed most of
the things brought up, and contemporary C++ (obviously most code is not in it
yet) even supports generic template functions with a straightforward syntax
(i.e. use auto instead of <..>).

~~~
Rusky
To the contrary, modern C++ has solved very few, if any, of the problems
described in the article.

Being generous:

* There's now `std::string_view` to address _some_ of the problems with `std::string`, but the rest are still there. There are some attempts to specify the encoding now, at least.

* Lambdas and `std::function` pretty much solve the function pointer complaints, with some added complexity.

* Containers still do silly things when you use `c[..]` syntax with no element there. (Both when trying to insert and when trying to retrieve!)

* The general level of language size and complexity, especially around templates, has only gotten worse. Concepts will finally help in some ways here.

~~~
cmroanirgo
If everyone can forgive my ignorance... I used to call everything in the std::
namespace as just that, the standard template library (STL) (whatever it's
iteration). So is this C++03 /C++11 stuff just updates to this library, or is
std::string recognised at the compiler level? (Genuinely confused).

(In old man voice) We ended up rolling out own stuff and marking std::
verboten. Why? Stl was too slow, too verbose, and too hard to grok the stack
when debugging. We ended up with less memory fragmentation, less dangling
ptrs, etc etc. In the rare case there was something in STL that was actually
cool (or faster, which was very rare), we'd gut it and reef out the part that
was cool to use in our implementation.

I presume these comments aren't popular (sorry about that, but this is during
90s and early 00s when dev cycles were clearly different). Eg. We had string
classes in all different flavours, some would interop, some wouldn't. Eg. We
had tree and hash classes that, while templateable, had a few core
implementations that made compilation _fast_. We had various ptr management
systems (ref counted, stack based, etc).

We made STL between verboten in all APIs because we been burnt so many times
using (/trying to use) other ppls APIs that exposed STL in its library. (We
were exclusively a windows shop, if that helps understand my confusion... PS.
I'm retired these days and have been out of the c++ game >10yrs)

~~~
vlovich123
Every new standard incorporates language & library changes. A perfect example
of this is r-value references. That was a new language feature in C++11 & was
adopted by all the standard library containers & algorithms.

Not sure which part of std::string you're referring to but the compiler
generally doesn't contain any knowledge of the library itself. It does goes
the other way though where the standard library has to know how to implement
certain functionality on a given compiler (some type traits functionality IIRC
isn't possible to implement without compiler builtins that expose the AST to
you). I think Rust has taken a more sustainable approach with their macro
system which can modify the AST instead of relying on builtins but even in
Rust I suspect they use builtins in certain places of their standard library.

Today's STL implementations are going to be better performing and more robust
than anything you'd write yourself so generally a good idea to stick to it as
a rule of thumb for the majority of code.

~~~
gpderetta
As non exhaustive list, compilers have have intimate knowledge of:

* various operator new * all the type traits * a lot of the names inherited from the C library

compilers could do much more, but in general they prefer to implement generic
optimizations instead of targeting a specific library name (for example
removing allocation for stack allocated std::strings was not done until the
generic removal of alloc/free was implemented).

~~~
MaxBarraclough
Many of the type-traits can be implemented without specific compiler support.
That's how Boost managed it. Here's their implementation of _is_signed_ if
you're curious [0].

I believe _some_ standard-library type-traits cannot be implemented without
specific compiler support.

[0]
[https://github.com/boostorg/type_traits/blob/059ed883/includ...](https://github.com/boostorg/type_traits/blob/059ed883/include/boost/type_traits/is_signed.hpp)

------
virtualritz
I recently picked up a C++ codebase I wrote maybe 30% myself and which was
last touched in 2013. It's a plug-in for a DCC app. The 3rd party API it
communicates with was changed so refactoring was inevitable.

Before I started refactoring I decided to make everything 'less ugly' by
moving it to C++17. Which would also help me get back into the code, after
eight years.

I spent two days on this. Then I decided: fuck it. I will RIIR™.[1]

It will mean that it will take me at least two months to get a beta of the
product with feature parity vs. maybe a week to port the old codebase to the
new API.

But on the other hand the C++ code is littered with stuff that 'just works'
but can explode in your face and which eventually needs some safety net
written around it. Which will likely take at least two months to do properly.

The truth is: after one and a half years of Rust C++ feels painfully ugly.

For context: I started with C++ with Borland's TurboC++ (moving from C) when I
was 15. I used C++ for almost 30 years by now. It's about time.

[1] Yes, I did read [http://adventures.michaelfbryan.com/posts/how-not-to-
riir/](http://adventures.michaelfbryan.com/posts/how-not-to-riir/)

------
unlinked_dll
Most of what the author complains about w.r.t callbacks is fixed with
std::function and lambdas (member function pointer syntax is necessarily
weird, because methods aren't just normal functions). I definitely don't miss
the days of std::bind. Nowadays you just do something like

    
    
        using Callback = std::function<int(int)>; // or whatever
        Callback cbk = [&](int i) { return instance.method(i); };
    

I've also seen some real evil hacks that rely on casting 0 as a pointer to a
class and relying on the ABI's spec for vtable layout to calculate the pointer
of the function as an offset from the `this` pointer. Because that's easier to
remember than the syntax for pointer-to-member functions.

~~~
AnimalMuppet
What's so awful about pointer to member? I've used it a couple of times, and
didn't think it was particularly weird. I mean, yes, I had to look up the
syntax, but it was rather straightforward.

~~~
gpderetta
there is nothing wrong with them, but pointers to member do not close over
this, so you still need some form of binding to use them as callbacks.

~~~
AnimalMuppet
Well, when you invoke a pointer to member, you need an actual object (a this)
to invoke it on. So when the member runs, it has a this.

But it sounds like what you want is a "handle" or some such term, by which you
can invoke a member function on an object, and all you need to do so is the
handle. That's a different problem than pointers to members are trying to
solve, but you can do that quite easily with a function object. That's
essentially a roll-your-own closure, and since you can define whatever data
members you want, you can close over anything.

One thing you have to watch out for, though, is lifetimes. C++ is not garbage-
collected, and so it will not preserve an object just because another object
has a pointer or reference to it. If you create a function object that
captures a pointer to member, and a "this" to invoke it on, and the "this"
gets destroyed, and then you use the function object, you're going to get
chaos.

------
crazypython
You can make C++ pretty: it's called D.
[https://dlang.org/comparison.html](https://dlang.org/comparison.html)

Here's the D<->C++ intercompatibility project:
[https://wiki.dlang.org/Calypso#Current_status](https://wiki.dlang.org/Calypso#Current_status)

~~~
coding123
There's always a D guy lurking about.

~~~
WalterBright
We're everywhere.

~~~
coding123
Whoa!

------
gpderetta
> The problem is that the default C++ string class is so utterly godawfully
> stupid. No garbage collection? Check. No refcounting? Check.

the irony is that in 2010, many std::string implementations were in fact
reference counted (including libstdc++). This was generally considered a major
mistake (because it doesn't work well with threads and when it does is a major
performance pitfall) and prohibited in C++11.

------
MrBuddyCasino
Apenwarr‘s comment to this thread: „ I have since stopped trying to program in
C++ at all. C is still ok, sometimes.“ [...] „I want to like Rust; maybe
someday.“

[https://twitter.com/apenwarr/status/1232848468156256256?s=21](https://twitter.com/apenwarr/status/1232848468156256256?s=21)

------
Ar-Curunir
The followup article
([https://apenwarr.ca/log/20100721](https://apenwarr.ca/log/20100721)) talks
about a potential C/C++ replacement, and seems like a lot of points match up
with Rust

------
ggambetta

      [The [] operator] is an absolute 
      failure of engineering!
      Do you want to know what real
      engineering is? It's this:
    
      map_set(m, 5, "foo");
      char *x = map_get(m, 5);
    

So like map::insert and map::at? Did these not exist in 2010?

~~~
pjmlp
They did.

------
dathinab
Didn't got much better since then with regard to that aspect. IMHO C++ tried
to adapt many features which had shown succesfull since then, but instead of
properly putting them into the language they often got implemented in a way
which I would describe as "somehow hacked in to try to avoid to actually
introduce new features" but others might describe as implemented in a very
C++ish way in symmetry to other features. Anyway the result is often sup-par.
Not downright shit but worse then it should be and more important making the
features work less good (from a complexity+usability POV) then in the
languages they where copied from.

------
AtlasBarfed
"C++ isn't a language, they say, it's a language construction kit! Build the
language of your dreams in C++! And it'll be portable and scalable and fast
and standardized!"

This is the power and achilles heel of Lisp as well.

~~~
nineteen999
Difference is, C++ actually has more than single digit mind/marketshare
outside the safety of the HN eggcup.

~~~
TheOtherHobbes
Market share, maybe.

Mindshare - Lisp has its zealots. C++ is tolerated rather than adored, because
it's more of a Katamari Damacy of stray CS than a language with a coherent
focus.

How many other languages have a Turing complete sublanguage built into them
just to handle templating?

~~~
nineteen999
> How many other languages have a Turing complete sublanguage built into them
> just to handle templating?

On the bright side, C++ doesn't have obscure keywords like "cdr" and "car"
that refer to specific hardware elements of an obsolete computer built in
1954.

~~~
praptak
Car and cdr are a shallow critique of Lisp, the equivalent of "omg,
significant whitespace" critique of Python.

~~~
kazinator
Significant whitespace is more of an issue than what two functions should be
called.

Significant whitespace means that we can't reliably use a traditional
whitespace-insensitive diff to to compare changes in Python code that
seriously change its meaning, such as change how many statements are in the
scope of an _if_.

------
cryptonector

      > But in python, it works perfectly (even for
      > user-defined types). How? Simple. Python's parser
      > has a little hack in it - which I'm sure must hurt
      > the python people a lot, so much do they hate
      > hacks - that makes m[5]= parse differently than
      > just plain m[5].
      > 
      > The python parser converts o[x]=y directly into
      > o.setitem(x,y).
    

The name for this is "generalized variables", at least in Lisp land. The idea
is to allow complex assignment left-hand side (LHS) expressions and turn
assignments into calls to setter functions, including whatever complex data
structure traversals might be needed.

Lisp has generalized variables via "setf macros", which turn assignments into
the right set of calls to setter functions. Setf macros do this at compile
time and generically for any getter/setter functions that have been registered
with a bit of ceremony.

(Lisp also has destructuring-bind, which lets you write a data structure with
variable symbols in it such that the corresponding variables will be bound to
the data find in the corresponding places of a real data structure value. The
two features, destructuring and generalized variables, are similarly magical.)

jq can do crazy generalized variable assignments like '.a[].b[0] = 1', but
this is a run-time thing. (The LHS is evaluated in a context that allows only
"path expressions", and in a reduction, while the RHS expression is run in the
body of the reduction to update the input value at each path matched by the
LHS.)

Icon implements generalized variables by letting procedures return "places"
\-- references to assignable locations --, so you can assign to the return
value of procedures. This may seem quite surprising when you see it, but it
works beautifully.

------
_wldu
_" About the same time we were starting to work on Go, I read, or tried to
read, the C++0x proposed standard and that was convincing to me."_ \-- Ken
Thompson

Source -- [https://www.youtube.com/watch?v=sln-
gJaURzk](https://www.youtube.com/watch?v=sln-gJaURzk)

------
pizlonator
I love programming in C++ and I love this rant.

If you ever design a language you should hope that it gets popular enough that
people bitch about it this hard. That’s a real triumph.

------
sagarm
I 100% agree with the title, but some of the complaints in this article have
been addressed since the article was published by C++11, C++14, or C++17.
Others are just weird.

> If you've heard anything about C++, you've probably heard that there's no
> standard string class and everybody rolls their own.

Is this still true? std::string seems perfectly reasonable now, especially now
that they've given up on supporting ropes and integrated the small string
optimization. Yes it doesn't specify an encoding.

> No garbage collection? Check. No refcounting? Check. Need to allocate/free
> heap space just to pass a string constant to a function?

Nothing else in the standard library is garbage collected or refcounted by
default. Why would std::string be the lone exception? You can opt into
refcounting for any type using std::shared_ptr.

The objection about allocating/freeing heap space is about APIs that take
const std::string& being passed C-string literals. Legitimate complaint, but
it's addressed by std::string_view now.

> ...rant about lack of []= operator...

[] mutating the map does surprise people, so that's definitely a legitimate
complaint. And it is annoying to have to use find() in a const context...

but simple operations like counting are simpler and more efficient in C++ than
in Python because the +=, -=, etc operators work:

    
    
      for value in list:
        counts[value] = counts.get(value, 0) + 1
    

vs

    
    
      for (auto& value : list)
        counts[value]++;
    

> So actually C++ maps are as fast as python maps, assuming your compiler
> writers are amazingly great, and a) implement the (optional) return-value
> optimization; b) inline the right stuff; and c) don't screw up their
> overcomplicated optimizer so that it makes your code randomly not work in
> other places.

This is just comical. Python is not playing in the same performance league as
C++, regardless of whether inlining or RVO happens. RVO of course is a general
optimization for returning objects by value from functions, not a special case
to optimize setting map items. It's still relevant, but less important since
C++11's move semantics.

------
sarabande
Replace the two double negatives and you get a better title:

"You can't make C++ beautiful, but you must try."

------
____Sash---701_
You mentioned that there's no standard string class for c++, and that python
is your goto, would recommend looking at Dart Lang, extension methods have
recently been added, it also compiles to native exec binaries.
[https://dart.dev/](https://dart.dev/)

------
asdfasgasdgasdg
That C code wouldn't work if the strings in the map are mutable (which they
are in C++). It wouldn't work if the strings were allocated dynamically
either. Once you write all the code required to get it working that way, the C
starts comparing less favorably to C++.

~~~
fctorial
> That C code wouldn't work if the strings in the map are mutable (which they
> are in C++). It wouldn't work if the strings were allocated dynamically
> either

I don't see why not? You'll have to decide the ownership semantics if they're
dynamically allocated but that's it?

------
trewr234234
The biggest issue with C++ for me is the confusing memory model. You have
smart_pointers, you have move semantics, and then you have libraries like
OpenCV doing their own refcounting (and also using std::shared_ptr). The C++11
features like lambdas are definitely welcome but at this point C++ epistemic
footprint is just too large.

------
diegoperini
Can a native English speaker kindly explain the title? With that many negative
tenses, it's really hard to parse the motivation of the author.

~~~
jimktrains2
It's in the vein of "even if you can't do something, you should to try anyway"

Not ugly is more of a less strong "pretty" and can't not is more of a "should"
(rather than "can"), so the title can be read as "you can't make c++ pretty,
but you should try (to make it pretty)".

~~~
diegoperini
Thanks!

------
dana321
C++ is mostly an abstraction layer over C.

I would rather use C++ because i would have the option to use abstractions
like std::map, std::string, std::vector, std::any etc. as they would save a
lot of time and code complexity.

IMHO the worst thing about using C/C++ is getting other libraries to play with
your project well even if you are using cmake or vcpkg, its not enough. You
have to have solid knowledge of each operating system class to get everything
working nicely over the long-term.

~~~
saagarjha
> C++ is mostly an abstraction layer over C.

That depends on how you write it. It’s possible to write C++ that bears no
semblance to C whatsoever. (I have done so for effect; I once assigned some
students to write a C program but wanted to provide a reference implementation
with source so I gave them one in C++ that wouldn’t translate directly at
all.)

~~~
dana321
Internally, i mean, thats how it was originally built and it generates code
much in the way that C would, if you would have manually coded it. Maybe not
jump on my head without thinking first!

~~~
saagarjha
Not trying to jump on your head; I apologize if you got that impression. But
while C++ was originally C preprocessor/transpiler, modern C++ has diverged
quite a bit from from this. I mean, take a look at this and tell me how it
would be possible to convert it to C in a straightforwards way:
[https://github.com/regular-
vm/libencoding/blob/master/encodi...](https://github.com/regular-
vm/libencoding/blob/master/encoding.h)

~~~
dana321
Internally, that's what C++ is doing..Converting the abstractions into code.
There is a lot of complexity hidden from view (you might see some of it when
you get some esoteric template bug in your ide)

Btw, that code is using std::unordered_map which is notoriously slow compared
to an ordinary std::map for a small amount of elements. And the unordered map
of register names each with a pair, why? Why not use std::vector for fast
lookup?

The crux of what i'm saying isn't anything new, Bjarne Stroustrup admits that
C++ is mainly a high-level abstraction language. There is some new things, but
really, the things that are new like << etc. are operator overloads which are
just more abstractions.

------
rkv
> No support for null strings? Check

Why would you ever want this? Gives me Java nightmares.

------
Quekid5
I certainly agree that Modern C++ is vastly bett... actually this is just
someone posting an old polemic and trying to stir resentment/controversy.
_IGNORE THIS CRITICSM OF C++_... there are much better and more relevant ones
which are worth taking seriously. This is not worth taking seriously.

(I wonder how many of these posters are real or fake. Given the karma and post
of this poster I'm inclined to... wait. Last comment was in 2017. Yet...
hasn't too many posts since that, I think it's about 4. Probably a bit out of
the loop, buy maybe not malicious.)

~~~
DenisM
From the rules:

[...] Please don't post insinuations about astroturfing, shilling, brigading,
foreign agents and the like. It degrades discussion and is usually mistaken.
[...]

[https://news.ycombinator.com/newsguidelines.html](https://news.ycombinator.com/newsguidelines.html)

