From 967e157a000ddd4cad4eaabe8466a98260c4b825 Mon Sep 17 00:00:00 2001 From: Victor Costan Date: Thu, 5 Sep 2019 05:09:38 -0700 Subject: [PATCH] Update C++ style guide for C++17. --- cppguide.html | 454 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 336 insertions(+), 118 deletions(-) diff --git a/cppguide.html b/cppguide.html index 739912b..bbf1f64 100644 --- a/cppguide.html +++ b/cppguide.html @@ -159,20 +159,19 @@ input.

C++ Version

-

-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.

+ +
Consider portability to other environments +before using features from C++14 and C++17 in your project. +
+

Header Files

In general, every .cc file should have an @@ -1914,7 +1913,7 @@ doubt, use overloads.

form, the return type appears before the function name. For example:

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:

@@ -2752,7 +2751,7 @@ types, use pre-increment.

Use of const

-

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.

@@ -2843,7 +2842,7 @@ consistent with the code around you!

Use of constexpr

-

In C++11, use constexpr to define true +

Use constexpr to define true constants or to ensure constant initialization.

@@ -3166,7 +3165,7 @@ to any particular variable, such as code that manages an external or internal data format where a variable of an appropriate C++ type is not convenient.

-
Struct data;
+
struct data;
 memset(&data, 0, sizeof(data));
 
@@ -3179,89 +3178,303 @@ memset(&data, 0, sizeof(data)); }
-

auto

+ +

Type deduction

-

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:

+
+
Function template argument deduction
+
A function template can be invoked without explicit template arguments. + The compiler deduces those arguments from the types of the function + arguments: +
template <typename T>
+void f(T t);
+
+f(0);  // Invokes f<int>(0)
+
+
auto variable declarations
+
A variable declaration can use the auto 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. +
+
Function return type deduction
+
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. +
+
Generic lambdas
+
A lambda expression can use the 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; });
+
+
Lambda init captures
+
Lambda captures can have explicit initializers, which can be used to + declare wholly new variables rather than only capturing existing ones: +
[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. +
+
Class template argument deduction
+
See below.
+
Structured bindings
+
When declaring a tuple, struct, or array using 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.

+ of 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: -

+

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

+ +

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.

+ +

Local variable type deduction

+ +

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.

+ +

Return type deduction

+ +

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.

+ +

Parameter type deduction

+ +

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).

+ +

Lambda init captures

+ +

Init captures are covered by a more specific + style rule, which largely supersedes the general rules for + type deduction.

+ +

Structured bindings

+ +

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. + +

Class template argument deduction

+ +

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.

Lambda expressions

@@ -3292,8 +3505,8 @@ std::for_each(v.begin(), v.end(), [weight, &sum](int x) { -Default captures implicitly capture any variable referenced in the -lambda body, including 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.

@@ -3632,36 +3873,11 @@ that you can use; otherwise work with them to provide one, using a new customization mechanism that doesn't have the drawbacks of std::hash.

-

C++11

-

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.

+

Other C++ Features

-

-

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: