Attributes is kind of a hidden language feature, but they do provide a nifty standardized way of specifying, well, attributes for various constructs that was previously only possible using compiler specific extensions.

Attributes

They were introduced with the C++11 revision of the C++ Standard and are described as (ยง7.6.1):

Attributes specify additional information for various source constructs such as types, variables, names, blocks, or translation units.

Traditionally each compiler have had their own way of specifying various extensions in code, such as clang’s __attribute__(), which can be used like this for disabling optimizations for a function:

__attribute__(optnone)
void SomeFunction()
{
    // ...
}

Microsoft’s compiler have a similar functionality in their __declspec extension keyword.

What attributes does is basically to unfify these under a standardized construct in the form of [[attribute-list]]. The C++ Standard does provide a few attributes, but most will be compiler specific and they are accessed using the compiler specific namespace.

The attributes defined by the C++ Standard are1:

  • [[noreturn]]
  • [[carries_dependency]]
  • [[deprecated]]
  • [[deprecated("text")]]

Of those the [[deprecated]] attribute is of particular interest. Every now and then an API has to change, but it can’t just change over night as would break a lot of client code. So there will be a phase where the old, deprecated, functionality is still supported but is recommended not to be used anymore. We can now easily let clients of our code know that:

// In library header file:

[[deprecated("to be replaced with more efficient Bar()")]]
void Foo();

// In client source code:

Foo();

Compiling that you’ll get an error2:

error: 'Foo' is deprecated: to be replaced with more efficient Bar()
       [-Werror,-Wdeprecated-declarations]

This is great! It’s now immediately obvious to clients that they are using a deprecated function that may soon be removed. They don’t have to remember to go through documentation, changelogs, or release notes (or worse a compilation error for missing function) to find that out.

The compiler specific attributes are specified in the same way, only prefixed by a compiler specific namespace. For instance, the clang attribute above can be specified like this:

[[clang::optnone]]
void SomeFunction()
{
    // ...
}

Unknown attributes are ignored but they do give warnings, and since you’re compiling with all warnings on as errors you might want to disable that particular one when compiling cross-platform code (that uses different attribute namespaces and attributes). In clang you do that with: -Wno-deprecated-declarations.

Alignment

With attributes also comes the ability to specify alignment for types or objects, and the ability to check alignment.

Alignments is primarily used for performance reasons to allow for optimal load and store operations for the CPU.

For example:

// 11 bytes. We'd like it to be aligned on a 16-byte boundary.
struct S {
    char p[3];
    char q[8];
};

S s;

printf("%p\n", (void*)&s);
printf("%ld\n", (long)&s & 15);

Outputs:

0x7fff5c402bc8
8

Unfortunately as can be seen by the output it’s not 16-byte aligned. This is where alignas comes in handy. If we change the definition of S to be:

struct alignas(16) S {
    char p[3];
    char q[8];
};

We get an output of:

0x7fff5a9d6bb0
0

Ah. Perfect. Except, that that check to see if we’re 16-byte aligned is not very pretty. Luckily there’s a alingof operator as well, and we can use it as:

// S defined as before.

printf("%ld\n", alingof(S));

Outputs:

16

Summary

Attributes provided a standardized way of accessing compiler specific extensions. The standard [[deprecated]] attribute is useful for letting clients know about imminent API changes.

  • C++ defines only a few attributes, most are compiler specific
  • alignas lets you align object on a specific byte boundary
  • alignof lets you check the alignment of a type

  1. A few more are defined in the C++17 revision of the Standard: [[fallthrough]], [[nodiscard]], [[maybe_unused]] ↩︎

  2. Always compile with all warnings enabled and treat them as errors. See the Embracing Compiler Errors For Fun And Profit post for more details ↩︎