Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Trip C++Now 2024 – think-cell (think-cell.com)
76 points by coffeeaddict1 on May 10, 2024 | hide | past | favorite | 53 comments


I'll say this because it needs to be said. Recently it seems like every developer on LinkedIn who has "C++" in their profile, and their cat, is spammed every couple of weeks by some recruiter with a "great opportunity, 130,000 euros FULLY REMOTE", turning out eventually to be recruiting for think-cell.

I got it, my friends got it, my work mates got it, everyone gets spammed periodically, it has become the laughingstock of job ads.

Why? Read the Glassdoor reviews. The CTO is a terrorist obsessed with complexity, they overengineered themselves into a corner by using every freaking C++ feature that ever existed, making sure that there's only 3 or 4 people in this world who can understand their code. Of course the usual pattern goes: guy learns #feature on-the-fly appearing both incredibly smart and embelishing his resume, until of course reality hits and the complexity of the crap he wrote outclasses his "genius" intellectual capacity so then he leaves, leaving the mess for future hires to deal with.

Consequently there's maybe 3 people left in this world capable to understand the atrocity that's running there, not saying fix it. So good luck finding them. These people, if they exist, already work for more that $130k.

Also, why do they need the absolute raging brinking edge of C++ features? What does this company do? Trillion dollar ultra low latency / high throughput high frequency trading? AAA+ games running on 4 x 4k monitors at 200+ fps?

No. They do some farty charts.

Again. Good luck with the recruitment! :)


Same. I and a couple of my colleagues got offers.

Regarding love of complexity and C++, they’re sitting on the C++ committee. Complexity is their bread and butter. :)

I also kinda doubt “Trillion dollar ultra low latency / high throughput high frequency trading/AAA+ games running on 4 x 4k monitors at 200+ fps” people are such enthusiasts of the bleeding-edge of C++. The standard library and the style of programming popular in the committee isn’t very concerned with high performance it seems.


>> Regarding love of complexity and C++, they’re sitting on the C++ committee. Complexity is their bread and butter. :)

Wow, I didn't know that, it explains a lot. There was a post on HN a while ago titled '“Modern” C++ Lamentations' (https://news.ycombinator.com/item?id=29898955). My eyes bleed when I see that "N Pythagorean triples" program in "C++". That's as close to classic C++ as Chinese is to Latin. And it's barely 10+ years since 2011. At this rate I don't even dare think what the "Modernest C++" of 2034 will look like, given the incentives the committee guys run on. If programming was done with pen and paper, I'd bet on making us stick a pen in our ass and write with that. Either way I'm expecting some equivalent proctology intervention needed to comply with the standard 10 years from now.


Post author here. To be fair, we specifically don't use std::ranges because of that "N Pythagorean triple" problem. We have our own custom library that is vastly superior to std::ranges. See e.g. here for context: https://www.youtube.com/watch?v=l7ntC-Y1syY

I was recently appointed co-chair for the std::ranges group and I'm trying to bring some of the improvements to C++.


> Regarding love of complexity and C++, they’re sitting on the C++ committee. Complexity is their bread and butter. :)

To me the think-cell guys sound like they are gung-ho on clout-chasing, to the point that this side of theirs even eclipses their own products and services.

It sounds they got the whole point of software engineering entirely backwards: their goal is to pile complex tech showcases in their product line and presentations as a proxy to being competent in their field,and instead they unwittingly put together unmaintainable and convoluted code that requires a high cognitive load just to troubleshoot the simplest issues.


That kind of sounds like a lot of code bases I have worked in. Once you get some many people on a project things can get messy. Perhaps its a sign they have some DX and tech debt they should address.

As for why the latest version of c++? Personally I have found the dx improvements of some of them to be worth the upgrade. I really like constexpr and the build time errors it gives (opposed to runtime). The newer standards let you use constexpr in more places with more stl types.


