Top
Best
New

Posted by volatileint 6 days ago

How well do you know C++ auto type deduction?(www.volatileint.dev)
86 points | 131 commentspage 2
gpderetta 23 hours ago|
One of the craziest bugs I have had in C++ was due to auto, and it would only trigger when Trump would announce tariffs. It would have been completely preventable if I had paid full attention to my IDE feedback or the correct compiler flags were set.
ajoseps 3 hours ago||
this sounds hilarious, please do tell. Does it relate to volatility spikes when tariffs are announced and some unexpected overflow?
am17an 17 hours ago||
This has got to be a hell of a story if it's true.
andrepd 22 hours ago||
Things like this are why I don't get when anyone calls Rust "more difficult than C++". I don't think I've ever encountered a more complex and cumbersome language than C++.
nurettin 20 hours ago|
Rust types can be very verbose, RefCell<Optional<Rc<Vec<Node>>>>

And lifetime specifiers can be jarring. You have to think about how the function will be used at the declaration site. For example usually a function which takes two string views require different lifetimes, but maybe at call site you would only need one. It is just more verbose.

C++ has a host of complexities that come with header/source splits, janky stdlib improvements like lock_guard vs scoped_lock, quirky old syntax like virtual = 0, a lack of build systems and package managers.

andrepd 18 hours ago||
> Rust types can be very verbose, RefCell<Optional<Rc<Vec<Node>>>>

Anything in any language can be very verbose and confusing if you one-line it or obfuscate it or otherwise write it in a deliberately confusing manner. That's not a meaningful point imo. What you have to do is compare what idiomatic code looks like between the two languages.

C++ has dozens of pages of dense standardese to specify how to initialise an object, full with such text as

> Only (possibly cv-qualified) non-POD class types (or arrays thereof) with automatic storage duration were considered to be default-initialized when no initializer is used. Each direct non-variant non-static data member M of T has a default member initializer or, if M is of class type X (or array thereof), X is const-default-constructible, if T is a union with at least one non-static data member, exactly one variant member has a default member initializer, if T is not a union, for each anonymous union member with at least one non-static data member (if any), exactly one non-static data member has a default member initializer, and each potentially constructed base class of T is const-default-constructible.

For me, it's all about inherent complexity vs incidental complexity. The having to pay attention to lifetimes is just Rust making explicit the inherent complexity of managing values and pointers thereof while making sure there isn't concurrent mutation, values moving while pointers to them exist, and no data races. This is just tough in itself. The aforementioned C++ example is just the language being byzantine and giving you 10,000 footguns when you just want to initialise a class.

1718627440 17 hours ago||
> > Only (possibly cv-qualified) non-POD class types (or arrays thereof) with automatic storage duration were considered to be default-initialized when no initializer is used. Each direct non-variant non-static data member M of T has a default member initializer or, if M is of class type X (or array thereof), X is const-default-constructible, if T is a union with at least one non-static data member, exactly one variant member has a default member initializer, if T is not a union, for each anonymous union member with at least one non-static data member (if any), exactly one non-static data member has a default member initializer, and each potentially constructed base class of T is const-default-constructible.

That's just a list of very simple rules for each kind of type. As a C++-phob person, C++ has a lot of footguns, but this isn't one of them.

andrepd 13 hours ago||
This was a random snippet of complicated rules to illustrate what I mean. C++ has significant footguns and complexity when it comes to initialising an object. Even sticking by "Effective Modern C++" rules is a significant cognitive burden on something that should be simple and straightforward. Rust has (mostly) no such footguns; its complexity follows from the essential complexity of the problems at hand.
jpc0 1 hour ago|||
Rust prevents the footgun, but also prevents shooting in the direction where your foot would be even if it isn't there.

There are absolutely places where that is required and in Rust those situations become voodoo to write.

C++ be default has more complexity but has the same complexity regardless of domain.

Rust by default has much less complexity, but in obscure situations outside of the beaten path the complexity dramatically ramps up far above C++.

This is not an argument for or against either language, it's a compromise on language design, you can choose to dislike the compromise but that doesn't mean it was the wrong one, it just means you don't like it.

A relatively simple but complex example, I want variable X to be loaded into a registerer in thos function and only written to memory at the end of the function.

