mirror of
https://github.com/isocpp/CppCoreGuidelines.git
synced 2025-12-18 13:14:40 +03:00
update from master
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# <a name="main"></a>C++ Core Guidelines
|
# <a name="main"></a>C++ Core Guidelines
|
||||||
|
|
||||||
April 13, 2023
|
September 29, 2023
|
||||||
|
|
||||||
Editors:
|
Editors:
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ All C++ programmers. This includes [programmers who might consider C](#S-cpl).
|
|||||||
|
|
||||||
## <a name="SS-aims"></a>In.aims: Aims
|
## <a name="SS-aims"></a>In.aims: Aims
|
||||||
|
|
||||||
The purpose of this document is to help developers to adopt modern C++ (currently C++17) and to achieve a more uniform style across code bases.
|
The purpose of this document is to help developers to adopt modern C++ (currently C++20 and C++17) and to achieve a more uniform style across code bases.
|
||||||
|
|
||||||
We do not suffer the delusion that every one of these rules can be effectively applied to every code base. Upgrading old systems is hard. However, we do believe that a program that uses a rule is less error-prone and more maintainable than one that does not. Often, rules also lead to faster/easier initial development.
|
We do not suffer the delusion that every one of these rules can be effectively applied to every code base. Upgrading old systems is hard. However, we do believe that a program that uses a rule is less error-prone and more maintainable than one that does not. Often, rules also lead to faster/easier initial development.
|
||||||
As far as we can tell, these rules lead to code that performs as well or better than older, more conventional techniques; they are meant to follow the zero-overhead principle ("what you don't use, you don't pay for" or "when you use an abstraction mechanism appropriately, you get at least as good performance as if you had handcoded using lower-level language constructs").
|
As far as we can tell, these rules lead to code that performs as well or better than older, more conventional techniques; they are meant to follow the zero-overhead principle ("what you don't use, you don't pay for" or "when you use an abstraction mechanism appropriately, you get at least as good performance as if you had handcoded using lower-level language constructs").
|
||||||
@@ -1928,7 +1928,7 @@ The assumption that the pointer to `char` pointed to a C-style string (a zero-te
|
|||||||
|
|
||||||
// we can assume that p cannot be nullptr
|
// we can assume that p cannot be nullptr
|
||||||
// we can assume that p points to a zero-terminated array of characters
|
// we can assume that p points to a zero-terminated array of characters
|
||||||
int length(not_null<zstring> p);
|
int length(not_null<czstring> p);
|
||||||
|
|
||||||
Note: `length()` is, of course, `std::strlen()` in disguise.
|
Note: `length()` is, of course, `std::strlen()` in disguise.
|
||||||
|
|
||||||
@@ -2235,9 +2235,9 @@ interface (widget.h)
|
|||||||
void draw(); // public API that will be forwarded to the implementation
|
void draw(); // public API that will be forwarded to the implementation
|
||||||
widget(int); // defined in the implementation file
|
widget(int); // defined in the implementation file
|
||||||
~widget(); // defined in the implementation file, where impl is a complete type
|
~widget(); // defined in the implementation file, where impl is a complete type
|
||||||
widget(widget&&); // defined in the implementation file
|
widget(widget&&) noexcept; // defined in the implementation file
|
||||||
widget(const widget&) = delete;
|
widget(const widget&) = delete;
|
||||||
widget& operator=(widget&&); // defined in the implementation file
|
widget& operator=(widget&&) noexcept; // defined in the implementation file
|
||||||
widget& operator=(const widget&) = delete;
|
widget& operator=(const widget&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2252,9 +2252,9 @@ implementation (widget.cpp)
|
|||||||
};
|
};
|
||||||
void widget::draw() { pimpl->draw(*this); }
|
void widget::draw() { pimpl->draw(*this); }
|
||||||
widget::widget(int n) : pimpl{std::make_unique<impl>(n)} {}
|
widget::widget(int n) : pimpl{std::make_unique<impl>(n)} {}
|
||||||
widget::widget(widget&&) = default;
|
widget::widget(widget&&) noexcept = default;
|
||||||
widget::~widget() = default;
|
widget::~widget() = default;
|
||||||
widget& widget::operator=(widget&&) = default;
|
widget& widget::operator=(widget&&) noexcept = default;
|
||||||
|
|
||||||
##### Notes
|
##### Notes
|
||||||
|
|
||||||
@@ -2500,7 +2500,7 @@ If there was a need, we could further templatize `read()` and `print()` on the d
|
|||||||
// check for errors
|
// check for errors
|
||||||
};
|
};
|
||||||
|
|
||||||
auto print(auto& output, const auto& value)
|
void print(auto& output, const auto& value)
|
||||||
{
|
{
|
||||||
output << value << "\n";
|
output << value << "\n";
|
||||||
}
|
}
|
||||||
@@ -3152,7 +3152,7 @@ Usually you forward the entire parameter (or parameter pack, using `...`) exactl
|
|||||||
Sometimes you may forward a composite parameter piecewise, each subobject once on every static control flow path:
|
Sometimes you may forward a composite parameter piecewise, each subobject once on every static control flow path:
|
||||||
|
|
||||||
template<class PairLike>
|
template<class PairLike>
|
||||||
inline auto test(PairLike&&... pairlike)
|
inline auto test(PairLike&& pairlike)
|
||||||
{
|
{
|
||||||
// ...
|
// ...
|
||||||
f1(some, args, and, forward<PairLike>(pairlike).first); // forward .first
|
f1(some, args, and, forward<PairLike>(pairlike).first); // forward .first
|
||||||
@@ -4980,11 +4980,11 @@ Note their argument types:
|
|||||||
class X {
|
class X {
|
||||||
public:
|
public:
|
||||||
// ...
|
// ...
|
||||||
virtual ~X() = default; // destructor (virtual if X is meant to be a base class)
|
virtual ~X() = default; // destructor (virtual if X is meant to be a base class)
|
||||||
X(const X&) = default; // copy constructor
|
X(const X&) = default; // copy constructor
|
||||||
X& operator=(const X&) = default; // copy assignment
|
X& operator=(const X&) = default; // copy assignment
|
||||||
X(X&&) = default; // move constructor
|
X(X&&) noexcept = default; // move constructor
|
||||||
X& operator=(X&&) = default; // move assignment
|
X& operator=(X&&) noexcept = default; // move assignment
|
||||||
};
|
};
|
||||||
|
|
||||||
A minor mistake (such as a misspelling, leaving out a `const`, using `&` instead of `&&`, or leaving out a special function) can lead to errors or warnings.
|
A minor mistake (such as a misspelling, leaving out a `const`, using `&` instead of `&&`, or leaving out a special function) can lead to errors or warnings.
|
||||||
@@ -5010,7 +5010,7 @@ Users will be surprised if copy/move construction and copy/move assignment do lo
|
|||||||
shared_ptr<Impl> p;
|
shared_ptr<Impl> p;
|
||||||
public:
|
public:
|
||||||
Silly(const Silly& a) : p(make_shared<Impl>()) { *p = *a.p; } // deep copy
|
Silly(const Silly& a) : p(make_shared<Impl>()) { *p = *a.p; } // deep copy
|
||||||
Silly& operator=(const Silly& a) { p = a.p; } // shallow copy
|
Silly& operator=(const Silly& a) { p = a.p; return *this; } // shallow copy
|
||||||
// ...
|
// ...
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6387,7 +6387,7 @@ If `x = x` changes the value of `x`, people will be surprised and bad errors can
|
|||||||
string s;
|
string s;
|
||||||
int i;
|
int i;
|
||||||
public:
|
public:
|
||||||
Foo& operator=(Foo&& a);
|
Foo& operator=(Foo&& a) noexcept;
|
||||||
// ...
|
// ...
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6436,8 +6436,13 @@ A non-throwing move will be used more efficiently by standard-library and langua
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
class Vector {
|
class Vector {
|
||||||
public:
|
public:
|
||||||
Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.sz = 0; a.elem = nullptr; }
|
Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.elem = nullptr; a.sz = 0; }
|
||||||
Vector& operator=(Vector&& a) noexcept { elem = a.elem; sz = a.sz; a.sz = 0; a.elem = nullptr; }
|
Vector& operator=(Vector&& a) noexcept {
|
||||||
|
delete elem;
|
||||||
|
elem = a.elem; a.elem = nullptr;
|
||||||
|
sz = a.sz; a.sz = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
// ...
|
// ...
|
||||||
private:
|
private:
|
||||||
T* elem;
|
T* elem;
|
||||||
@@ -6451,8 +6456,8 @@ These operations do not throw.
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
class Vector2 {
|
class Vector2 {
|
||||||
public:
|
public:
|
||||||
Vector2(Vector2&& a) { *this = a; } // just use the copy
|
Vector2(Vector2&& a) noexcept { *this = a; } // just use the copy
|
||||||
Vector2& operator=(Vector2&& a) { *this = a; } // just use the copy
|
Vector2& operator=(Vector2&& a) noexcept { *this = a; } // just use the copy
|
||||||
// ...
|
// ...
|
||||||
private:
|
private:
|
||||||
T* elem;
|
T* elem;
|
||||||
@@ -6555,8 +6560,8 @@ The compiler is more likely to get the default semantics right and you cannot im
|
|||||||
|
|
||||||
Tracer(const Tracer&) = default;
|
Tracer(const Tracer&) = default;
|
||||||
Tracer& operator=(const Tracer&) = default;
|
Tracer& operator=(const Tracer&) = default;
|
||||||
Tracer(Tracer&&) = default;
|
Tracer(Tracer&&) noexcept = default;
|
||||||
Tracer& operator=(Tracer&&) = default;
|
Tracer& operator=(Tracer&&) noexcept = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
Because we defined the destructor, we must define the copy and move operations. The `= default` is the best and simplest way of doing that.
|
Because we defined the destructor, we must define the copy and move operations. The `= default` is the best and simplest way of doing that.
|
||||||
@@ -6571,8 +6576,8 @@ Because we defined the destructor, we must define the copy and move operations.
|
|||||||
|
|
||||||
Tracer2(const Tracer2& a) : message{a.message} {}
|
Tracer2(const Tracer2& a) : message{a.message} {}
|
||||||
Tracer2& operator=(const Tracer2& a) { message = a.message; return *this; }
|
Tracer2& operator=(const Tracer2& a) { message = a.message; return *this; }
|
||||||
Tracer2(Tracer2&& a) :message{a.message} {}
|
Tracer2(Tracer2&& a) noexcept :message{a.message} {}
|
||||||
Tracer2& operator=(Tracer2&& a) { message = a.message; return *this; }
|
Tracer2& operator=(Tracer2&& a) noexcept { message = a.message; return *this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
Writing out the bodies of the copy and move operations is verbose, tedious, and error-prone. A compiler does it better.
|
Writing out the bodies of the copy and move operations is verbose, tedious, and error-prone. A compiler does it better.
|
||||||
@@ -6966,9 +6971,9 @@ In particular, `std::vector` and `std::map` provide useful relatively simple mod
|
|||||||
Sorted_vector() = default;
|
Sorted_vector() = default;
|
||||||
Sorted_vector(initializer_list<T>); // initializer-list constructor: sort and store
|
Sorted_vector(initializer_list<T>); // initializer-list constructor: sort and store
|
||||||
Sorted_vector(const Sorted_vector&) = default;
|
Sorted_vector(const Sorted_vector&) = default;
|
||||||
Sorted_vector(Sorted_vector&&) = default;
|
Sorted_vector(Sorted_vector&&) noexcept = default;
|
||||||
Sorted_vector& operator=(const Sorted_vector&) = default; // copy assignment
|
Sorted_vector& operator=(const Sorted_vector&) = default; // copy assignment
|
||||||
Sorted_vector& operator=(Sorted_vector&&) = default; // move assignment
|
Sorted_vector& operator=(Sorted_vector&&) noexcept = default; // move assignment
|
||||||
~Sorted_vector() = default;
|
~Sorted_vector() = default;
|
||||||
|
|
||||||
Sorted_vector(const std::vector<T>& v); // store and sort
|
Sorted_vector(const std::vector<T>& v); // store and sort
|
||||||
@@ -7537,7 +7542,7 @@ For example, `center` has to be implemented by every class derived from `Shape`.
|
|||||||
|
|
||||||
##### Example, dual hierarchy
|
##### Example, dual hierarchy
|
||||||
|
|
||||||
How can we gain the benefit of stable hierarchies from implementation hierarchies and the benefit of implementation reuse from implementation inheritance?
|
How can we gain the benefit of stable hierarchies from interface hierarchies and the benefit of implementation reuse from implementation inheritance?
|
||||||
One popular technique is dual hierarchies.
|
One popular technique is dual hierarchies.
|
||||||
There are many ways of implementing the idea of dual hierarchies; here, we use a multiple-inheritance variant.
|
There are many ways of implementing the idea of dual hierarchies; here, we use a multiple-inheritance variant.
|
||||||
|
|
||||||
@@ -7668,8 +7673,8 @@ Copying a polymorphic class is discouraged due to the slicing problem, see [C.67
|
|||||||
protected:
|
protected:
|
||||||
B(const B&) = default;
|
B(const B&) = default;
|
||||||
B& operator=(const B&) = default;
|
B& operator=(const B&) = default;
|
||||||
B(B&&) = default;
|
B(B&&) noexcept = default;
|
||||||
B& operator=(B&&) = default;
|
B& operator=(B&&) noexcept = default;
|
||||||
// ...
|
// ...
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8202,8 +8207,8 @@ Consider:
|
|||||||
cout << pb2->id(); // "D"
|
cout << pb2->id(); // "D"
|
||||||
|
|
||||||
|
|
||||||
if (pb1->id() == "D") { // looks innocent
|
if (pb2->id() == "D") { // looks innocent
|
||||||
D* pd = static_cast<D*>(pb1);
|
D* pd = static_cast<D*>(pb2);
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
@@ -8866,7 +8871,7 @@ The C++17 `variant` type (found in `<variant>`) does that for you:
|
|||||||
v = 123; // v holds an int
|
v = 123; // v holds an int
|
||||||
int x = get<int>(v);
|
int x = get<int>(v);
|
||||||
v = 123.456; // v holds a double
|
v = 123.456; // v holds a double
|
||||||
w = get<double>(v);
|
double w = get<double>(v);
|
||||||
|
|
||||||
##### Enforcement
|
##### Enforcement
|
||||||
|
|
||||||
@@ -11486,7 +11491,7 @@ Requires messy cast-and-macro-laden code to get working right.
|
|||||||
error(7, "this", "is", "an", "error"); // crash
|
error(7, "this", "is", "an", "error"); // crash
|
||||||
const char* is = "is";
|
const char* is = "is";
|
||||||
string an = "an";
|
string an = "an";
|
||||||
error(7, "this", "is", an, "error"); // crash
|
error(7, "this", is, an, "error"); // crash
|
||||||
}
|
}
|
||||||
|
|
||||||
**Alternative**: Overloading. Templates. Variadic templates.
|
**Alternative**: Overloading. Templates. Variadic templates.
|
||||||
@@ -12469,7 +12474,7 @@ Flag naked `new`s and naked `delete`s.
|
|||||||
|
|
||||||
##### Reason
|
##### Reason
|
||||||
|
|
||||||
That's what the language requires and mistakes can lead to resource release errors and/or memory corruption.
|
That's what the language requires, and mismatches can lead to resource release errors and/or memory corruption.
|
||||||
|
|
||||||
##### Example, bad
|
##### Example, bad
|
||||||
|
|
||||||
@@ -12486,8 +12491,8 @@ This example not only violates the [no naked `new` rule](#Res-new) as in the pre
|
|||||||
|
|
||||||
##### Enforcement
|
##### Enforcement
|
||||||
|
|
||||||
* If the `new` and the `delete` are in the same scope, mistakes can be flagged.
|
* Flag mismatched `new` and `delete` if they are in the same scope.
|
||||||
* If the `new` and the `delete` are in a constructor/destructor pair, mistakes can be flagged.
|
* Flag mismatched `new` and `delete` if they are in a constructor/destructor pair.
|
||||||
|
|
||||||
### <a name="Res-arr2"></a>ES.62: Don't compare pointers into different arrays
|
### <a name="Res-arr2"></a>ES.62: Don't compare pointers into different arrays
|
||||||
|
|
||||||
@@ -13430,8 +13435,8 @@ Unsigned types support bit manipulation without surprises from sign bits.
|
|||||||
|
|
||||||
##### Note
|
##### Note
|
||||||
|
|
||||||
Unsigned types can also be useful for modulo arithmetic.
|
Unsigned types can also be useful for modular arithmetic.
|
||||||
However, if you want modulo arithmetic add
|
However, if you want modular arithmetic add
|
||||||
comments as necessary noting the reliance on wraparound behavior, as such code
|
comments as necessary noting the reliance on wraparound behavior, as such code
|
||||||
can be surprising for many programmers.
|
can be surprising for many programmers.
|
||||||
|
|
||||||
@@ -13445,7 +13450,7 @@ can be surprising for many programmers.
|
|||||||
##### Reason
|
##### Reason
|
||||||
|
|
||||||
Because most arithmetic is assumed to be signed;
|
Because most arithmetic is assumed to be signed;
|
||||||
`x - y` yields a negative number when `y > x` except in the rare cases where you really want modulo arithmetic.
|
`x - y` yields a negative number when `y > x` except in the rare cases where you really want modular arithmetic.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
@@ -13475,7 +13480,7 @@ but if you had seen `us - (s + 2)` or `s += 2; ...; us - s`, would you reliably
|
|||||||
|
|
||||||
##### Exception
|
##### Exception
|
||||||
|
|
||||||
Use unsigned types if you really want modulo arithmetic - add
|
Use unsigned types if you really want modular arithmetic - add
|
||||||
comments as necessary noting the reliance on overflow behavior, as such code
|
comments as necessary noting the reliance on overflow behavior, as such code
|
||||||
is going to be surprising for many programmers.
|
is going to be surprising for many programmers.
|
||||||
|
|
||||||
@@ -13534,7 +13539,7 @@ Incrementing a value beyond a maximum value can lead to memory corruption and un
|
|||||||
|
|
||||||
##### Exception
|
##### Exception
|
||||||
|
|
||||||
Use unsigned types if you really want modulo arithmetic.
|
Use unsigned types if you really want modular arithmetic.
|
||||||
|
|
||||||
**Alternative**: For critical applications that can afford some overhead, use a range-checked integer and/or floating-point type.
|
**Alternative**: For critical applications that can afford some overhead, use a range-checked integer and/or floating-point type.
|
||||||
|
|
||||||
@@ -13559,7 +13564,7 @@ Decrementing a value beyond a minimum value can lead to memory corruption and un
|
|||||||
|
|
||||||
##### Exception
|
##### Exception
|
||||||
|
|
||||||
Use unsigned types if you really want modulo arithmetic.
|
Use unsigned types if you really want modular arithmetic.
|
||||||
|
|
||||||
##### Enforcement
|
##### Enforcement
|
||||||
|
|
||||||
@@ -13609,7 +13614,7 @@ This also applies to `%`.
|
|||||||
|
|
||||||
##### Reason
|
##### Reason
|
||||||
|
|
||||||
Choosing `unsigned` implies many changes to the usual behavior of integers, including modulo arithmetic,
|
Choosing `unsigned` implies many changes to the usual behavior of integers, including modular arithmetic,
|
||||||
can suppress warnings related to overflow,
|
can suppress warnings related to overflow,
|
||||||
and opens the door for errors related to signed/unsigned mixes.
|
and opens the door for errors related to signed/unsigned mixes.
|
||||||
Using `unsigned` doesn't actually eliminate the possibility of negative values.
|
Using `unsigned` doesn't actually eliminate the possibility of negative values.
|
||||||
@@ -13631,7 +13636,7 @@ Consider:
|
|||||||
auto a = area(height, 2); // if the input is -2 a becomes 4294967292
|
auto a = area(height, 2); // if the input is -2 a becomes 4294967292
|
||||||
|
|
||||||
Remember that `-1` when assigned to an `unsigned int` becomes the largest `unsigned int`.
|
Remember that `-1` when assigned to an `unsigned int` becomes the largest `unsigned int`.
|
||||||
Also, since unsigned arithmetic is modulo arithmetic the multiplication didn't overflow, it wrapped around.
|
Also, since unsigned arithmetic is modular arithmetic the multiplication didn't overflow, it wrapped around.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
@@ -17519,7 +17524,7 @@ and should be used only as building blocks for meaningful concepts, rather than
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
// bad; insufficient
|
// bad; insufficient
|
||||||
concept Addable = requires(T a, T b) { a+b; };
|
concept Addable = requires(T a, T b) { a + b; };
|
||||||
|
|
||||||
template<Addable N>
|
template<Addable N>
|
||||||
auto algo(const N& a, const N& b) // use two numbers
|
auto algo(const N& a, const N& b) // use two numbers
|
||||||
@@ -17547,7 +17552,7 @@ The ability to specify meaningful semantics is a defining characteristic of a tr
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
// The operators +, -, *, and / for a number are assumed to follow the usual mathematical rules
|
// The operators +, -, *, and / for a number are assumed to follow the usual mathematical rules
|
||||||
concept Number = requires(T a, T b) { a+b; a-b; a*b; a/b; };
|
concept Number = requires(T a, T b) { a + b; a - b; a * b; a / b; };
|
||||||
|
|
||||||
template<Number N>
|
template<Number N>
|
||||||
auto algo(const N& a, const N& b)
|
auto algo(const N& a, const N& b)
|
||||||
@@ -17588,7 +17593,7 @@ This is a specific variant of the general rule that [a concept must make semanti
|
|||||||
|
|
||||||
##### Example, bad
|
##### Example, bad
|
||||||
|
|
||||||
template<typename T> concept Subtractable = requires(T a, T b) { a-b; };
|
template<typename T> concept Subtractable = requires(T a, T b) { a - b; };
|
||||||
|
|
||||||
This makes no semantic sense.
|
This makes no semantic sense.
|
||||||
You need at least `+` to make `-` meaningful and useful.
|
You need at least `+` to make `-` meaningful and useful.
|
||||||
@@ -17678,10 +17683,10 @@ Specifying semantics is a powerful design tool.
|
|||||||
// The operators +, -, *, and / for a number are assumed to follow the usual mathematical rules
|
// The operators +, -, *, and / for a number are assumed to follow the usual mathematical rules
|
||||||
// axiom(T a, T b) { a + b == b + a; a - a == 0; a * (b + c) == a * b + a * c; /*...*/ }
|
// axiom(T a, T b) { a + b == b + a; a - a == 0; a * (b + c) == a * b + a * c; /*...*/ }
|
||||||
concept Number = requires(T a, T b) {
|
concept Number = requires(T a, T b) {
|
||||||
{a + b} -> convertible_to<T>;
|
{ a + b } -> convertible_to<T>;
|
||||||
{a - b} -> convertible_to<T>;
|
{ a - b } -> convertible_to<T>;
|
||||||
{a * b} -> convertible_to<T>;
|
{ a * b } -> convertible_to<T>;
|
||||||
{a / b} -> convertible_to<T>;
|
{ a / b } -> convertible_to<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
##### Note
|
##### Note
|
||||||
@@ -18966,7 +18971,7 @@ Write your own "advanced TMP support" only if you really have to.
|
|||||||
|
|
||||||
## <a name="SS-temp-other"></a>Other template rules
|
## <a name="SS-temp-other"></a>Other template rules
|
||||||
|
|
||||||
### <a name="Rt-name"></a>T.140: If an operation can be reused, give it a name](#Rt-name
|
### <a name="Rt-name"></a>T.140: If an operation can be reused, give it a name
|
||||||
|
|
||||||
See [F.10](#Rf-name)
|
See [F.10](#Rf-name)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user