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.

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