Understanding Attributes

The new C++11 standard includes the ability to specify “attributes” for various declarations. The concept of attributes will be familiar to you if you’ve done work in languages like C# or Java. However, there are major differences between C++ attributes and attributes from other languages. This blog posting will cover what this new feature for C++ means for everyday programmers.

Attributes are additional metadata that can be used to provide further information about programmatic entities. You can think of attributes like comments for compilers — they have no semantic meaning by themselves, but they can affect the way a compiler treats your source code by giving it more information. For instance, C++ has an attribute that allows the programmer to specify that a function will never actually return via normal execution flow (such as a return statement). Think about the function “abort” from the C runtime library — it will never return. Once you call abort, any code past the call to abort will never be executed. By allowing the programmer to specify that a function will never return via normal means allows the compiler to provide the programmer with more information about possible bugs in their code. If you have a call to printf following the call to abort, the compiler could (in theory) produce a warning about the call to printf being unreachable. Or the compiler could choose to optimize the function call itself. These actions have no semantic effect on your code, but they are hints to the compiler just the same.

In some programming languages, all attributes are user-defined. For instance, in C#, all attributes are a subclass of a well-known class called System.Attribute. (Granted, there is compiler support for attribute notation. But the attributes themselves are all user-definable, even if the CLR defines ones for the user.) These sort of attributes are interesting to the compiler as well as the programmer because they’re something that can be queried at runtime via introspection. For example, there is an ObsoleteAttribute class that the programmer can attach to code that they wish to mark as deprecated. The compiler can inspect this at compile time and issue warnings at the call site to tell the programmer they’re using a deprecated function. Or there is a SerializableAttribute class that can be queried at runtime when attempting to serialize or deserialize objects for transport.

In C++, attributes are compiler-defined. The programmer cannot define their own attributes for use at runtime. This is due to the fact that C++ does not retain attribute information for RTTI, so there is nothing for the programmer to query at runtime. Currently, the C++11 language defines only two attributes: noreturn and carries_dependency. There were some other attributes in earlier drafts of the specification (final, overrides, hides and base_check), but these were promoted (and somewhat renamed) from attributes to be contextual keywords. There was also one draft attribute (align) which was not really promoted but laterally moved for some strange reason (I’ll get to this momentarily).

The syntax for attributes in C++11 is: [[namespacename::attributename]], where the namespacename and :: are optional. So the built-in attributes will look like: [[noreturn]] or [[carries_dependency]]. However, the namespace qualifier is allowed so that compiler vendors can implement their own custom attributes as they see fit. This is both a blessing and a curse as it means compilers can implement more powerful attributes, but at the expense of code portability. So it goes…

One interesting thing to note about attributes is that two left square brackets is always interpreted as the start of an attribute, even when an attribute would be illegal. That may seem like a minor point, but with the inclusion of lambda functions, it does pose an interesting code gotcha:

someArray[[]{ return 0; }()] = 12; // Error!

Effectively, this means you cannot use lambda expressions as part of an array index. It’s not likely to be something you ever encounter, but it is something to be aware of (much like the olden days when you couldn’t have two right angle brackets closing a template argument).

So what are these attributes used for?

The noreturn attribute applied only to functions themselves, and is used to tell the compiler that a function will not return (note that throwing an exception is not the same thing as returning). So, for instance:

[[noreturn]] void someFunction() {
  throw "error";
}

This is a useful attribute for functions which are not expected to return normally because the compiler is allowed to issue warnings when a function that doesn’t return has paths which do return normally. But more importantly, it’s an obvious signal to the programmer that there’s something “special” about this function that they may need to pay more attention to. The following C++ standard library functions are marked as noreturn: abort, _Exit, exit, quick_exit, unexpected, terminate, rethrow_exception, and throw_with_nested.

The carries_dependency attribute is an optimization hint to the compiler pertaining to memory operations. It is applied to functions or function parameters to denote dependency propagation into and out of the function. When applied to the function, it is effectively being applied to the function’s return value. For instance:

[[carries_dependency]] int *foo();
void bar( [[carries_dependency]] int *f );

I am not an optimization expert by any stretch, but my understanding of why this is interesting has to do with weakly-ordered CPU architectures when passing memory locations between threads. Without information about the dependencies, the compiler has to generate memory fences so that the values will be fetched properly. With the dependency information, the compiler may be able to omit some of these expensive fences for more performant code. There’s a reasonable explanation of this feature over on Stack Overflow. Note that no standard library function make use of this attribute.

There is one more attribute I want to talk about. Remember how I mentioned something about an “align” attribute? In one of the later drafts for the specification, there was an [[align]] attribute that allowed the programmer to specify alignment information for class members. This attribute was removed in the final specification, but it was replaced by the alignas contextual keyword. However, alignas is still considered an attribute — just without the syntax of one! I’m uncertain of the reasoning behind this change, but I suspect it has something to do with the fact that the alignment attribute can take constant expressions.

The alignas meta-attribute can be applied to variables and class data members, but not to register variables, bitfields, function parameters or the catch clause formal parameter. It takes two forms: one is a simple identifier, and the other is a constant expression evaluating to an integer value. All three of the following declarations are aligned the same way:

alignas( sizeof( double ) ) int i;
alignas( double ) int j;
alignas( i ) int k;

The declaration for i uses an integer constexpr to set the alignment. The declarations for j and k use identifiers (one is a type identifier, the other is a variable identifier). In all three cases, the variables are aligned on 8-byte boundaries. The standard library uses the alignas meta-attribute on the aligned_storage type trait.

Hopefully you’ve gained a better understanding of attributes in C++11. I don’t think any of these are going to be used very often for most people’s code. However, I suspect that compiler extensions will provide more examples of what attributes can be used for. Attributes should never become a common-place feature within the language though, as the tendency is to promote them to be contextual keywords instead.

tl;dr: C++11 comes with new syntax to support attributes, as well as a few attributes you can use. They’re not something you’re likely to run across frequently, but you should understand what they are, and that they do not affect the semantics of your program.

This entry was posted in C/C++ and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *