diff --git a/cppguide.html b/cppguide.html index 739912b..bbf1f64 100644 --- a/cppguide.html +++ b/cppguide.html @@ -159,20 +159,19 @@ input.
-Currently, code should target C++11, i.e., should not use C++14 or -C++17 features. The C++ version targeted by this guide will advance -(aggressively) over time.
- --Code should avoid features that have been removed from -the latest language version (currently C++17), as well as the rare -cases where code has a different meaning in that latest version. -Use of some C++ features is restricted or disallowed. Do not use -non-standard extensions.
+Currently, code should target C++17, i.e., should not use C++2x + features. The C++ version targeted by this guide will advance + (aggressively) over time.
+Do not use + non-standard extensions.
+ +In general, every .cc
file should have an
@@ -1914,7 +1913,7 @@ doubt, use overloads.
int foo(int x);-
The new form, introduced in C++11, uses the auto
+
The newer form, introduced in C++11, uses the auto
keyword before the function name and a trailing return type after
the argument list. For example, the declaration above could
equivalently be written:
In APIs, use const
whenever it makes sense. With C++11,
+
In APIs, use const
whenever it makes sense.
constexpr
is a better choice for some uses of
const.
In C++11, use constexpr
to define true
+
Use constexpr
to define true
constants or to ensure constant initialization.
Struct data; +-struct data; memset(&data, 0, sizeof(data));@@ -3179,89 +3178,303 @@ memset(&data, 0, sizeof(data)); }
Use auto
to avoid type names that are noisy, obvious,
-or unimportant - cases where the type doesn't aid in clarity for the
-reader. Continue to use manifest type declarations when it helps
-readability.
Use type deduction only if it makes the code clearer to readers who aren't + familiar with the project, or if it makes the code safer. Do not use it + merely to avoid the inconvenience of writing an explicit type.
+ + + +There are several contexts in which C++ allows (or even requires) types to +be deduced by the compiler, rather than spelled out explicitly in the code:
+template <typename T> +void f(T t); + +f(0); // Invokes f<int>(0)+
auto
variable declarationsauto
keyword in place
+ of the type. The compiler deduces the type from the variable's
+ initializer, following the same rules as function template argument
+ deduction with the same initializer (so long as you don't use curly braces
+ instead of parentheses).
+ auto a = 42; // a is an int +auto& b = a; // b is an int& +auto c = b; // c is an int +auto d{42}; // d is an int, not a std::initializer_list<int> ++
auto
can be qualified with const
, and can be
+ used as part of a pointer or reference type, but it can't be used as a
+ template argument. A rare variant of this syntax uses
+ decltype(auto)
instead of auto
, in which case
+ the deduced type is the result of applying
+ decltype
+ to the initializer.
+ auto
(and decltype(auto)
) can also be used in
+ place of a function return type. The compiler deduces the return type from
+ the return
statements in the function body, following the same
+ rules as for variable declarations:
+ auto f() { return 0; } // The return type of f is int+ Lambda expression return types can be + deduced in the same way, but this is triggered by omitting the return type, + rather than by an explicit
auto
. Confusingly,
+ trailing return type syntax for functions
+ also uses auto
in the return-type position, but that doesn't
+ rely on type deduction; it's just an alternate syntax for an explicit
+ return type.
+ auto
keyword in place of
+ one or more of its parameter types. This causes the lambda's call operator
+ to be a function template instead of an ordinary function, with a separate
+ template parameter for each auto
function parameter:
+ // Sort `vec` in increasing order +std::sort(vec.begin(), vec.end(), [](auto lhs, auto rhs) { return lhs > rhs; });+
[x = 42, y = "foo"] { ... } // x is an int, and y is a const char*+ This syntax doesn't allow the type to be specified; instead, it's deduced + using the rules for
auto
variables.
+ auto
, you can
+ specify names for the individual elements instead of a name for the whole
+ object; these names are called "structured bindings", and the whole
+ declaration is called a "structured binding declaration". This syntax
+ provides no way of specifying the type of either the enclosing object
+ or the individual names:
+ auto [iter, success] = my_map.insert({key, value}); +if (!success) { + iter->second = value; +}+ The
auto
can also be qualified with const
,
+ &
, and &&
, but note that these qualifiers
+ technically apply to the anonymous tuple/struct/array, rather than the
+ individual bindings. The rules that determine the types of the bindings
+ are quite complex; the results tend to be unsurprising, except that
+ the binding types typically won't be references even if the declaration
+ declares a reference (but they will usually behave like references anyway).
+ (These summaries omit many details and caveats; see the links for further + information.)
Sometimes code is clearer when types are manifest, -especially when a variable's initialization depends on -things that were declared far away. In expressions -like:
+C++ code is usually clearer when types are explicit, + especially when type deduction would depend on information from + distant parts of the code. In expressions like:
auto foo = x.add_foo(); auto i = y.Find(key);
it may not be obvious what the resulting types are if the type
-of y
isn't very well known, or if y
was
-declared many lines earlier.
y
isn't very well known, or if y
was
+ declared many lines earlier.
-Programmers have to understand the difference between
-auto
and const auto&
or
-they'll get copies when they didn't mean to.
Programmers have to understand when type deduction will or won't + produce a reference type, or they'll get copies when they didn't + mean to.
-If an auto
variable is used as part of an
-interface, e.g. as a constant in a header, then a
-programmer might change its type while only intending to
-change its value, leading to a more radical API change
-than intended.
If a deduced type is used as part of an interface, then a + programmer might change its type while only intending to + change its value, leading to a more radical API change + than intended.
-auto
is permitted when it increases readability,
-particularly as described below. Never initialize an auto
-typed
-variable with a braced initializer list.
Specific cases where auto
is allowed or encouraged:
-
find
, begin
, or end
for
-instance).new
and
-std::make_unique
-commonly falls into this category, as does use of auto
in
-a range-based loop over a container whose type is spelled out
-nearby.std::pair<KeyType, ValueType>
whereas it is actually
-std::pair<const KeyType, ValueType>
). This is
-particularly well paired with local key
-and value
aliases for .first
-and .second
(often const-ref).
-for (const auto& item : some_map) { - const KeyType& key = item.first; - const ValType& value = item.second; - // The rest of the loop can now just refer to key and value, - // a reader can see the types in question, and we've avoided - // the too-common case of extra copies in this iteration. -} --
The fundamental rule is: use type deduction only to make the code
+ clearer or safer, and do not use it merely to avoid the
+ inconvenience of writing an explicit type. When judging whether the
+ code is clearer, keep in mind that your readers are not necessarily
+ on your team, or familiar with your project, so types that you and
+ your reviewer experience as as unnecessary clutter will very often
+ provide useful information to others. For example, you can assume that
+ the return type of make_unique<Foo>()
is obvious,
+ but the return type of MyWidgetFactory()
probably isn't.
These principles applies to all forms of type deduction, but the + details vary, as described in the following sections.
+Function template argument deduction is almost always OK. Type deduction + is the expected default way of interacting with function templates, + because it allows function templates to act like infinite sets of ordinary + function overloads. Consequently, function templates are almost always + designed so that template argument deduction is clear and safe, or + doesn't compile.
+ +For local variables, you can use type deduction to make the code clearer + by eliminating type information that is obvious or irrelevant, so that + the reader can focus on the meaningful parts of the code: +
std::unique_ptr<WidgetWithBellsAndWhistles> widget_ptr = + absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); +absl::flat_hash_map<std::string, + std::unique_ptr<WidgetWithBellsAndWhistles>>::const_iterator + it = my_map_.find(key); +std::array<int, 0> numbers = {4, 8, 15, 16, 23, 42};+ +
auto widget_ptr = absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); +auto it = my_map_.find(key); +std::array numbers = {4, 8, 15, 16, 23, 42};+ +
Types sometimes contain a mixture of useful information and boilerplate,
+ such as it
in the example above: it's obvious that the
+ type is an iterator, and in many contexts the container type and even the
+ key type aren't relevant, but the type of the values is probably useful.
+ In such situations, it's often possible to define local variables with
+ explicit types that convey the relevant information:
+
auto it = my_map_.find(key); +if (it != my_map_.end()) { + WidgetWithBellsAndWhistles& widget = *it->second; + // Do stuff with `widget` +}+ If the type is a template instance, and the parameters are + boilerplate but the template itself is informative, you can use + class template argument deduction to suppress the boilerplate. However, + cases where this actually provides a meaningful benefit are quite rare. + Note that class template argument deduction is also subject to a + separate style rule. + +
Do not use decltype(auto)
if a simpler option will work,
+ because it's a fairly obscure feature, so it has a high cost in code
+ clarity.
Use return type deduction (for both functions and lambdas) only if the
+ function body has a very small number of return
statements,
+ and very little other code, because otherwise the reader may not be able
+ to tell at a glance what the return type is. Furthermore, use it only
+ if the function or lambda has a very narrow scope, because functions with
+ deduced return types don't define abstraction boundaries: the implementation
+ is the interface. In particular, public functions in header files
+ should almost never have deduced return types.
auto
parameter types for lambdas should be used with caution,
+ because the actual type is determined by the code that calls the lambda,
+ rather than by the definition of the lambda. Consequently, an explicit
+ type will almost always be clearer unless the lambda is explicitly called
+ very close to where it's defined (so that the reader can easily see both),
+ or the lambda is passed to an interface so well-known that it's
+ obvious what arguments it will eventually be called with (e.g.
+ the std::sort
example above).
Init captures are covered by a more specific + style rule, which largely supersedes the general rules for + type deduction.
+ +Unlike other forms of type deduction, structured bindings can actually
+ give the reader additional information, by giving meaningful names to the
+ elements of a larger object. This means that a structured binding declaration
+ may provide a net readability improvement over an explicit type, even in cases
+ where auto
would not. Structured bindings are especially
+ beneficial when the object is a pair or tuple (as in the insert
+ example above), because they don't have meaningful field names to begin with,
+ but note that you generally shouldn't use
+ pairs or tuples unless a pre-existing API like insert
+ forces you to.
If the object being bound is a struct, it may sometimes be helpful to + provide names that are more specific to your usage, but keep in mind that + this may also mean the names are less recognizable to your reader than the + field names. We recommend using a comment to indicate the name of the + underlying field, if it doesn't match the name of the binding, using the + same syntax as for function parameter comments: +
auto [/*field_name1=*/ bound_name1, /*field_name2=*/ bound_name2] = ...+ As with function parameter comments, this can enable tools to detect if + you get the order of the fields wrong. + +
Use class template argument deduction only with templates that have + explicitly opted into supporting it.
+ + +Class + template argument deduction (often abbreviated "CTAD") occurs when + a variable is declared with a type that names a template, and the template + argument list is not provided (not even empty angle brackets): +
std::array a = {1, 2, 3}; // `a` is a std::array<int, 3>+ The compiler deduces the arguments from the initializer using the + template's "deduction guides", which can be explicit or implicit. + +
Explicit deduction guides look like function declarations with trailing
+ return types, except that there's no leading auto
, and the
+ function name is the name of the template. For example, the above example
+ relies on this deduction guide for std::array
:
+
namespace std { +template <class T, class... U> +array(T, U...) -> std::array<T, 1 + sizeof...(U)>; +}+ Constructors in a primary template (as opposed to a template specialization) + also implicitly define deduction guides. + +
When you declare a variable that relies on CTAD, the compiler selects + a deduction guide using the rules of constructor overload resolution, + and that guide's return type becomes the type of the variable.
+ + +CTAD can sometimes allow you to omit boilerplate from your code.
+ + +The implicit deduction guides that are generated from constructors + may have undesirable behavior, or be outright incorrect. This is + particularly problematic for constructors written before CTAD was + introduced in C++17, because the authors of those constructors had no + way of knowing about (much less fixing) any problems that their + constructors would cause for CTAD. Furthermore, adding explicit deduction + guides to fix those problems might break any existing code that relies on + the implicit deduction guides.
+ +CTAD also suffers from many of the same drawbacks as auto
,
+ because they are both mechanisms for deducing all or part of a variable's
+ type from its initializer. CTAD does give the reader more information
+ than auto
, but it also doesn't give the reader an obvious
+ cue that information has been omitted.
Do not use CTAD with a given template unless the template's maintainers
+ have opted into supporting use of CTAD by providing at least one explicit
+ deduction guide (all templates in the std
namespace are
+ also presumed to have opted in). This should be enforced with a compiler
+ warning if available.
Uses of CTAD must also follow the general rules on + Type deduction.
this
if any members are used:
+Default captures implicitly capture any variable referenced in the
+lambda body, including this
if any members are used:
const std::vector<int> lookup_table = ...; std::vector<int> indices = ...; @@ -3304,10 +3517,22 @@ std::sort(indices.begin(), indices.end(), [&](int a, int b) { });-
Lambdas were introduced in C++11 along with a set of utilities
-for working with function objects, such as the polymorphic
-wrapper std::function
.
-
A variable capture can also have an explicit initializer, which can + be used for capturing move-only variables by value, or for other situations + not handled by ordinary reference or value captures: +
std::unique_ptr<Foo> foo = ...; +[foo = std::move(foo)] () { + ... +}+ Such captures (often called "init captures" or "generalized lambda captures") + need not actually "capture" anything from the enclosing scope, or even have + a name from the enclosing scope; this syntax is a fully general way to define + members of a lambda object: +
[foo = std::vector<int>({1, 2, 3})] () { + ... +}+ The type of a capture with an initializer is deduced using the same rules + as
auto
.
std::function
.
This is especially confusing when capturing 'this' by value, since the use
of 'this' is often implicit.
+ auto
placeholder (although init captures can
+ indicate it indirectly, e.g. with a cast). This can make it difficult to
+ even recognize them as declarations.auto
, with the additional problem that the syntax doesn't
+ even cue the reader that deduction is taking place.auto
.std::hash
.
-Use libraries and language extensions from C++11 when appropriate. -Consider portability to other environments -before using C++11 features in your -project.
- -C++11 contains -significant changes both to the language and -libraries.
+C++11 was the official standard until 2014, and -is supported by most C++ compilers. It standardizes -some common C++ extensions that we use already, allows -shorthands for some operations, and has some performance -and safety improvements.
- - -The C++11 standard is substantially more complex than -its predecessor (1,300 pages versus 800 pages), and is -unfamiliar to many developers. The long-term effects of -some features on code readability and maintenance are -unknown. We cannot predict when its various features will -be implemented uniformly by tools that may be of -interest, particularly in the case of projects that are -forced to use older versions of tools.
- -As with Boost, some C++11 +
As with Boost, some modern C++ extensions encourage coding practices that hamper readability—for example by removing checked redundancy (such as type names) that may be @@ -3670,12 +3886,9 @@ metaprogramming. Other extensions duplicate functionality available through existing mechanisms, which may lead to confusion and conversion costs.
- - -C++11 features may be used unless specified otherwise. -In addition to what's described in the rest of the style -guide, the following C++11 features may not be used:
+In addition to what's described in the rest of the style +guide, the following C++ features may not be used:
<fenv.h>
headers, because many
compilers do not support those features reliably.
+ <filesystem>
header, which
+
+ does not have sufficient support for testing, and suffers
+ from inherent security vulnerabilities.