As a C++ dev in Germany, experienced this firsthand. They make you do some truly ridiculous tests, and in the end 130k isn't that far off from somewhere like Maxon where you actually do interesting work. Having spoken to others who applied, both in person and via easily found reddit thread on r/cpp, 100% certain I would have been absolutely miserable there, and everyone mentions the CTO.


>100% certain I would have been absolutely miserable there, and everyone mentions the CTO

But..but.. think-cell says that:

"Our CTO Arno maintains a close working relationship with all of our developers. Together, they review code, troubleshoot, and brainstorm. Most likely due to Arno's and Markus' academic roots, think-cell is flourishing without program managers and lengthy meetings."

Those are all the traits of a good manager and pleasant work environment. And you get to work in Berlin!


> flourishing without program managers and lengthy meetings

€130k for a good C++ dev is a steal in most markets, but I can see this 'perk' as a huge draw for people tired of shops where "bringing key stakeholders into conversations at the appropriate time" is valued over simply building product.


>€130k for a good C++ dev is a steal in most markets,

In most markets? Definitely not.


> €130k for a good C++ dev is a steal in most markets (...)

To reign you back to reality, in Europe not even FANGs pay that kind of salaries. They only pay that kind of cash to senior and staff-level engineers.


130k amounts to 65 EUR/hr if you're working as a contractor. This is a low rate for an experienced and domain expert meaning that people with qualifications that think-cell is asking for already earn easily almost double than what they offer. To earn 130k as a non-contractor, you wouldn't have to look for FAANG (which do pay a lot more than that with their TC scheme) - there's plenty of companies that are willing to offer that amount of money for the right skills they need.


> 130k amounts to 65 EUR/hr if you're working as a contractor.

As a contractor, you have B2B contracts which means you have to pay taxes over your revenue. In general this amounts to a tax rate in a range of 20%-30%.

> This is a low rate for an experienced (...)

I repeat: in Europe, FANG salaries only start to come close to these values if you are principal/staff engineer. Your "experienced" weasel word contrasts with reality.


> As a contractor, you have B2B contracts which means you have to pay taxes over your revenue. In general this amounts to a tax rate in a range of 20%-30%.

No shit Sherlock. You have to pay the taxes as a regular employee too. Go figure.

As a matter of fact, 130k with B2B contract actually earns you more net profit then the 130k of a regular employee contract.

> I repeat: in Europe, FANG salaries only start to come close to these values if you are principal/staff engineer.

Again, you're talking nonsense. Go check levels.fyi if you don't believe me.

From my personal experience be let assured that earning 130k in Europe is very much possible, and without being a staff/principal engineer, and without working for the FAANG. You need to be decently experienced and know how to make your shit done.


> No shit Sherlock. You have to pay the taxes as a regular employee too. Go figure.

Yes, go figure that 130k in B2B contract is largely equivalent to 70-80k as a full-time employment.

Which is the whole point.

> As a matter of fact, 130k with B2B contract actually earns you more net profit then the 130k of a regular employee contract.

It really doesn't, unless you're doing dodgy taxes. Tax rates for salaried employees only surpass VAT and other taxes charged to self-employed professionals when you reached the highest income brackets.

Also, as a self-employed professional you are forced to pay your own contributions to social security, which for salaried employees are paid by the employers.

Do your math.


Truly, it's far from reassuring that the rest of think-cell's developer blog is more about "modern C++" debates on style and pitfalls ("Should we stop declaring functions and use global constexpr lambdas instead? Consider argument-dependent lookup...") than about developing their product.


Oh, that's just a side effect of the way the work is distributed :D I'm hired to maintain our core libraries and general C++ stuff, and I write most of the blog articles, so it's about the problems I face in my everday work.

We should blog more about the other stuff. For example, we've implemented a pretty cool lossless compression scheme for the preview of powerpoint slides in search, and have acquiried a cool technology to search for existing slides by drawing a sketch of their layout. But the people working on that are too busy to blog about them...


This is the description of at least 50% of the C++ projects where I was involved with. Only missing the language lawyers citing paragraphs of the standard at least once a meeting.


