I've been writing C++ for half a decade now, and auto
has always been a great source of discomfort to me.
Whenever I came back to a past project that makes extensive use of it, I found myself confused,
and first had to look at all the types before I could make sense of it.
Similarly, I've worked as an intern at a company that had a AAA policy for its code base. Whenever something didn't work, and I had to debug some code, half the time was spent just looking up types. I was not alone in this struggle; my senior colleagues were wasting their time the same way when debugging the code they wrote.
Were we all too stupid to handle auto
, or is there something wrong with it?
Well, I haven't been able to articulate my thoughts until now;
now I understand the issues with auto
, and why it confuses me so much.
This article seeks to raise awareness for the problems auto
causes,
and makes a reasonable recommendation for when exactly auto
can be used.
In code bases I've worked on (some of which were mine, some of which were work-related),
I frequently dug up auto
-based code like this:
void Container::do_things() {
auto chunk = search_for_chunk();
add_extra_stuff_to_chunk(chunk);
use_somehow(std::move(chunk));
}
While you can vaguely intuit what this code does judging by the names involved, it is very difficult to understand the exact semantics.
chunk
could be a container such asstd::vector<ChunkItem>
, or it could be a singular item likeChunk
. The namechunk
isn't really a bad name, but without knowing the type, we know very little about how to use it.- Similarly,
std::move
might be- unnecessary if
chunk
is a trivial type, or - it could be crucial for the code to compile, if
chunk
is of typestd::unique_ptr<Chunk>
, or - it could be performance-critical, such as if
chunk
is of typestd::vector
- unnecessary if
Let's explore in depth why auto
is so problematic.
The basic issue with auto
is that it hides information.
This is very useful when the information is redundant, such as in
int x = static_cast<int>(...);
// vs
auto x = static_cast<int>(...);
However, it is way too easy to hide crucial information which would prevent bugs and help us reason about code.
One negative consequence of auto
is that it hides whether we have ownership over the objects involved.
Ownership information is crucial to write safe and efficient code.
Consider the following example, which contains a likely bug and a performance issue:
std::string_view a = get_name_1(); // getting a non-owning view into a name
store(a); // DANGER!!, the viewed name might expire while we store it
std::string b = get_name_2(); // copying, or taking ownership of a name
store(b); // OK, but we should probably use std::move instead
At first glance, we can spot a potential bug in store(a)
, and a possible optimization for store(b)
.
Even with nonsensical variable names like a
and b
, we can reason about this easily.
auto a = get_name_1(); // owning or non-owning? who knows ...
store(a); // hmmm, whoever wrote this probably knew what they were doing ...
auto b = get_name_2(); // owning or non-owning? who knows ...
store(b); // hmmm, whoever wrote this probably knew what they were doing ...
Without revealing the types involved, we have no idea that this code contains a
bug (possibly security vulernability) and a missed optimization.
Even if we used more meaningful variable names like name
instead of a
, this wouldn't have prevented anything.
Essentially, C++ uses types in order to encode ownership (e.g. T*
is drastically different from std::unique_ptr<T>
)
and such information is lost when using auto
.
Garbage-collected languages like JavaScript can make type inference more comfortable, because you don't have to worry about
ownership nearly as much when everything is magically owned by the run-time environment.
Besides hiding whether we own something at all, auto
hides the style of ownership.
auto thing = get_thing()
could mean a lot of things:
- If
thing
is a small trivial value, then we don't need tostd::move
it, and we can use it more freely. - If
thing
is a large value, we need tostd::move
it and concern ourselves with accidental expensive copies. - If
thing
is astd::optional<Thing>
, we have ownership, and we have value semantics, but only if the value is valid. - If
thing
is astd::unique_ptr<Thing>
, we have unique ownership, and we muststd::move
it to avoid errors. - If
thing
is astd::shared_ptr<Thing>
, we have shared ownership, and we must make sure not to create circular shared ownership to avoid memory leaks; copying is possible. - If
thing
is aThing*
, then we probably don't have any ownership.
Each possibility implies that we have to use thing
in a radically different way.
If we misuse it, we either get a compiler error, or we create memory leaks, performance regressions, etc.
Type information is too important in C++ to be erased, in many cases.
Compromise: we can use class template argument deduction CTAD in C++17 to
get ownership information without the exact type.
For example, std::unique_ptr p = ...;
*
Counter-argument: "even when you know the type, ownership may be unclear.
For example, T*
can be owned or not owned.
If so, auto
doesn't solve anything."
However, it is possible to avoid these ambiguous ownership schemes.
The ownership that a std::unique_ptr
has is unambigous.
CppCoreGuidelines says I.11 Never transfer ownership by a raw pointer (T*
) or reference (T&
).
Roughly, we have "things", and "collections of things".
If we diligently used names such as users
to refer to Collection<User>
, and user
to refer to a single User
,
it would be obvious whether we have a collection, even with auto
.
However, collections of things often have a singular name, such as
Collection<Fish>
becomesschool
Collection<ChunkItem>
becomeschunk
Collection<RadioButton>
becomesbutton_group
Collection<Number>
becomeshistogram
With knowledge about the domain, we can infer that a histogram
has to be a collection.
Intuitively, it's also obvious that button_group
must consist of multiple buttons.
However, it is not so obvious that school
and chunk
are containers, and this breaks our expectations.
If we see school.remove(fish)
, we might assume that the
"school removes the fish (somehow??)", not that the "fish is removed from the school (collection)".
If we see chunk.clear()
, and chunk
is something like an array, this operation could perform a reset of every element inside.
This could be a fairly expensive operation, but it looks quite harmless to us.
Counter-argument: "these problems aren't an inevitable outcome of using auto
.
Rather, they result from the difficulty of naming things."
However, auto
exacerbates these problems, and it's unclear whether developers can reliably come up
with names so good, that auto
becomes obsolete.
In the end, this is opinion-based.
So far, all the mentioned problems can be mitigated somewhat by making our variable names more elaborate. For example, we could write
auto name_view
for astd::string_view
auto name_owned
for astd::string
.
However, this is a foolish practice because nothing ensures the correctness of the name.
If the code is refactored and still compiles (due to std::string
having a similar interface as std::string_view
),
then we end up with a misleading name (std::string name_view
).
In the end, variable names are just comments, and no comment is better than a misleading comment.
If it's crucial that we are using a std::string_view
instead of std::string
,
we should show the type instead of mirroring it in our variable name.
This is robust, and conveys more information.
Compromise: catch these attempts of including type information in variable names during code review.
Advice against them in a style guide.
auto
encourages this bad practice, but it's not an inevitable result of auto
.
Naming is one of the hardest problems in software development. A good name can help you understand code vastly faster; a bad name can mislead you and waste your time.
Think about how often we name things:
- We almost never name namespaces.
- Much more often, we name types (classes, enumerations, etc.).
- Much more often, we name functions.
- Much more often, we name parameters and variables.
If we remove type information, then our variable names better be good. Suddenly, the most frequent naming problem becomes the most important one, and any naming mistake has much greater impact.
Type information provides a safety net for bad naming of local variables, such as
- quasi-useless names like
std::vector v = ...;
, or - outright misleading names like
int ptr = ...;
,
Counter-argument: "this has nothing to do with auto
, and everything to do with naming.
These are separate issues."
In a sense, this is correct, but naming is always going to remain a hard problem.
To error is human; everyone will come up with bad variable names at some point.
auto
removes a safety net for when this inevitably happens.
Herb Sutter's article on AAA notes that we should program towards an interface.
The exact types are often implementation details, and can be omitted, which is what auto
does.
For example, we can write auto it = begin(c)
without needing to know the exact type of iterator.
This argument has a lot of merit, but auto
is only a half-measure.
Consider that auto it = begin(c)
communicates that we obtain an object, not a reference.
To be consistent, we should be writing auto&&
or decltype(auto)
to completely ignore the type,
but AAA advocates typically don't take this next logical step, even when applicable.
In practice, this is a very minor issue, but it demonstrates something important:
the exact type may only be an implementation detail, but some portion of the type isn't.
The exact type container::iterator
is an implementation detail, but we still want begin(c)
to give us an object
of some iterator type.
The intention of not caring about the exact type,
but caring about the type to some extent is more accurately implemented by type aliases than by auto
:
// Throughout the project, we don't care whether a quantity is int, long, or some other integer type.
// We decide on this implementation detail only in one place.
using quantity = int;
// We don't care what specific type get_amount() returns, but we do care that it returns an object,
// and this object is of some quantity type.
quantity get_amount();
// Even when refactoring by changing the definition of quantity, this code remains correct.
quantity a = get_amount();
The standard library already follows this appraoch, and so do many other libraries.
A proper library will almost never use the fundamental types directly, e.g. use float
or double
directly.
It is more common to see aliases such as float_type
.
Counter-argument: "even the alias may be too verbose. How are you going to shorten container::iterator
?"
This is true. For begin(c)
, we expect to get some iterator type,
and common sense tells us that this is the iterator type of the the container.
Use of auto
for iterators is a good, and I recommend it.
Counter-argument: "the alias may not be broad enough.
We don't care that the type is container::size_type
, we only care about the fact that it's some integer type."
This is also true. There are cases where the properties we care about are a middle ground between
auto
, which just says that we want some object, whatever that may bea_specific_alias
, which may encode more information than we want
We have to decide whether we make our code less specific, or more specific to the type than we actually want. This is a dilemma; the solution is subjective and case-by-case.
Compromise: There is an ugly, but philosophically optimal solution to the dilemma where auto
is too little, and the alias is too much.
auto s = end - start; // we want s to be some integral type, but don't care about the exact type
static_assert(std::integral<decltype(s)>); // this encodes intention which was missing from auto
// we could also us a macro such as
TYPE_ASSERT(s, std::integral); // expands to []<std::integral T>(T&) {}(s)
The diagnostics quality is questionable for the macro (see Compiler Explorer).
Counter-argument: "it takes a lot of effort to use proper type aliases throughout a project; especially for integer types.
Are we really supposed to have aliases for quantity
, index
, size
, etc.?"
This argument is true; it takes effort.
And yes, we really should be using these aliases instead of leaking implementation through direct use of int
, float
, etc.
The granularity of type aliases can vary between projects though.
For many projects, it is sufficient to use a single alias using integer = long long;
.
Consider this mathematical code:
vec3 d = p - q;
We can tell that d
is a three-dimensional vector, and due to conventions,
it probably represents a "distance" or "difference".
Even with poor variable names, we can understand this code.
On the contrary,
auto difference = end - start;
We have much better variable names, but we cannot intuit whether difference
is one-dimensional or three-dimensional.
The first example is more concise and has worse names, yet it conveys more critical information.
In some domains, type information is too important to omit. Also, if we include type names, we can get away with much worse naming for our variable names. That is a good thing, because naming is hard, and having tremendously more leeway in the required quality of variable names makes our job easier.
Of course, type information isn't a full replacement for variable names. We should have good names and type information:
vec3 difference = end - start;
Note: expressions like p * q
are even worse, because they could be scalar/vector/matrix multiplication.
Multiplication with *
can mean so many things that use of auto
in linear algebra can be royally confusing.
Different operations, even if named the same can have vastly different cost. For example:
std::string_view::substr
is trivially cheap.std::string::substr
might involve dynamic allocations.
Similarly, items.insert(position, Item{})
could be
- O(1) if
items
is astd::list
. - O(n) if
items
is astd::vector
.
It becomes virtually impossible to reason about the costs in our code if we don't know the data structures involved.
auto
also makes our code vulnerable to performance regressions.
For example:
auto str = get_string();
auto all_but_first = str.substr(1);
If get_string()
returns std::string_view
(and it might at the time of writing), this code is perfectly fine.
However, if it later gets changed to std::string
, we might see a performance regression.
Worse yet, we don't even know that this is the case, because our code still compiles, and likely passes all test cases.
Extremely sophisticated regression testing is required to detect the potential damage of auto
.
Counter-argument: "this is not an inherent issue with auto
;
it's a problem related to std::string
and std::string_view
having an identical interface."
While this is true, it's also unclear what the alternative is.
Conventional names like .substring
for a portion of a string are intuitive, and they mean that users only
have to memorize one interface, not two.
There is no right or wrong here, only trade-offs.
Using explicit types wins the trade-off because we get to use a common and memorable interface, and we don't suffer from unexpected performance issues.
To understand this, consider the following code:
auto& container = get_container();
auto& a = container.emplace_back(0);
auto& b = container.emplace_back(1);
use_two(a, b);
At the time of writing, container
might be std::deque
.
std::deque
does not invalidate references on emplace_back
, so keeping a
and b
like this is perfectly fine.
However, if it is subsequently changed to std::vector
, then this code is no longer correct.
If the second emplace_back
resizes the std::vector
, then a
is invalidated and this code contains undefined behavior.
Worse yet, it still compiles, we are just not aware that it is broken.
In this way, auto
may introduce undetected security vulnerabilities
(CWE-416 use after free).
Counter-argument: "auto
isn't the underlying problem here.
The actual problem is using the same insert()
name in the library with different semantics."
However, unless we replace the C++ standard library with one that doesn't have these design issues, we may run into these problems. For many projects, it isn't practical to avoid the standard library, so these issues may as well be problems with the core language.
A classic example:
template <typename T>
void process(const std::vector<T> &v) {
for (auto x : v) {
if (/* ... */) {
v = T{}; // reset
}
consume(x);
}
}
This code would fail to compile for the std::vector<bool>
specializaton.
In the example above, the correct type would have been std::vector<T>::value_type
.
std::vector<bool>::reference
is an object, not a refeence, so auto x
doesn't perform a copy in this context.
From my experience, people see this mostly as a design blunder in the standard library, and std::vector<bool>
is frowned upon.
However, it illustrates that in generic code (especially the unconstrained style that C++ has),
static type inference is problematic.
A classic example:
std::uint8_t a = /* ... */, b = /* ... */;
std::uint8_t c = a + b;
auto d = a + b; // d is int
Integer promotion means that a + b
isn't the type we expect, if our goal is to perform 8-bit unsigned addition.
Another example is taken from Herb Sutter's article on AAA:
// Classic C++ declaration order // Modern C++ style const char* s = "Hello"; auto s = "Hello";
It may not be obvious to inexperienced developers that the string literal "Hello"
decays to a pointer here.
auto s
is neither an array, nor a std::string
.
I've seen many new developers get confused by the type of string literals, and by pointer decay.
A good alternative to Herb's code in modern C++ would be:
std::string_view s = "Hello";
If we used auto
, we wouldn't have the convenient interface of std::string_view
.
We would work with a pointer.
Counter-argument: "for string literals, this can be avoided by using "Hello"sv
."
This is true, however it's up to the project style guide whether use of these literals is appropriate or not. After all, they are a relatively niche opt-in feature, and may be quite surprising to many readers.
Also, not every type has a literal suffix, e.g. there is no literal for short
or signed char
.
Either this leads to inconsistency (auto x = short{10}
vs auto x = 10u
), or we need to create additional
user-defined literals.
Herb Sutter's article on AAA recommends auto x = type{expr}
as a modern replacement for the type x = expr
style,
at least when the user explicitly uses a type, rather than whatever the deduced type is.
Herb Sutter acknowledges that this syntax has its limitations, some of which will likely never be resolved:
unsigned char{expr}
is ill-formed (uchar{expr}
with an extra type alias could be used instead)int[] {expr}
is ill-formed (std::array
could be used instead)class T{expr}
is ill-formed (T{expr}
could be used instead)
A major issue is that type{expr}
may inadvertently call a std::initializer_list
constructor,
and this possibility can make it unsuitable for template code where we don't know what constructors type
has.
Note that std::initializer_list
constructors win against the copy constructor in overload resolution.
There are other minor issues.
Prior to C++17, type{exp}
would not be a case of guaranteed copy elision, and this code may invoke a copy/move constructor.
In practice, this is a minor problem because compilers perform optional copy elision in these simple cases.
It is also illegal to write auto m = std::mutex{};
, and necessary to fall back to std::mutex m;
before C++17.
In practice, this is a minor problem because we can make an exception to AAA for immovable types, while using the rule elsewhere.
It's only a minor inconsistency.
Herb Sutter also mentions that preferring auto x = type{expr}
is a guideline for developers
because it requires the variable to be initialized, and prevents implicit narrowing conversions.
The problem with using this declaration style to always initialize a variable is that it sweeps bugs under the rug.
It will initialize numeric types to zero, which prevents compiler diagnostics, static analyzers, or sanitizers from catching mistakes.
Some tool could have caught int x; use(x);
as a bug, possibly before compilation (during linting).
If we sweep the issue under the rug by initializing everything to zero, tooling has no chance at spotting this mistake.
Counter-argument: "tooling isn't perfect, and if, for whatever reason, a use-before-initialized bug slips through the cracks, then the ramifications might be more severe (CWE-457: Use of Uninitialized Variable)."
This is true. In some domains, the damage caused by this bug far outweighs the benefits of catching it more easily through tooling. The project manager has to make an informed trade-off; there is no strictly right/wrong here.
auto x = type{expr}
for the purpose of avoiding type conversions is not a complete solution.
It does prevent some of them, but doesn't catch:
- narrowing conversions in function calls, such as
take_float(my_double)
- sign mismatch in expressions, such as
signed + unsigned
,signed < unsigned
, ..., - narrowing conversions in
return
statements, unless someone commits to writingreturn {expr};
(but that is unusual and unpopular style).
List-initialization is only a bandaid, not a full solution.
To catch a larger set of mistakes, it is inevitable that we need to use additional diagnostics
such as -Wconversion
for GCC/Clang.
This isn't to say that {expr}
is a useless, just that the "added safety" argument is extremely weak.
This is one of the major design blunders of C++.
type(expr)
is not simply construction; it's a function-style cast and allows for more conversions than type x(expr)
.
This can lead to surprising behavior when type
is a pointer or reference.
Overall, neither auto x = type{expr}
nor auto x = type(expr)
are universal replacements for non-auto
initialization.
The promised consistency is not achievable, and the safety arguments become minor or irrelevant in the face of tooling.
Both are more verbose than the non-auto
counterpart, and choosing them is largely a stylistic preference.
Herb Sutter also makes the argument that using auto
in other places creates consistency with
deduced return types or trailing return types.
This is true, but do we actually want to use trailing return types?
Even though they seem like a viable replacement for the old-school syntax, there are some caveats:
- The overwhelming majority of teaching material does not teach
auto main() -> int
from the start. Trailing return types violate the principle of least surprise to any newcomers to a code base who don't already practice this style. Teaching/reference materials almost always have to be translated into the trailing return type style first. auto main() -> int
is significantly more verbose thanint main()
, making it innately unattractive.- Unlike in languages like Rust, there is no distinction between
let
andfn
, there is onlyauto
. This makesauto
a noisy and relatively long keyword that conveys little information. - The alleged benefit of aligning function names can also be achieved by breaking the line after the return type.
Counter-argument: "you an always use trailing return types (e.g. in cases involving decltype
) where you could use
classic return types, but not the other way around. Trailing return types are more consistent."
This is true, but the cases where it makes a difference are very rare.
In the vast majority of cases, the return type is simple.
Sometimes it is so simple (e.g. int()
vs auto() -> int
that we more than double the length of the declaration.
Counter-argument: "trailing return types are consistent with lambda expressions."
In some sense, yes, but lambda expressions are only weakly consistent with other language features.
The location of constexpr
and other keywords is different for lambdas, and they are the only expression that begins with []
.
()
is also optional for lambdas but isn't for functions.
Aiming for more consistency with lambdas is a noble (though distant and fully unachievable) goal
and auto
gets closer to that than classic return types.
It's entirely subjective how much weight this goal has.
Note: This is only semi-related to this article, which is mostly focused on static type inference.
However, it is worth mentioning auto
parameters.
Besides use of auto
on local variables, you can use it in function parameters, creating an abbreviated function template.
For example:
void foo(std::string_view x) {
print(x.substring(4)); // typo, we should have written substr
}
void bar(auto x) {
print(x.substring(4));
}
This produces two errors:
<source>: In function 'void foo(std::string_view)':
<source>:6:13: error: 'using std::string_view = class std::basic_string_view<char>' {aka 'class std::basic_string_view<char>'} has no member named 'substring'; did you mean 'substr'?
6 | print(x.substring(4));
| ^~~~~~~~~
| substr
<source>: In instantiation of 'void bar(auto:11) [with auto:11 = const char*]':
<source>:14:8: required from here
<source>:10:13: error: request for member 'substring' in 'x', which is of non-class type 'const char*'
10 | print(x.substring(4));
| ~~^~~~~~~~~
While the second error is shorter, it has two separate locations.
A language server would give us an error at the call site of bar
, which is misleading:
the cause of our mistake is the implementation of bar
, not what we call it with.
The first error is overall better, and even makes a suggestion related to the typo we've made.
It is located only in one place, and describes the issue more clearly.
Besides that, we've also created an unconstrained template that can be called with anything.
This is bug-prone.
The same problems apply to generic lambdas (to a lesser extent).
If a generic lambda is immediately used (e.g. std::some_algorithm(..., [](auto x) { ... });
),
it's probably okay to use auto
.
In all fairness, avoiding auto
has some problems too.
One issue is seen in this code:
int x = get_amount();
get_amount
could have returned int
at the time of writing, but what if it's updated?
If it's not long get_amount()
, there is a narrowing conversion.
Or conversely, if x
is long
and get_amount()
returns int
, then this requires a sign-extending conversion.
auto
solves these issues because we always use the returned type, no implicit conversions performed.
I believe that this argument is not strong enough to justify auto
though, given that:
- Consensus among C++ developers is that heavy use of implicit conversions is bad.
- Safety could be gained by flagging implicit conversions with automatic tools.
- In a sane type system, implicit conversions should not be costly.
- For example, there is a conversion
std::string -> std::string_view
, but not the other way around.
- For example, there is a conversion
Note: an alternative solution this refactoring dilemma is to use type aliases like quantity
as described in the prior sections.
Firstly, this relies on powerful language servers, and such tools aren't always available. For example, we don't have a language server running in the background when looking at code on GitHub, and we would still like to understand it.
Secondly, IDEs aren't good enough at dealing with auto
.
As an anecdote, QtCreator displays (or used to) std::string
as std::__cxx11::__basic_string<char>
when revealing the type of a variable.
This can be somewhat mitigated through special case (e.g. display std::string
as string
like in CLion).
However, such solutions don't work for user-defined types.
For example, we might define an SI unit system in the type system, and instead of si::km
, we might get si_unit<1000, 0, 1, 0, 0, 0, 0>
.
We cannot make sense of such a type.
C++ has too powerful of a type system as that the IDE would be a viable solution to the problem, even when available.
Even if we could magically reveal the optimal type on demand in all cases, this doesn't solve all issues. We can still run into performance regressions and security vulnerabilities, as described above.
I follow this rule of thumb when deciding whether to use auto
:
If the information that is hidden by
auto
could be locally inferred, or doesn't matter, useauto
.
auto x = static_cast<To>(y);
This is a good use of auto
.
We know exactly what the type is from the right hand side.
Repeating ourselves would be bad.
auto text = prefix + get_name() + suffix;
print(text);
Even though we don't have a clue about ownership or any types involved, this code is fine.
We only print(text)
, which works regardless of what ownership we have, if any.
We also expect print
to accept a view or a const&
to a string, so there should be no performance issues, intuitively.
auto s = sqrt(x);
No matter the type of x
, we have the expectation that it's a small, cheap, numeric type.
We also understand that a sqrt
function should return the same type as the input.
It's extremely unlikely that we see any vulnerability or performance regression, so auto
is fine here,
even if we know nothing about x
.
Note: this example is similar to the prior p - q
example against auto
.
auto
is acceptable in this case because sqrt(x)
conveys that the result is one-dimensional,
which cannot be inferred from subtraction.
for (auto it = c.begin(); it != c.end(); ) {
if (...) it = c.erase(it);
else ++it;
}
Here, we can locally infer that it
is an iterator based on the interface of c
.
This code is obviously correct, because we know what an iterator is and what interface it has.
We don't care about the specific type name container::iterator
.
void insert_some(int n) {
auto& c = get_container();
for (int i = 0; i < n; ++i) {
c.insert(make(n));
}
}
Depending on the type of container, this could have vastly different performance impliciations. Also, if the container has a member function for bulk insertion, maybe this code could be improved. A senior developer familiar with the container could spot such an opportunity at first glance, but only if they knew the type of the container.
The type c
cannot be locally inferred, and we do care about specifics, so this is a malicious use of auto
.
The rule of thumb I've given above is obviously subjective, and you might question whether useful information is hidden at times. I recommend to choose caution over brevity, when in doubt.
If you're not sure whether useful information is hidden, don't use auto
.
Caveats
It’s more my interest in this comment to support people and perspectives than to argue for or against arguments on technical concerns like AAA. That said, hopefully my comments herein reflect that interest.
Perhaps debates like these can be incentives in favor of code ownership and coding standards that give more room to the individual developer on whether to use or not use certain things like
auto
in the new code they’re developing for the team.Appreciations
As someone who's been doing C++ since the early days of the Cfront compiler and who is also a fan of AAA, I'll still appreciate some things in this gist like:
Suggestions
Errors?
Is there perhaps an error or two in
auto
in templates is may not be correct?The example is:
Did you mean to assign to
x
instead ofv
, or to takev
as a non-const
parameter? Also, it seems thatv
isconst
qualified prevents compilation, not bool. Did I misunderstand perhaps?