2. Standard Attributes
Code annotations - attributes - are probably not the best-known feature of C++. However, they might be handy for expressing additional information for the compiler and also for other programmers. Since C++11, there has been a standard way of specifying attributes. And in C++17 we got even more useful additions.
In this chapter, you’ll learn:
- What are the attributes in C++
- Vendor-specific code annotations vs the Standard form
- In what cases attributes are handy
- C++11 and C++14 attributes
- New additions in C++17
Why Do We Need Attributes?
Have you ever used __declspec, __attribute__ or #pragma directives in your code?
For example:
// set an alignment
struct S { short f[3]; } __attribute__ ((aligned (8)));
// this function won't return
void fatal () __attribute__ ((noreturn));
Or for DLL import/export in MSVC:
#if COMPILING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
Those are existing forms of compiler-specific attributes/annotations.
So what is an attribute?
An attribute is additional information that can be used by the compiler to produce code. It might be utilised for optimisation or some specific code generation (like DLL stuff, OpenMP, etc.). Also, annotations allow you to write more expressive syntax and help other developers to reason about code.
Contrary to other languages such as C#, in C++, the compiler has fixed the meta-information system. You cannot add user-defined attributes. In C# you can derive from System.Attribute.
What’s best about Modern C++ attributes?
Since C++11, we get more and more standardised attributes that will work with other compilers. We’re moving away from compiler-specific annotation to standard forms. Rather than learning various annotation syntaxes you’ll be able to write code that is common and has the same behaviour.
In the next section, you’ll see how attributes used to work before C++11.
Before C++11
In the era of C++98/03, each compiler introduced its own set of annotations, usually with a different keyword.
Often, you could see code with #pragma, __declspec, __attribute spread throughout the code.
Here’s the list of the common syntax from GCC/Clang and MSVC:
GCC Specific Attributes
GCC uses annotation in the form of __attribute__((attr_name)). For example:
int square (int) __attribute__ ((pure)); // pure function
Documentation:
- Attribute Syntax - Using the GNU Compiler Collection (GCC)
- Using the GNU Compiler Collection (GCC): Common Function Attributes
MSVC Specific Attributes
Microsoft mostly used __declspec keyword, as their syntax for various compiler extensions. See the documentation here: __declspec Microsoft Docs.
__declspec(deprecated) void LegacyCode() { }
Clang Specific Attributes
Clang, as it’s straightforward to customise, can support different types of annotations, so look at the documentation to find more. Most of GCC attributes work with Clang.
See the documentation here: Attributes in Clang — Clang documentation.
Attributes in C++11 and C++14
C++11 took one big step to minimise the need to use vendor-specific syntax. By introducing the standard format, we can move a lot of compiler-specific attributes into the universal set.
C++11 provides a cleaner format of specifying annotations over our code.
The basic syntax is just [[attr]] or [[namespace::attr]].
You can use [[attr]] over almost anything: types, functions, enums, etc., etc.
For example:
[[attrib_name]] void foo() { } // on a function
struct [[deprecated]] OldStruct { } // on a struct
In C++11 we have the following attributes:
[[noreturn]] :
It tells the compiler that control flow will not return to the caller. Examples:
[[noreturn]] void terminate() noexcept;- functions like
std::abortorstd::exitare also marked with this attribute.
[[carries_dependency]] :
Indicates that the dependency chain in release-consume std::memory_order propagates in and out of the function, which allows the compiler to skip unnecessary memory fence instructions. Mostly to help to optimise multi-threaded code and when using different memory models.
C++14 added:
[[deprecated]] and [[deprecated("reason")]] :
Code marked with this attribute will be reported by the compiler. You can set its reason.
Example of [[deprecated]]:
[[deprecated("use AwesomeFunc instead")]] void GoodFunc() { }
// call somewhere:
GoodFunc();
GCC reports the following warning:
warning: 'void GoodFunc()' is deprecated: use AwesomeFunc instead
[-Wdeprecated-declarations]
You know a bit about the old approach, new way in C++11/14… so what’s the deal with C++17?
C++17 Additions
With C++17 we get three more standard attributes:
[[fallthrough]][[nodiscard]][[maybe_unused]]
Plus three supporting features:
- Attributes for Namespaces and Enumerators
- Ignore Unknown Attributes
- Using Attribute Namespaces Without Repetition
Let’s go through the new attributes first.
[[fallthrough]] Attribute
Indicates that a fall-through in a switch statement is intentional and a warning should not be issued for it.
switch (c) {
case 'a':
f(); // Warning! fallthrough is perhaps a programmer error
case 'b':
g();
[[fallthrough]]; // Warning suppressed, fallthrough is ok
case 'c':
h();
}
With this attribute, the compiler can understand the intentions of a programmer. It’s also much more readable than using a comment.
[[maybe_unused]] Attribute
Suppresses compiler warnings about unused entities:
static void impl1() { ... } // Compilers may warn when function not called
[[maybe_unused]] static void impl2() { ... } // Warning suppressed
void foo() {
int x = 42; // Compilers may warn when x is not used later
[[maybe_unused]] int y = 42; // Warning suppressed for y
}
Such behaviour is helpful when some of the variables and functions are used in debug only path. For example in assert() macros;
void doSomething(std::string_view a, std::string_view b) {
assert(a.size() < b.size());
}
If later a or b is no used in this function, then the compiler will generate a warning in release only builds. Marking the given argument with [[maybe_unused]] will solve this warning.
[[nodiscard]] Attribute
[[nodiscard]] can be applied to a function or a type declaration to mark the importance of the returned value:
[[nodiscard]] int Compute();
void Test() {
Compute(); // Warning! return value of a
// nodiscard function is discarded
}
If you forget to assign the result to a variable, then the compiler should emit a warning.
What it means is that you can force users to handle errors. For example, what happens if you forget about using the return value from new or std::async()?
Additionally, the attribute can be applied to types. One use case for it might be error codes:
enum class [[nodiscard]] ErrorCode {
OK,
Fatal,
System,
FileIssue
};
ErrorCode OpenFile(std::string_view fileName);
ErrorCode SendEmail(std::string_view sendto,
std::string_view text);
ErrorCode SystemCall(std::string_view text);
Now, every time you’d like to call such functions, you’re “forced” to check the return value. For important functions checking return codes might be crucial and using [[nodiscard]] might save you from a few bugs.
You might also ask what it means “not to use” a return value?
In the Standard, it’s defined as “Discarded-value expressions”. It means that you call a function only for its side effects. In other words, there’s no if statement around or an assignment expression. In that case, when a type is marked as [[nodiscard]] the compiler is encouraged to report a warning.
However, to suppress the warning you can explicitly cast the return value to void or use [[maybe_unused]]:
[[nodiscard]] int Compute();
void Test() {
static_cast<void>(Compute()); // fine...
[[maybe_unused]] auto ret = Compute();
}
Attributes for Namespaces and Enumerators
The idea for attributes in C++11 was to be able to apply them to all sensible places like classes, functions, variables, typedefs, templates, enumerations… But there was an issue in the specification that blocked attributes when they were applied on namespaces or enumerators.
This is now fixed in C++17. We can now write:
namespace [[deprecated("use BetterUtils")]] GoodUtils {
void DoStuff() { }
}
namespace BetterUtils {
void DoStuff() { }
}
// use:
GoodUtils::DoStuff();
Clang reports:
warning: 'GoodUtils' is deprecated: use BetterUtils
[-Wdeprecated-declarations]
Another example is the use of deprecated attribute on enumerators:
enum class ColorModes {
RGB [[deprecated("use RGB8")]],
RGBA [[deprecated("use RGBA8")]],
RGB8,
RGBA8
};
// use:
auto colMode = ColorModes::RGBA;
Under GCC we’ll get:
warning: 'RGBA' is deprecated: use RGBA8
[-Wdeprecated-declarations]
Ignore Unknown Attributes
The feature is mostly for clarification.
Before C++17, if you tried to use some compiler-specific attribute, you might even get an error when compiling in another compiler that doesn’t support it. Now, the compiler omits the attribute specification and won’t report anything (or just a warning). This wasn’t mentioned in the Standard, and it needed clarification.
// compilers which don't
// support MyCompilerSpecificNamespace will ignore this attribute
[[MyCompilerSpecificNamespace::do_special_thing]]
void foo();
For example in GCC 7.1 there’s a warnings:
warning: 'MyCompilerSpecificNamespace::do_special_thing'
scoped attribute directive ignored [-Wattributes]
void foo();
Using Attribute Namespaces Without Repetition
The feature simplifies the case where you want to use multiple attributes, like:
void f() {
[[rpr::kernel, rpr::target(cpu,gpu)]] // repetition
doTask();
}
Proposed change:
void f() {
[[using rpr: kernel, target(cpu,gpu)]]
doTask();
}
That simplification might help when building tools that automatically translate annotated code of that type into different programming models.
Section Summary
Attributes available in C++17:
| Attribute | Description |
|---|---|
[[noreturn]] |
a function does not return to the caller |
[[carries_dependency]] |
extra information about dependency chains |
[[deprecated]] |
an entity is deprecated |
[[deprecated("reason")]] |
provides additional message about the deprecation |
[[fallthrough]] |
indicates a intentional fall-through in a switch statement |
[[nodiscard]] |
a warning is generated if the return value is discarded |
[[maybe_unused]] |
an entity might not be used in the code |
Each compiler vendor can specify their syntax for attributes and annotations. In Modern C++, the ISO Committee tries to extract common parts and standardise it as [[attributes]].
There’s also a relevant quote from Bjarne Stroustrup’s C++11 FAQ about suggested use:
There is a reasonable fear that attributes will be used to create language dialects. The recommendation is to use attributes to only control things that do not affect the meaning of a program but might help detect errors (e.g.
[[noreturn]]) or help optimisers (e.g.[[carries_dependency]]).
Compiler support
| Feature | GCC | Clang | MSVC |
|---|---|---|---|
[[fallthrough]] |
7.0 | 3.9 | 15.0 |
[[nodiscard]] |
7.0 | 3.9 | 15.3 |
[[maybe_unused]] |
7.0 | 3.9 | 15.3 |
| Attributes for namespaces and enumerators | 4.9/61 | 3.4 | 14.0 |
| Ignore unknown attributes | All versions | 3.9 | 14.0 |
| Using attribute namespaces without repetition | 7.0 | 3.9 | 15.3 |
All of the above compilers also support C++11/14 attributes.