Oh how I miss the language lawyers in C++. Some really elevated it to a high calling. </s>


They got obsoleted by clang-tidy.


The power of linters to reign in the bike shedding and pointless nitpicking in pull requests should be mentioned more often. I recall that once I had the misfortune to have a junior developer in a team who was highly ambitious but low-skilled , and their way to claim high standards and attention to detail was to fill everyone's PRs with remarks bickering over whitespaces while citing his personal style guide that the guy was keeping in a personal Google Docs document.

That nonsense stopped the very moment I onboarded clang-format, but the guy unexplainably refused to run the tool on personal PRs. Insane.


The yelling CTO and the Glassdor reviews are certainly a red flag for potential employees.

But the company itself is interesting.

They built an add-on for PowerPoint for which they charge more than MS charges for the entire Office Suite (my company pays around €130 per year and user).

With a million users served by approximately 20 devs they are very profitable, which they highlight in their job ads.

As a small company with a relatively boring product, they need to do something to attract new people. And they can afford to approach every c++ programmer individually, support community efforts (standards committee) and sending people to Aspen for a conference. They might be a little clumsy so their recruiting comes across as spam.

As for latest c++ features not being appropriate: as a user, I don’t care as long as the product works as intended, which it has been doing for me since over 10 years now.


It does make "C with classes" sound pretty appealing.

Well, maybe also with some very limited template facilities.

Shoot... Did I just describe Ada95 somewhat?


Finally somebody on HN getting attention for mentioning this sad truth.

Usually even the least criticism on this 'holy' language [that emphasizes 'intent' and glorifies people who truly understand 'const correctness!' and the correct usage of dynamic_cast] gets immediately downvoted.

The sad truth being, tons of impossible to understand code bases exist in the wild, due to, for example, the mix of old, less old and new memory handling strategies having been applied.


> Google does not use std::ranges for many reasons, including performance, the ability to create dangling references, and a cubic stack blowup when deeply nesting some range adapters like std::views::filter

I remember discussing this exact problem for boost range in the boost mailing list almost 20 years ago. My proposal was to make ranges be their own thing and generate the iterators on the fly when begin/end was requested, instead of always storing them internally. But I think it was not considered a big issue in practice.

Today we have sentinel end-iterators that can be stateless, so it should be possible to avoid blowout without fundamentally rethinking ranges.


My, admittedly, not so big experience with std::ranges is that they generate an abnormal binary bloat in comparison to the traditional code, so, I also tried to argue against their usage in one my ex-teams who, paradoxically, wanted to achieve tight execution on platforms with tight resources.

Another paradox was that the exceptions were disabled because of reasons while at the same time std::ranges (range-v3) implementation did not support this. I wonder how Google solved this problem since AFAIK they also disallow exceptions.


> cubic stack blowup when deeply nesting some range adapters like std::views::filter

I have no doubt this observation is true, but I haven't been able to picture it, can somebody explain? I don't think ELI5 (let alone an explanation suitable for a Golden Retriever) will work here, but in terms that say a C programmer would grasp. What exactly gets shoved onto the stack when we're using a std::views::filter ?



Thanks but I saw the numbers, however to me the numbers just say "It's terrible" and leave me with the same question I had before, what's actually on the stack ? It's not going to just be empty space left there as a joke, are there... cached values? Pointers to something (what?). I suspect it's supposed to be obvious but I don't get it.


My "Eric's Famous Pythagorean Triples" blog post is 5+ years old and possibly not addressing exactly the same issue as think-cell/Google cares about, but, you might still be interested to read it: https://quuxplusone.github.io/blog/2019/03/06/pythagorean-tr...

In my case, the blowup happens mainly because the Ranges style tends to turn easily fungible code like `if x < y` into relatively non-fungible data like the capture-list of `[y]() { return x < y; }`. The former is easily subjected to optimizations like the hoisting of loop invariants; the latter is not. Another way to put this is: The compiler is happy to mess with the layout of the function stack frame up until the very last minute; and to mess with the coroutine frame almost as long; but the layout of a lambda (or std::bind) capture frame is set-in-stone very early, by the front-end. And Ranges loves lambdas.


