ch7.new_attributes
C++17 - The complete guide, Ch 7: New Attributes and Attribute Features
- Since C++11, it has been possible to specify attributes (formal annotations that enable or disable warnings).
- With C++17, new attributes were introduced. In addition, attributes can now be used in a few more places and with some additional convenience.
7.1 Attribute [[nodiscard]]
- The new attribute
[[nodiscard]]can be used to encourage warnings by the compiler if a return value of a function is not used (however, the compiler is not required to issue a warning). [[nodiscard]]should usually be used to signal misbehavior when return values are not used. The misbehavior might be:- Memory leaks, such as not using returned allocated memory
- Unexpected or non-intuitive behavior such as getting different/unexpected behavior when not using the return value
- Unnecessary overhead, such as calling something that is a no-op if the return value is not used
- Here are some examples of where using this attribute is useful:
- Functions that allocate resources that have to be released by another function call should be marked with
[[nodiscard]]. - A typical example would be a function to allocate memory, such as
malloc()or the member functionallocate()of allocators.
- Functions that allocate resources that have to be released by another function call should be marked with
- However, note that some functions might return a value meaning that no compensating call is necessary.
- For example, programmers call the C function
realloc()with a size of zero bytes to free memory so that the return values do not have to be saved to call free() later. - For this reason, marking realloc() with
[[nodiscard]]would be counterproductive.
- For example, programmers call the C function
- A good example of a function changing its behavior non-intuitively when not using the return value is
std::async()(introduced with C++11).std::async()starts a functionality asynchronously in the background and returns a handle that can be used to wait for the end of the started functionality (and get access to any return value or exception).- However, when the return value is not used the call becomes a synchronous call because the destructor of the unused return value is called immediately waiting for the end of the started functionality.
- Therefore, not using the return value silently contradicts the whole purpose of calling
std::async(). Compilers can warn about this whenstd::async()is marked with[[nodiscard]].
- Another example is the member function
empty(), which checks whether an object (container/string) has no elements. - Programmers pretty often call this function to “empty” the container (remove all elements)
- This incorrect application of empty() can often be detected because it does not use the return value.
- Therefore, marking the member function accordingly helps to detect such an logical error.
class MyContainer {
...
public:
[[nodiscard]] bool empty() const noexcept;
...
};
- If for whatever reason you do not want to use a return value marked with
[[nodiscard]]you can cast the return value tovoid:
(void)coll.empty(); // disable [[nodiscard]] warning
- Note that attributes in function declarations are not inherited if the functions are overwritten:
struct B {
[[nodiscard]] int* foo();
};
struct D : B {
int* foo();
};
B b;
b.foo(); // warning
(void)b.foo(); // no warning
D d;
d.foo(); // no warning
- Therefore, you have mark the derived member function with
[[nodiscard]]again (unless there is a reason that not using the return value makes sense in the derived class). - As usual, you can place an attribute that applies to a function in front of all declaration specifiers or after the function name:
class C {
[[nodiscard]] friend bool operator== (const C&, const C&);
friend bool operator!= [[nodiscard]] (const C&, const C&);
};
- Placing the attribute between
friendandboolor betweenboolandoperator==is not correct. - Although the language feature was introduced with C++17, it is not used in the standard library yet. The proposal to apply this feature there simply came too late for C++17. Therefore, one of the key motivations for this feature, adding it to the declaration of
std::async(), has not been done yet. - However, for all the examples discussed above, corresponding fixes will come with the next C++ 20.
- However, to make your code more portable, you should use
[[nodiscard]]instead of non-portable means (such as[[gnu:warn_unused_result]]for gcc and clang or_Check_return_for Visual C++) to mark functions accordingly. - When defining operator
new(), you should mark the functions with[[nodiscard]]as is done, for example, when defining a header file to track all calls ofnew.
7.2 Attribute [[maybe_unused]]
-
The new attribute maybe_unused can be used to avoid warnings by the compiler for not using a name or entity.
-
The attribute may be applied to the declaration of a class, a type definition with typedef or using, a variable, a non-static data member, a function, an enumeration type, or an enumerator (enumeration value).
-
One application is to name a parameter without (necessarily) using it:
void foo(int val, [[maybe_unused]] std::string msg) {
#ifdef DEBUG log(msg);
#endif
...
}
- Another example would be to have a member without using it:
class MyStruct {
char c;
int i;
[[maybe_unused]] char makeLargerSize[100];
...
};
- Note that you cannot apply
[[maybe_unused]]to a statement. Therefore, you cannot counter[[nodiscard]]with[[maybe_unused]]directly:
[[nodiscard]] void* foo();
int main() {
foo(); // WARNING: return value not used
[[maybe_unused]] foo(); // ERROR: attribute not allowed here
[[maybe_unused]] auto x = foo(); // OK
}
7.3 Attribute [[fallthrough]]
- The new attribute
[[fallthrough]]can be used to avoid warnings by the compiler for not having a break statement after a sequence of one or more case labels inside a switch statement. For example:
void commentPlace(int place) {
switch (place) {
case 1:
std::cout << "very ";
[[fallthrough]];
case 2:
std::cout << "well\n";
break;
default:
std::cout << "OK\n";
break;
}
}
- Note that the attribute has to be used in an empty statement. It must therefore end with a semicolon.
- Using the attribute as the last statement in a switch statement is not allowed.
7.4 General Attribute Extensions
The following features were enabled for attributes in general with C++17:
Attributes can now be used to mark namespaces.
- For example, you can now deprecate a namespace as follows:
namespace [[deprecated]] DraftAPI {
...
}
- This is also possible for inline and unnamed namespaces.
Attributes can now be used to mark enumerators (values of enumeration types).
- For example, you can introduce a new enumeration value as a replacement of an existing (now deprecated) enumeration value as follows:
enum class City {
Berlin = 0,
NewYork = 1,
Mumbai = 2,
Bombay [[deprecated]] = Mumbai,
...
};
- Here, both
MumbaiandBombayrepresent the same numeric code for a city but using Bombay is marked as deprecated. Note that for enumeration values, the attribute is placed behind the identifier.
User-defined attributes
- For user-defined attributes, which should usually be defined in their own namespace, you can now use a using prefix to avoid the repetition of the attribute namespace for each attribute. That is, instead of:
[[MyLib::WebService, MyLib::RestService, MyLib::doc("html")]] void foo();
// all specified with MyLib::
you can just write
[[using MyLib: WebService, RestService, doc("html")]] void foo();
// use using MyLib: once
Note that with a using prefix, using the namespace again is an error:
[[using MyLib: MyLib::doc("html")]] void foo(); // ERROR