Using C++17 Attributes Today
C++11 added generalized attributes to the standard, a way to specify extra
information to the compiler about an expression. Previously different compilers
exposed a different mechanism to achieve this goal,
__attribute__((deprecated))
on GCC/clang and
__declspec(deprecated)
in Visual C++. Generalized attributes, or attribute
specifier sequences, allow you to express this is the compiler agnostic form
[[deprecated]]
.
The
depcrecated
attribute was standardized in C++14 however GCC, clang, and Visual C++ already supported it in some form. For portable C++11 code the simple form must be used, this does not take a string describing of why the feature was deprecated.
New Attributes in C++17
With C++17 three additional attributes were added to the standard which enable additional compiler warnings when used in general code.
nodiscard
specifies that a return value should not be discardedfallthrough
indicates to the compiler that you intended a case in a switch statement to fall-through, requires an optional warning to be enabledmaybe_unused
silences warnings generated when a variable is only used in an assertion
Unfortunately at the time of writing C++17 compilers are not available yet. Even if they were it still could be many years before developers actually get to use one in a production environment. Happily the C++ standards committee did not pull these concepts out of thin air, in fact some compilers support equivalent attributes.
How To Use Them
Here’s the caveat, I will be focusing on attributes provided by GCC and clang. If you use one of these compilers for development or support them in your continuous integration system you can still reap the benefits.
Attribute Detection
Since not all compilers will support these attributes we need a mechanism to
detect if any given attribute is supported. Fortunately clang and
GCC provide such a mechanism, __has_cpp_attribute
according to these
guidelines.
Support was added to clang in version 3.6 and GCC
in version 5.
To use the __has_cpp_attribute
function like macro at the same time as
targeting compilers which don’t support it yet the following guarded definition
is useful as described
here.
#ifndef __has_cpp_attribute
#define __has_cpp_attribute(name) 0
#endif
Using the nodiscard Attribute
It is not unusual to disable exceptions in a project, usually because of the
performance implications when an error occurs. This is common practice in game
development and in the LLVM compiler infrastructure project, which
clang is apart of. Usually in such code bases error codes are used to
notify the caller of a function that a recoverable error has occurred. It is
however easy to forget to check error codes, this is where the nodiscard
attribute comes in.
Specifying the nodiscard
attribute can be done on a function definition.
int [[nodiscard]] foo() { /* ... */ }
foo(); // warning: ignoring return value of function declared
// with warn_unused_result attribute
But if you are using a class for you error reporting the attribute can be specified on the class definition in just one place instead.
class [[nodiscard]] error { /* ... */ };
error bar();
bar(); // warning: ignoring return value of function declared
// with warn_unused_result attribute
This is especially useful when compiling with warnings as errors as now it is possible to ensure error conditions are being handled at compile time.
The nodiscard
attribute came to life with another name
gnu::warn_unused_result
which was introduced to GCC a number of years
ago and to clang in version 3.6. Using attribute detection
now we are able to use nodiscard
functionality on a larger proportion of
currently available compilers and projects and use the C++17 attributes when
available.
#if __has_cpp_attribute(nodiscard)
#define NODISCARD [[nodiscard]]
#elif __has_cpp_attribute(gnu::warn_unused_result)
#define NODISCARD [[gnu::warn_unused_result]]
#else
#define NODISCARD
#endif
Now we can use this to update the definitions from the example above.
int NODISCARD foo() { /* ... */ }
class NODISCARD error { /* ... */ };
Using the fallthrough Attribute
An common gripe with switch
statements is that adding break
to a case
is
not implicit. Whilst the language doesn’t support implicitly breaking the
compiler can help. The fallthrough
attribute originated in clang with
the name clang::fallthrough
in version 3.6. GCC will add
support in version 7 which is still in development at the time of writing.
#if __has_cpp_attribute(fallthrough)
#define FALLTHROUGH [[fallthrough]]
#elif __has_cpp_attribute(clang::fallthrough)
#define FALLTHROUGH [[clang::fallthrough]]
#else
#define FALLTHROUGH
#endif
To take advantage of the fallthrough
attribute the -Wimplicit-fallthrough
warning must be enabled. Once we have this warning enabled case
statements
which do not contain a break
or the fallthrough
attribute will generate a
warning.
enum class option { A, B, C };
void choice(option value) {
switch (value) {
case option::A:
// ...
case option::B: // warning: unannotated fall-through between
// switch labels
// ...
FALLTHROUGH;
case option::C: // no warning
// ...
break;
}
}
Note that the
fallthrough
attribute must be a null statement, that is it must be an expression on its own line at the end of thecase
statement.
Using the maybe_unused Attribute
It is common practice to sprinkle code with assert
's to ensure you catch
unexpected inputs during development. Sometimes this results in a variable being
created which is only used with in the assert
itself. This can result in
warnings being produced when assertions are disabled, I’ve seen this break the
build many times when compiling with warnings as errors. Silencing these
warnings is the intention of the maybe_unused
attribute.
But wait, there is already a solution for this. The traditional way to silence these warnings is to do the following.
int value = 42;
(void)value;
assert(value == 42);
To get the same behaviour using maybe_unused
we don’t need a separate
statement, but first here is the detection code.
#if __has_cpp_attribute(maybe_unused)
#define MAYBE_UNUSED [[maybe_unused]]
#elif __has_cpp_attribute(gnu::unused)
#define MAYBE_UNUSED [[gnu::unused]]
#else
#define MAYBE_UNUSED
#endif
Now to disable the unused variable warning when assertions are disabled do the following with one line of code less whilst adding also readability.
MAYBE_UNUSED int value = 42;
assert(value == 42);
Conclusion
Compiler support for C++17 standard attributes should be coming along soon, in fact version 3.9 of clang has enabled them in C++11 mode and upwards. For those without access to the latest compilers it can still be possible to get access to these little known but useful features described here.