That is complex in C/C++ but you can look at decompilation and attempt to coerce the compiler into that.

In rust everything is so abstracted I wouldn't know where to begin looking to coerce the compiler into generating that machine code and might just decide to implement it in ASM, which defeats the point of using a high level language.

Granted you might go the FFMPEG route ans just choose to do that regardless but rust makes it much harder.

You don't always need that level of control but when you do it seems absurdly complex.

1718627440 12 hours ago|||
I see. But including a snippet that actually supports your claim would have been more useful.
antonvs 1 day ago||
“How well do you know Latin grammar rules?”
wheybags 22 hours ago|
Romanes eunt domus
CalChris 1 day ago||
Apparently the C++ standard calls this type deduction. But I've always called it type inference.
Maxatar 1 day ago||
Type deduction is a form of type inference, a very restricted/crude form of type inference that only considers the type of the immediate expression. The term is used in C++ because it predates the use of auto and was the term used to determine how to implicitly instantiate templates. auto uses exactly the same rules (with 1 single exception) as template type deduction, so the name was kept for familiarity. If instead of initializing a variable, you went through the examples on this website and passed the expression into a template function, the type would be deduced in exactly the same way with the exception of initializer lists.

Type inference is usually reserved for more general algorithms that can inspect not only how a variable is initialized, but how the variable used, such as what functions it's passed into, etc...

zarzavat 1 day ago||
> Type inference is usually reserved for more general algorithms that can inspect not only how a variable is initialized, but how the variable used, such as what functions it's passed into, etc...

In a modern context, both would be called "type inference" because unidirectional type inference is quite a bit more common now than the bidirectional kind, given that many major languages adopted it.

If you want to specify constraint-based type inference then you can say global HM (e.g. Haskell), local HM (e.g. Rust), or just bidirectional type inference.

plq 1 day ago|||
Type inference is when you try to infer a type from its usage. ``auto`` does no such thing, it just copies a known type from source to target. Target has no influence over source's type.

https://news.ycombinator.com/item?id=11604474

gpderetta 23 hours ago|||
Inference is a constraint solving problem: the type of a variable depends on all the ways it is used. In C++, deduction simply sets the type of a variable from its initializing expression.

95%[1] of the time, deduction is enough, but occasionally you really wish you had proper inference.

[1] percentage made up on the spot.

jb1991 1 day ago||
The same general concepts often have different terminology between different languages. For example, which is better, a parent class or a super class? Or a method function or a member function? Initializer or constructor? Long list of these synonyms.
112233 1 day ago|
Saving this to use as an argument when C++ comes up in a discussion. This toxic swamp cannot be fixed and anyone chosing to jump into it needs a warning.
OneDeuxTriSeiGo 1 day ago|
Most of the relevance of this is limited to C++ library authors doing metaprogramming.

Most of the "ugly" of these examples only really matters for library authors and even then most of the time you'd be hard pressed to put yourself in these situations. Otherwise it "just works".

Basically any adherence to a modicum of best practices avoids the bulk of the warts that come with type deduction or at worst reduces them to a compile error.

112233 1 day ago||
I see this argument often. It is valid right until you get first multipage error message from a code that uses stl (which is all c++ code, because it is impossible to use c++ without standard library).
OneDeuxTriSeiGo 20 hours ago|||
Those aren't the ugly part of C++ and to be entirely honest reading those messages is not actually hard, it's just a lot of information.

Those errors are essentially the compiler telling you in order:

1. I tried to do this thing and I could not make it work.

2. Here's everything I tried in order and how each attempt failed.

If you read error messages from the top they make way more sense and if reading just the top line error doesn't tell you what's wrong, then reading through the list of resolution/type substitution failures will be insightful. In most cases the first few things it attempted will give you a pretty good idea of what the compiler was trying to do and why it failed.

If the resolution failures are a particularly long list, just ctrl-f/grep to the thing you expected to resolve/type-substitute and the compiler will tell you exactly why the thing you wanted it to use didn't work.

They aren't perfect error messages and the debugging experience of C++ metaprogramming leaves a lot to be desired but it is an order of magnitude better than it ever has been in the past and I'd still take C++ wall-o-error over the extremely over-reductive and limited errors that a lot of compilers in other languages emit (looking at you Java).