According to https://en.cppreference.com/w/cpp/ranges/filter_view return value of views::filter is either a new ranges::view or some sort of range adaptor that seems to be equivalent to a ranges::filter_view.

I can only guess that it is those return values that are temporarily put on stack so that the consequent | operations could be chained. My hypothesis, without knowing much about this stuff, is that since the compiler cannot elide them (according to the godbolt link from the presentation), stack usage, as found by Google, has cubic growth. I don't quite understand why since I guess it's expected to grow linearly, e.g. 48 bytes at a time.


The naive implementation of range would have a range represented of a pairs of iterators, with every iterator containing a copy of each upstream range, which blows up quadratically; libstdc++ implementation is not implemented this way though: each range has one copy of the upstream range plus the predicate, while this could be optimized further for stateless predicates, the range growth should still be linear.

Yet, when I print the final size of the chained filter ranges and I saw it increasing superlinearly with the number of stages, so I must be missing something.

There is also additional wasted stack space for each temporary range. In theory the compiler should be able to optimize them away, but it probably doesn't always.



Since C++ ranges kinda look like Rust iterators, but I believe Rust doesn't have this problem, can somebody explain why? What's the difference? Or maybe it also has that problem?


Rust's iterators are single-use streams of items, and nothing more.

A Rust iterator can be fully implemented by a closure that either returns the next element or signals it's done. This minimalist interface composes well, especially that it always uses single ownership, so there's always only one copy of the state accessed only from one place.

They can't describe collections. They don't remember their beginning. They don't need to know their end until they reach it. They can't be sorted. They don't need to support splitting or iterating in reverse.


hm, so I tried this on the playground and it seems like Rust iterators also scale super-linearly in debug mode (which is expected, because every adaptor creates a new local that contains the previous one plus something extra), but this seems to go away in release mode (as everything is inlined). Is the problem in C++ something different?


it's exactly the same behaviour in C++. but people are used to this behaviour in Rust while in C++ they're used to writing traditional for-loops which do not have such an overhead at -O0 over -O3


