Top
Best
New

Posted by volatileint 6 days ago

How well do you know C++ auto type deduction?(www.volatileint.dev)
86 points | 131 comments
am17an 11 hours ago|
Usually codebases disallow auto because without an IDE it's difficult to see the type. I think this reduces the cognitive load of C++ a bit. The only time it is allowed is getting iterators types from STL containers.

I remember fretting about these rules when reading Scott Meyer's Effective C++11, and then later to realize it's better not to use auto at all. Explicit types are good types

guenthert 10 hours ago||
Hard disagree. There are times when nobody, really nobody, cares about a given type. See the example of std::chrono::steady_clock::now over at cppreference.com. There you have

        const auto start = std::chrono::steady_clock::now();
        do_some_work(size);
        const auto end = std::chrono::steady_clock::now();
        const std::chrono::duration<double> diff = end - start;

        std::cout << "diff = " << diff << "; size = " << size << '\n';
Looking up the (current standard's) return type of std::chrono::steady_clock::now() and spelling it out would serve no purpose here.
cjfd 10 hours ago|||
With 'auto' it is so very verbose. It can be shorter. Let us put "using TP = std::chrono::steady_clock::time_point;" in some header file to be used in many places. Now you can write

  TP start = TP::clock::now();
  do_some_work(size);
  TP end = TP::clock::now();
moregrist 10 hours ago|||
I prefer to put the `using` in the block where you need the std::chrono code, which keeps it local and tidy. Putting it in a header is declaring a global type and asking for trouble; at least bound it in a namespace or a class.
gpderetta 9 hours ago||||
how is TP more descriptive than auto here?
UncleMeat 10 hours ago|||
Some organizations don't like putting using declarations in headers since now you've got a global uniqueness requirement for the name "TP."
dosshell 5 hours ago||
You put the using as class member (private) or as local in the function.
am17an 9 hours ago|||
I agree, this would be in the same vein as "STL returns a verbose type, it's okay to use auto here because no-one cares"
moregrist 10 hours ago|||
> Usually codebases disallow auto because without an IDE it's difficult to see the type. I think this reduces the cognitive load of C++ a bit. The only time it is allowed is getting iterators types from STL containers.

Strong agree here. It's not just because it reduces cognitive load, it's because explicit types allows and requires the compiler to check your work.

Even if this isn't a problem when the code is first written, it's a nice safety belt for when someone does a refactor 6-12 months (or even 5+ years) down the road that changes a type. With auto, in the best case you might end up with 100+ lines of unintelligible error messages. In the worst case the compiler just trudges on and you have some subtle semantic breakage that takes weeks or months to chase down.

The only exceptions I like are iterators (whose types are a pita in C++), and lambda types, where you sometimes don't have any other good options because you can't afford the dynamic dispatch of std::function.

112233 7 hours ago|||
There are unspeakble types, such as lambdas, that are not exactly easy to handle without "auto". How a variable containing local lambda with captures will look without "auto"?
dosshell 5 hours ago||
If it captures variables, it is not possible to manually declare the type. You can however hide it in an std::function. But then you probably get some overhead and some heap allocations in real life.
maxlybbert 10 hours ago|||
I know Google’s styleguide discourages auto, but other C++ places I’ve worked weren’t scared of it. The type deduction rules usually do what I expect, unless I have a weird pre-C++11 proxy type that somehow hasn’t been updated in the last 14 years.
cjfd 10 hours ago||
I agree that auto should be used as little as possible. There are good uses, though. It is okay to use when the type is trivially inferred from the code. What is auto in "auto ptr = std::make_shared<MyNiceType>();". Everybody who knows any C++ knows. Also, lambdas do not have a type that can be written down, so it is okay to use auto for them.

I also prefer not to use auto when getting iterators from STL containers. Often I use a typedef for most STL containers that I use. The one can write MyNiceContainerType::iterator.

spot5010 10 hours ago||
Pre LLM agents, a trick that I used was to type in

auto var = FunctionCall(...);

Then, in the IDE, hover over auto to show what the actual type is, and then replace auto with that type. Useful when the type is complicated, or is in some nested namespace.

connicpu 10 hours ago||
That's what I still do. Replacing auto with deduced type is one of my favorite clangd code actions.
dataflow 19 hours ago||
This makes things seem more complicated than they already are, I feel?

There's nothing special about auto here. It deduces the same type as a template parameter, with the same collapsing rules.

decltype(auto) is a different beast and it's much more confusing. It means, more or less, "preserve the type of the expression, unless given a simple identifier, in which case use the type of the identifier."

Surac 19 hours ago||
I always try to avoid auto in c++ or var in C#. On the paper it is a nice was to save you from typing out the type but this is only true if you have tool support and can use your mouse to obtain the type. In printouts or even in a text snippet I’m am lost. I think auto inC++ was the try to shorten some overburden type constructs and make it a little more friendly to use heavy templating. Please forgive my bad English. I’m no native speaker
xnorswap 16 hours ago||
The guidelines I follow for C# has "use var where it's obvious, but explicitly type when not".

So for example I'd write:

  var x = new List<Foo>();
Because writing:

  List<Foo> x = new List<Foo>();
Feels very redundant

Whereas I'd write:

  List<Foo> x = FooBarService.GetMyThings();
Because it's not obvious what the type is otherwise ( Some IDEs will overlay hint the type there though ).

Although with newer language features you can also write:

  List<Foo> x = new();
Which is even better.
tcfhgj 16 hours ago|||
I would rename `x` to `foos` and jump to the function/use IDE hints for the exact type when needed.
Leherenn 14 hours ago|||
Right, although I would argue the most interesting part of the type here is the container, not the containee.

With good naming it should be pretty obvious it's a Foo, and then either you know the type by heart, or will need to look up the definition anyway.

With standard containers, you can have the assumption that everyone knows the type, at least high level. So knowing whether it's a list, a vector, a stack, a map or a multimap, ... is pretty useful and avoid a lookup.

Aeglaecia 15 hours ago|||
an interesting demarcation of subjective mental encapsulation ... associating the anonymous type of a buffer with the buffer's name ... as opposed to explicitly specifying the type of an anonymously named buffer
lan321 11 hours ago|||
I usually prefer

    List<Foo> x = new();
since it gives me better alignment and since it's not confused with dynamic.

Nowadays I only use

    var x = new List<Foo>();
in non-merged code as a ghetto TODO if I'm considering base types/interface.
majoe 18 hours ago|||
I came to like auto over the years, although I also use it sparingly. Sometimes the concrete types only add visual noise and not much helpful information, e.g. iterators:

auto it = some_container.begin();

Not even once have I wished to know the actual type of the iterator.

binary132 11 hours ago|||
It’s extremely convenient for certain things. For example, let’s say I’m referring to an enum constant field of a deeply-nested type. This can easily be expressed as “auto k = a.b.c.d.kind” instead of “Alpha::Bet::Charlie::Delta::Kinds k = a.b.c.d.kind”. It should be used sparingly in local contexts where the meaning cannot be confusing.
pjmlp 16 hours ago|||
That is like saying that one rather make fire with sticks and stones than with a lighter, because otherwise one would be lost when going out camping.

IDEs are an invention from the late 1970's, early 1980's.

3836293648 40 minutes ago|||
You can rely on IDE features when GitHub's web view has them
1718627440 10 hours ago||||
I think IDEs are a moving target. Do you consider syntax highlighting to make something an IDE? Macro expansion? Autocomplete? intellisense-like based on full text search? based on a parser? based on the compiler? Is Kate an editor or an IDE? It has LSP support.

Having syntax highlighting makes me slightly faster, but I want to still be able to understand things, when looking at a diff or working over SSH and using cat.

pjmlp 9 hours ago||
Nope, that is a programmer's editor.
1718627440 6 hours ago||
I agree, but that means, that even complete integration of the compiler does not make an IDE. So the only distinguishing feature I can think of is startup time /s .
addaon 6 hours ago||
An IDE, by name, integrates the development environment. Depending on target, development lifecycle usually includes writing code, generating binaries, pushing those binaries to the target, executing on the target, and debugging on the target; so an IDE includes editing, compiling, linking, device/JTAG drivers, and debugging, in my mind. I suppose for platforms without cross-compilation the device drivers vanish, and for languages without compilers and linkers similar; but at a minimum, development means both authoring and debugging code, and so an IDE must provide an integrated view of the debugging process.
1718627440 5 hours ago||
Then that distinction doesn't match the original claim, that readability of unlabeled code doesn't matter, since we now have IDEs, as this is an orthogonal matter according to your definition.
gpderetta 16 hours ago|||
Very much true. On the other hand web based review interfaces seem to be stuck in the '60, when they could be so much better if they properly integrated with the compiler.
pjmlp 15 hours ago||
The ones on Github with VSCode (Web) integration look quite good already.
Rucadi 18 hours ago|||
auto has a good perk, it prevents uninitialized values (Which is a source of bugs).

For example:

auto a;

will always fail to compile not matter what flags.

int a;

is valid.

Also it prevents implicit type conversions, what you get as type on auto is the type you put at the right.

That's good.

feelamee 17 hours ago|||
uninitialized values are not the source of bugs. This is a good way to find logic errors in code (e.g. using sanitizer)
jb1991 6 hours ago||
"bug" can refer to many categories of problems, including logic errors. I've certainly seen uninitialized variables be a source of bugs. Stackoverflow for example is full of discussions about debugging problems caused by uninitialized variables, and the word "bug" is very often used in those contexts.

What do you mean it is not a source of bugs?

1718627440 5 hours ago|||
> What do you mean it is not a source of bugs?

I think what they mean, and what I also think is that the bug does not come from the existence of uninitialized variables. It comes from the USE of uninitialized variables. Making the variables initialized does not make the bug go away, at most it silences it. Making the program invalid instead (which is what UB fundamentally is) is way more helpful for making programs have less bugs. That the compiler still emits a program is a defect, although an unfixable one.

As to my knowledge C (and derivatives like C++) is the only common language where the question "Is this a program?" has false positives. It is certainly an interesting choice.

feelamee 4 hours ago|||
I mean that bug is *not in* uninitialized variable - bug in program logic. E.g. one of code pathes don't initialize variable.

So, I see uninitialized variables as a good way to find such logic errors. And, therefore, advice to always initialize variable - bad practice.

Of course if you already have a good value to initialize variable - do it. But if you have no - better leave it uninitialized.

Moreover - this will not cause safety issues in production builds because you can use `-ftrivial-auto-var-init` to initialize automatic variables to e.g. zeroes (`-fhardened` will do this too)

jb1991 18 hours ago|||
This is indeed exactly correct. Probably on its own this is the most important reason for most people to use it, as I think most of the millions of C++ developers in the world (and yes there are apparently millions) are not messing with compiler flags to get the checks that probably should be there by default anyway. The keyword auto gives you that.
amelius 17 hours ago|||
Maybe an in-between solution could be a tool that substitutes auto in your code.
samdoesnothing 18 hours ago||
Odd, it's not a problem in dynamically typed languages or languages like Kotlin, Swift, etc. I think it's more just what you're used to.
tigranbs 16 hours ago||
The last time I worked meaningfully with C++ was back in 2013. Now that I write mostly Rust and TypeScript, I'm amazed by how C++ has changed over the years!

Regarding the "auto" in C++, and technically in any language, it seems conceptually wrong. The ONLY use-case I can imagine is when the type name is long, and you don't want to type it manually, or the abstractions went beyond your control, which again I don't think is a scalable approach.

tialaramex 16 hours ago|
Type inference is actually very useful you just need to have the right amount, too little and most of your time is spent on bureaucracy, too much and the software is incomprehensible.

In both Rust and C++ we need this because we have unnameable types, so if their type can't be inferred (in C++ deduced) we can't use these types at all.

In both languages all the lambdas are unnameable and in Rust all the functions are too (C++ doesn't have a type for functions themselves only for function pointers and we can name a function pointer type in either language)

0xcafecafe 11 hours ago|||
Another place where auto can be useful is to handle cases where the function signature changes without making changes at the calling site. An explicitly typed var would need changing or worse, can work with some potential hidden bugs due to implicit type conversion.
1718627440 15 hours ago|||
> C++ doesn't have a type for functions themselves only for function pointers

C has this, so I think C++ has as well. You can use a typedef'ed function to declare a function, not just for the function pointer.

gpderetta 13 hours ago||
Same in C++. You can't do much with the function type itself as there are no objects with that type, but you can create references and pointers to it.
1718627440 12 hours ago||
But

    typedef void * (type) (void * args);

    type foo;

    a = foo (b);
works?
meindnoch 11 hours ago|||
Those are function pointers. Your parent was referring to the function type. Per ISO/IEC 9899:TC3:

A function type describes a function with specified return type. A function type is characterized by its return type and the number and types of its parameters. A function type is said to be derived from its return type, and if its return type is T , the function type is sometimes called ‘‘function returning T’’. The construction of a function type from a return type is called ‘‘function type derivation’’.

1718627440 11 hours ago||
Not necessarily, foo can also just be an ordinary function. That was my point.

> Per ISO/IEC 9899:TC3:

What is it supposed to tell me?

tialaramex 7 hours ago||
ISO/IEC 9899 is the name of the ISO document describing the C programming language. The current edition is, I think the 2023 document aka C23.

You can read a "draft" of that document here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf

[If you've ever been under the impression that "real" people use the actual ISO text, disabuse yourself of that notion, ISO takes ages to turn the same exact words into an official branded document, then charges $$$ for a PDF, ain't nobody got time or money for that]

I can't tell you what they intended by TC3. It might be a typo or it might be some way to refer to a specific draft or a section within that draft. I doubt this particular section changes frequently so I wouldn't worry about it.

gpderetta 5 hours ago|||
Technical Corrigendum 3. It was an amendment to the '99 standard that contains a few important clarifications.
tialaramex 2 hours ago||
OK? But like, why a version of C99? So neither C89, which I could understand as this idea that it has "always" been that way, but also not C23, which is the current standard.

It sounds like this text is essentially the same in C23, maybe moved around a bit.

1718627440 39 minutes ago||
> But like, why a version of C99?

Would also be my default goto version. Reasonable old to be supported everywhere, some quality of live improvements like initialization syntax, without all the modern fluff.

1718627440 6 hours ago|||
Thanks, I did know that. My question was what kind of claim that quote is supposed to support, not where the quote is from.
gpderetta 10 hours ago|||
This is true, in both C++ and C, you can use a function type to declare (but not define) a function! This pretty much never comes up and I forgot.

edit: you literally said this in your original comment. I failed at reading comprehension.

1718627440 9 hours ago||
> This pretty much never comes up

I regularly use that in C, to make sure a function matches an abstract interface. Sure, that often ends up in a function pointer, but not always and when I declare the type signature, it isn't yet a function pointer.

> but not define

I think that is because the type signature only contains the types, but no parameter names, which are required for a definition. This is arbitrary, since for data types, the member names are part of the type. It sounds totally fixable, but then you either have two types of function types, one where all parameter names are qualified and one where they aren't and only could use the former for function definitions. Or you would make names also mandatory for function declarations.

gpderetta 9 hours ago||
Interesting use case.

> It sounds totally fixable, but then you either have two types of function types, one where all parameter names are qualified and one where they aren't and only could use the former for function definitions

Making the names part of the type would be a bit weird, although we have seen stranger things. The biggest problem is that it would be a breaking change at least in C++.

1718627440 6 hours ago||
> The biggest problem is that it would be a breaking change at least in C++.

Exactly, I believe that to be the case in C as well. In C23 the rules for types to be considered compatible were actually relaxed, so this proposal wouldn't make any sense. It would be a useless breaking change for no gain, other than a bit less typing and maybe feeding some language layers, so there is really no reason to do that. It actually makes the code less clear, since you now need to always lookup the type definition, which would be against C's philosophy.

jandrewrogers 19 hours ago||
I knew the answer to most of these intuitively but the story isn’t great. Regardless of the programming language, I’ve always been an “auto” minimalist. There are relatively few contexts where relying on inference is justified by the expedience. Ignoring the issues raises by the article, explicitness simplifies things and reduces bugs.

That said, there are some contexts in which “auto” definitely improves the situation.

jb1991 18 hours ago|
You will typically find bugs go up when you do not use auto in most average C++ code bases because, as noted by another comment, a lot of C++ developers have the bad habit of writing uninitialized variables, which auto prevents.
1718627440 15 hours ago|||
As a C developer to me uninitialized variables are a feature and I love to invoke UB with __builtin_unreachable, because it makes the compiler able to actually bark at me on incorrect usage instead of it silently working.
adrianN 16 hours ago|||
Automatic conversion between numeric types is also a fun source of bugs.
physicsguy 17 hours ago||
Auto is fine where the context is obvious, I think. For e.g.:

    void func(std::vector<double> vec) {
        for (auto &v : vec) {
            // do stuff
        }
    }

Here it's obvious that v is of type double.
wheybags 16 hours ago|
v is double& in your example, not double. But it's not obvious that omitting the & causes a copy. If you see "for (auto v : vec)" looks good right? But if vec contains eg long strings, you've now murdered your perf because you're copying them out of the array instead of grabbing refs. Yes, you could make the same mistake without auto, but it's easier to notice. It's easy to forget (or not notice) that auto will not resolve to a reference in this case, because using a reference is "obviously what I want here", and the name of the feature is "auto" after all - "surely it will figure it out, right?"
jcelerier 14 hours ago|||
> But if vec contains eg long strings, you've now murdered your perf because you're copying them out of the array instead of grabbing refs.

I've seen much more perf-murdering things being caused by

   std::map<std::string, int> my_map;
   for(const std::pair<std::string, int>& v: my_map) {
       ...
   }
than with auto though
verall 8 hours ago|||
Wow I really thought this would be a compile error. The implicit cast here really is a footgun. Looks like '-Wrange-loop-construct' (included in -Wall) does catch it:

> warning: loop variable 'v' of type 'const std::pair<std::__cxx11::basic_string<char>, int>&' binds to a temporary constructed from type 'std::pair<const std::__cxx11::basic_string<char>, int>' [-Wrange-loop-construct] 11 | for (const std::pair<std::string, int>& v: m) {

As they say, the power of names...

dubi_steinkek 12 hours ago|||
Why is this a perf footgun? As someone who doesn't write a lot of c++, I don't see anything intuitively wrong.

Is it that iterating over map yields something other than `std::pair`, but which can be converted to `std::pair` (with nontrivial cost) and that result is bound by reference?

nemetroid 11 hours ago||
Close, it is a std::pair, but it differs in constness. Iterating a std::map<K, V> yields std::pair<const K, V>, so you have:

  std::pair<const std::string, int>
vs

  std::pair<std::string, int>
1718627440 10 hours ago||
And what does casting const change, that would involve runtime inefficiencies?
gpderetta 10 hours ago|||
It is not a cast. std::pair<const std::string, ...> and std::pair<std::string,...> are different types, although there is an implicit conversion. So a temporary is implicitly created and bound to the const reference. So not only there is a copy, you have a reference to an object that is destroyed at end of scope when you might expect it to live further.
1718627440 6 hours ago||
I guess this is one of the reasons, why I don't use C++. Temporaries is a topic, where C++ on one side and me and C on the other side has had disagreements in the past. Why does changing the type even create another object at all? Why does it allocate? Why doesn't the optimizer use the effective type to optimize that away?
nemetroid 10 hours ago|||
Each entry in the map will be copied. In C++, const T& is allowed to bind to a temporary object (whose lifetime will be extended). So a new pair is implicitly constructed, and the reference binds to this object.
spacechild1 15 hours ago|||
> Yes, you could make the same mistake without auto, but it's easier to notice.

Is it really? I rather think that a missing & is easier to spot with "auto" simply because there is less text to parse for the eye.

> If you see "for (auto v : vec)" looks good right?

For me the missing & sticks out like a sore thumb.

> It's easy to forget (or not notice) that auto will not resolve to a reference in this case

Every feature can be misused if the user forgets how it works. I don't think people suddenly forget how "auto" works, given how ubiquitous it is.

tialaramex 13 hours ago||
It's a foot gun. Why is the default target for this gun my foot? "You should be careful to choose the target properly" isn't an answer to that question.
gpderetta 13 hours ago||
In what way it is a footgun? auto x = ... ; does what I would expect. Copying is usually the default in C++ and that's what I would expect to happen here.

If auto deduced reference types transparently, it would actually be more dangerous.

tialaramex 10 hours ago|||
Copying is a weird default because who said that's even achievable, let alone cheap?

So I guess I depart from you there and thus my issue here is not really about auto

spacechild1 9 hours ago||
C++ has value semantics, which means that values of user-defined types generally behave like values of built-in types. In this sense, copying is the only logical default. It's just how the language has been designed.

Things are different in Rust because of lifetimes and destructive moves. In this context, copying would be a bad default indeed.

> because who said that's even achievable, let alone cheap?

Nobody said that. The thing is that user-defined types can be anything from tiny and cheap to huge and expensive. A language has to pick one default and be consistent. You can complain one way or the other.

1718627440 5 hours ago||
I find passing by value to be sensible, but the allocating part sounds like a bad idea. Passing the value of something doesn't imply making a copy, if the value is never changed, it can be entirely optimized away.
spacechild1 3 hours ago||
> Passing the value of something doesn't imply making a copy

Yes, languages like Rust can automatically move variables if the compiler can prove that they will not be used anymore. Unfortunately, this is not possible in C++, so the user has to move explicitly (with std::move).

1718627440 1 hour ago||
> Unfortunately, this is not possible in C++, so the user has to move explicitly (with std::move).

Honestly why not? A locally used variable sounds to be very much something the compiler can reason about. And a variable only declared in a loop, which is destroyed at the end of each iteration and only read from should be able to be optimized away. I don't know Rust, I mostly write C.

spacechild1 1 hour ago||
Yes, this could work for simple cases, but this breaks down pretty quickly:

  void checkFoo(const Foo&);
  
  Foo getFoo();
  
  void example() {
    std::vector<Foo> vec;

    Foo foo = getFoo();
    if (checkFoo(foo)) {
      // *We* know that checkFoo() does not store a
      // reference to 'foo' but the compiler does not
      // know this. Therefore it cannot automatically
      // move 'foo' into the std::vector.
      vec.push_back(std::move(foo));
    }
  }
The fundamental problem is that C++ does not track object lifetimes. You would end up with a system where the compiler would move objects only under certain circumstances, which would be very hard to reason about.
1718627440 47 minutes ago||
Ah, you are right. It theoretically should be able to do it, but even with -flto, there are likely cases, where it doesn't. It is less of a problem with C, since you explicitly tell the compiler, whether you want things to get passed as value or pointer. Also I typically annotate ownership, so it it easy to forget that the compiler doesn't actually knows this. This is a weird limitation, there should be just a parameter attribute to do that.
spacechild1 12 hours ago|||
That's exactly what I was about to write!
delduca 4 hours ago||
I use auto everywhere, no regrets.

My LS can infer it anytime.

yearolinuxdsktp 4 hours ago|
+1 I agree 100% use it everywhere and let LS/IDE infer it.

On top of that:

* Reduce refactoring overhead (a type can be evolved or substituted, if it duck types the same, the autos don't change)

* If using auto instead of explicit types makes your code unclear, it's not clear enough code to begin with. Improve the names of methods being called or variable names being assigned to.

I can see explicit types when you are shipping a library, as part of the API your library exposes. Then you want types to be explicit and changes to be explicit.

rrhjm53270 4 hours ago||
A trick that I used was

``` struct dummy{}; dummy d = ANYTHING_YOU_WANT_GO_GET_THE_TYPE; ```

Compile it with g++, and get the type info from compilation error -:)

flakes 20 hours ago||
Auto has really made c++ unapproachable to me. It's hard enough to reason about anything templated, and now I frequently see code where every method returns auto. How is any one supposed to do a code review without loading the patch into their IDE?
OneDeuxTriSeiGo 19 hours ago||
I'd suppose this really depends on how you are developing your codebase but most code should probably be using a trailing return type or using an auto (or template) return type with a concept/requires constraint on the return type.

For any seriously templated or metaprogrammed code nowadays a concept/requires is going to make it a lot more obvious what your code is actually doing and give you actually useful errors in the event someone is misusing your code.

jjmarr 19 hours ago||
I don't understand why anyone would use auto and a trailing return type for their functions. The syntax is annoying and breaks too much precedent.
dataflow 19 hours ago|||
Generally, you don't. I'm not sure why the parent suggested you should normally do this. However, there are occasional specific situations in which it's helpful, and that's when you use it.
OneDeuxTriSeiGo 13 hours ago||
It just is an improvement for a bunch of reasons.

1. Consistency across the board (places where it's required for metaprogramming, lambdas, etc). And as a nicety it forces function/method names to be aligned instead of having variable character counts for the return type before the names. IMHO it makes skimming code easier.

2. It's required for certain metaprogramming situations and it makes other situations an order of magnitude nicer. Nowadays you can just say `auto foo()` but if you can constrain the type either in that trailing return or in a requires clause, it makes reading code a lot easier.

3. The big one for everyday users is that trailing return type includes a lot of extra name resolution in the scope. So for example if the function is a member function/method, the class scope is automatically included so that you can just write `auto Foo::Bar() -> Baz {}` instead of `Foo::Baz Foo::Bar() {}`.

dataflow 10 hours ago||
1. You're simply not going to achieve consistency across the board, because even if you dictate this by fiat, your dependencies won't be like this. The issue of the function name being hard to spot is easier to fix with tooling (just tell your editor to color them or make them bold or something). OTOH, it's not so nice to be unable to tell at a glance if the function return type is deduced or not, or what it even is in the first place.

2. It's incredibly rare for it to be required. It's not like 10% of the time, it's more like < 0.1% of the time. Just look at how many functions are in your code and how many of them actually can't be written without a trailing return type. You don't change habits to fit the tiny minority of your code.

3. This is probably the best reason to use it and the most subjective, but still not a particularly compelling argument for doing this everywhere, given how much it diverges from existing practice. And the downside is the scope also includes function parameters, which means people will refer to parameters in the return type much more than warranted, which is decidedly not always a good thing.

gpderetta 13 hours ago||||
1) consistency, 2) scoping is different and can make it a significant difference.

I have been programming in C++ for 25 years, so I'm so used to the original syntax that I don't default to auto ... ->, but I will definitely use it when it helps simplify some complex signatures.

Rucadi 18 hours ago||||
it makes function declarations/instantiations much more grep-able.
rovingeye 19 hours ago|||
Consistency (lambdas, etc.)
themafia 19 hours ago|||
The pain of lisp without any of the joy.
Traubenfuchs 16 hours ago||
Same in Java and Kotlin, but then again, the majority of people who write code wouldn't understand what I mean when I say "whether a collection is returned as List or Set is an important part of the API 'contract'".
More comments...