112233 17 hours ago|||
Most of the time it's as uou say!

But "I tried to do this thing" error is completely useless in helping to find the reason why the compiler didn't do the thing it was expected to do, but instead chose to ignore.

Say, you hit ambiguous overload resolution, and have no idea what actually caused it. Or, conversely, implicit conversion gets hidden, and it helpfully prints all 999 operator << overloads. Or there is a bug in consteval bool type predicate, requires clause fails, and compiler helpfully dumps list of functions that have differet arguments.

How do you debug consteval, if you cannot put printf in it?

Not everyone can use clang or even latest gcc in their project, or work in a familiar codebase.

ux266478 18 hours ago|||
To be fair to C++, the only languages with actually decently debuggable metaprograms are Lisp and Prolog.

Modern C++ in general is so hostile to debugging I think it's astounding people actually use it.

wheybags 22 hours ago||||
And I see this argument often. People make too much fuss about the massive error messages. Just ignore everything but the first 10 lines and 99.9% of the time, the issue is obvious. People really exaggerate the amout of time and effort you spend dealing with these error messages. They look dramatic so they're very memeable, but it's really not a big deal. The percentage of hours I've spent deciphering difficult cpp error messages in my career is a rounding error.
112233 17 hours ago||
Do you also consider that knowing type deduction is not necessary to fix those errors, unless you are writing a library? Because that is not my experience (c++ "career" can involve such wildly different codebases, it's hard to imagine what others must be dealing with)
1718627440 22 hours ago|||
> it is impossible to use c++ without standard library

Citation needed. This is common for embedded application, since why would anyone program a STL for that?

OneDeuxTriSeiGo 20 hours ago|||
There's actually multiple standard libraries for embedded applications and a lot of the standard library from C++11 and on was designed with embedded in mind in particular.

And with each std release the particularly nasty parts of std get decoupled from the rest of the library. So it's at the point nowadays where you can use all the commonly used parts of std in an embedded environment. So that means you get all your containers, iterators, ranges, views, smart/RAII pointers, smart/RAII concurrency primitives. And on the bleeding edge you can even get coroutines, generators, green threads, etc in an embedded environment with "pay for what you use" overhead. Intel has been pushing embedded stdlib really hard over the past few years and both they and Nvidia have been spearheading the senders and receivers concurrency effort. Intel uses S&R internally for handling concurrency in their embedded environments internal to the CPU and elsewhere in their hardware.

(Also fun side note but STL doesn't "really" stand for "standard template library". Some projects have retroactively decided to refer to it as that but that's not where the term STL comes from. STL stands for the Adobe "Software Technology Lab" where Stepanov's STL project was formed and the project prior to being proposed to committee was named after the lab.)

gpderetta 17 hours ago||
AFAIK Stepanov only joined Adobe much later. I think he was at HP during the development of the STL, but moved to SGI shortly after (possibly during standardization).

The other apocryphal derivation of STL I have heard is "STepanov and Lee".

112233 19 hours ago|||
gladly! Since deep-linking draft pdf from phone is hard, here is the next best thing: https://en.cppreference.com/w/cpp/freestanding.html

freestanding requires almost all std library. Please note that -fno-rtti and -fno-exceptions are non-conformant, c++ standard does not permit either.

Also, such std:: members as initializer_list, type_info etc are directly baked into compiler and stuff in header must exactly match internals — making std library a part of compiler implementation

gpderetta 15 hours ago|||
> freestanding requires almost all std library.

have you actually read the page you linked to? None of the standard containers is there, nor <iostream> or <algorithm>. <string> is there but marked as partial.

If anything, I would expect more headers like <algorithm>, <span>, <array> etc to be there as they mostly do not require any heap allocation nor exceptions for most of their functionality. And in fact they are available with GCC.

The only bit I'm surprised is that coroutine is there, as they normally allocate, but I guess it has full support for custom allocators, so it can be made to work on freestanding.

1718627440 18 hours ago|||
> Please note that -fno-rtti and -fno-exceptions are non-conformant, c++ standard does not permit either.

I did not know that.

My understanding was that C does not require standard library functions to be present in freestanding. The Linux kernel famously does not build in freestanding mode, since then GCC can't reason about the standard library functions which they want. This means that they need to implement stuff like memcpy and pass -fno-builtin.

Does that mean that freestanding C++ requires the C++ standard library, but not the C standard library? How does that work?

112233 17 hours ago||
Honestly? No idea how the committee is thinking. When, say, gamedev people write proposal, ask for a feature, explain it is important and something they depend on and so on, it gets shot down on technicality. Then they turn around and produce some insane feature that, like, rips everything east to west (like modules), and suddenly voting goes positive.

The "abstract machine" C++ assumes in the standard is itself a deeply puzzling construct. Luckily, compiler authors seem much more pragmatic and reasonable, I do not fear -fno-exceptions dissapearing suddenly, or code that accesses mmapped data becoming invalid because it didn't use start_lifetime_as

1718627440 17 hours ago||
So as to your understanding

> freestanding C++ requires the C++ standard library, but not the C standard library

is true?

> The "abstract machine" C++ assumes in the standard is itself a deeply puzzling construct.

I find the abstract machine to be quite a neat abstraction, but I am also more of a C guy.

112233 14 hours ago||
Looking here:

https://eel.is/c++draft/compliance

One of required headers in freestanding, <cstdlib>, is labelled "C standard library", but it is not <stdlib.h> Something similar with other <csomething> headers.

This kinda implies C library is required, if I read it correctly, but maybe someone else can correct me: https://eel.is/c++draft/library.c

1718627440 13 hours ago||
So from the GCC 10 documentation:

> The ISO C standard defines (in clause 4) two classes of conforming implementation. A "conforming hosted implementation" supports the whole standard including all the library facilities; a "conforming freestanding implementation" is only required to provide certain library facilities: those in '<float.h>', '<limits.h>', '<stdarg.h>', and '<stddef.h>'; since AMD1, also those in '<iso646.h>'; since C99, also those in '<stdbool.h>' and '<stdint.h>'; and since C11, also those in '<stdalign.h>' and '<stdnoreturn.h>'. In addition, complex types, added in C99, are not required for freestanding implementations.

> The standard also defines two environments for programs, a "freestanding environment", required of all implementations and which may not have library facilities beyond those required of freestanding implementations, where the handling of program startup and termination are implementation-defined; and a "hosted environment", which is not required, in which all the library facilities are provided and startup is through a function 'int main (void)' or 'int main (int, char *[])'. An OS kernel is an example of a program running in a freestanding environment; a program using the facilities of an operating system is an example of a program running in a hosted environment.

> GCC aims towards being usable as a conforming freestanding implementation, or as the compiler for a conforming hosted implementation. By default, it acts as the compiler for a hosted implementation, defining '__STDC_HOSTED__' as '1' and presuming that when the names of ISO C functions are used, they have the semantics defined in the standard. To make it act as a conforming freestanding implementation for a freestanding environment, use the option '-ffreestanding'; it then defines '__STDC_HOSTED__' to '0' and does not make assumptions about the meanings of function names from the standard library, with exceptions noted below. To build an OS kernel, you may well still need to make your own arrangements for linking and startup. *Note Options Controlling C Dialect: C Dialect Options.

> GCC does not provide the library facilities required only of hosted implementations, nor yet all the facilities required by C99 of freestanding implementations on all platforms. To use the facilities of a hosted environment, you need to find them elsewhere (for example, in the GNU C library). *Note Standard Libraries: Standard Libraries.

> Most of the compiler support routines used by GCC are present in 'libgcc', but there are a few exceptions. GCC requires the freestanding environment provide 'memcpy', 'memmove', 'memset' and 'memcmp'. Finally, if '__builtin_trap' is used, and the target does not implement the 'trap' pattern, then GCC emits a call to 'abort'.

So the last paragraph means that my remark about the Linux kernel might be wrong.

So the required headers are all about basic constants for types, the types themselves (bool), and basic language features like stdarg, iso646 or stdalign. Sounds sensible to me. Not sure what C++ does with that.

This matches with https://en.cppreference.com/w/c/language/conformance.html . Since C23, stdlib.h is also required for dynamic allocation, string conversions and some other things.

This also actually matches the links provided by you. In https://eel.is/c++draft/cstdlib.syn you see that not all declarations are actually marked for freestanding implementations.