I expect (but I'm not sure) it's because Rust iterators are a deliberately thinner, less powerful idea. Suppose I give you a Rust iterator A and a C++ range B.

You can ask A for the next() item and get back an Option<Item>, this literally iterates as an inherent part of getting your answer, there's no current() or previous() and there's no separate finished() if you ran out of items you got None instead of Some(item). A can be asked for a hint about how much is left, but it's allowed to say it has no useful idea: between zero and more than you can count, inclusive.

B is expected to provide a richer, albeit unsafe, API.

I believe somehow this means it's only reasonable for B to cache answers, as it can be asked to give the same answer again and it'd be inefficient to re-calculate it, whereas there is no way to ask A to repeat an answer so it makes no sense for it to cache (obviously there are special purpose Rust iterators which can have any feature, but the Iterator trait itself doesn't need to cache).


Did their 4-9 hour(!) mandatory take home coding test. Never done such a terrible test in my life; in every way they made a test akin to basically locking a c++ dev in a room for anywhere between 4-9 hours and afterwards rolling a die.

Its underspecified, but then has automated tests, that, after failing twice, automatically reject you as a candidate. Of course it doesnt give examples or example test cases or real test cases, so you're shit outta luck trying to solve it. The challenge itself would be fun and solvable in 4 hours, but around the 2 hour mark you start having questions and from there its just bad.

You can google this coding test ("think-cell interval_map") and youll get a lot more info.

I complained to them just in case they cared, and they did seem to care, but failing to specify an automatically tested coding challenge is the kind of psycho shit you do if you think youre the smartest guy in the room (but arent).

ETA: To be clear, I stopped the second I realized it was an underspecified test. I'm not that mad, just mad I fell for spending a whole saturday morning doing it, without ever talking to anyone there in a call or anything.

That and the fact that theyre just building a powerpoint plugin and seem to overcomplicate it to such a massive degree: Yeah no thanks.


Rappel sounds very similar to Clojure’s transducers in spirit and implementation.


Regarding the addition to libxx: I’m not fond of adding an increasing number of specific compiler options which need to be turned on for memory-safety. It should be default whenever possible. What do you think?

I love -faddress=sanitizer or -fsanitize. The historically growing number of warnings which should be turned on are an issue. For example the options -Wconversion, -Wsign-conversion and -Warith-conversion shall be default with C++XX. And if your code doesn’t compile, fix it, use and older revision or turn it deliberately off (saying: I’m aware, read the handbook, I take the risk). Doing such changes with language revisions allows people to take notice about it without surprise.

I want some ideas of CPP2/cppfront[1] in C++XX. Finally using #unsafe when needed, like Rust. C++ does evolve over decades, more like other languages.

[1] https://github.com/hsutter/cppfront

BTW. Use the AddressSanitizer. Please! The toolchain improved the usage and safety of the language so much. It wasn’t available twenty years ago but it is now.


I think what you're looking for are profiles.

- https://github.com/BjarneStroustrup/profiles

Ie. rather than a bunch of tools helping you find undefined behaviour (or left-and-right improvements of what the behaviour should be) you'd like to be able to make high level claims about your code and have the compiler validate those guarantees.

C++ having a huge C++ legacy, things of course are never easy. So for the time being this is just work-in-progress.


First of all they have to be designed and voted in.

If WG21 is actually open to having them, ISO C++29 is probably when they might land, then given the compiler velocity with previous standards, expect at least 5 years to mature, on the top three, let alone everyone else.

If I am not dead by then, I will certainly be retired.

How long will the industry be willing to wait for profiles?


> BTW. Use the AddressSanitizer. Please! The toolchain improved the usage and safety of the language so much.

In general I agree with the sentiment, use AddressSanitizer in testing/debugging. However it's not meant to be a hardening option, AFAIK, so I advise against using it in production (along with other sanitizers), even if you can live with the performance hit.


Yep.

Theoretically it is possible but libasan is not intended for linking or shipping in production. Also stuff like LeakSanitizer[1] actually cannot [1] be used with GDB.

While shipping debug symbols is something I recommend and has no side-effects aside from mere file-size (debug symbols are only loaded when used).

Usual exceptions apply as you encounter them.

[1] https://github.com/google/sanitizers/issues/857


I think the compilers would be allowed to just make your default warning changes as non-fatal diagnostics aren't a conformance matter. So you probably need to lobby GCC, Clang and MSVC.

Herb's discussion of "unsafe" for Cpp2 is... misleading. Rust's unsafe does not turn off checking. If you take some safe Rust which compiles (even if it panics) and you just wrap that in a block with the unsafe keyword, all you get for your trouble is a new diagnostic (by default a warning) saying not to do that because it's futile to use an unsafe block here. No checks are switched off, no "performance" shortcuts are added, if it would panic before it still does now - it's just the same code with a frivolous "unsafe" sign on it.


Swift does this, but their release cycle is currently more frequent and the roadmap is (possibly) more clear. Paraphrasing:

    Warning: <Certain undesirable behavior>
    will be an error in Swift <Version
    + 1>.
I’m not sure if it’s an huge improvement, but it’s easy to block all forward warnings with `-Werror` to clean up right away.


both c++ and Rust emit these kind of warnings to deprecate previously allowed constructs that are deemed undesirable, but I don’t follow how it relates to this particular topic


I created a similar sum type during high school detention but type order didn't seem possible at the time. I'm glad someone else has eyes on it.

I unfortunately don't write C++ anymore because the TC is too low. It's sad that Python and JS pay more.


Think cell is the company the creator of lexy works at


If you mean https://github.com/foonathan/lexy , then yes, foonathan (Jonathan Müller) is literally the "I" in the first sentence of TFA.


oh indeed, I had not scrolled enough!


Yes, but sadly I have to use Boost.Spirit at work :(




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: