What Is an Attribute Template?
In modern C++ (starting with the C++20 standard) a new kind of attribute was introduced: the attribute template. Traditional attributes are simple tokens placed inside double square brackets, for example [[nodiscard]] or [[deprecated]]. An attribute template extends this idea by allowing the attribute to be a class template that can be instantiated with one or more arguments. The syntax looks like a regular attribute, but the identifier may be followed by a templateargument list:
// Example of a template attribute[[my_namespace::trace<int, double>]]void foo(); The compiler treats the whole expression inside the brackets as a single attribute token. The template is then instantiated during compilation, and the resulting type can be queried by the compiler or by tooling that understands the attribute.
Why Were Attribute Templates Added?
Several motivations drove the inclusion of attribute templates in the language:
- Expressiveness: Developers can convey richer information to the compiler without inventing a multitude of separate attributes.
- Reusability: A single template can be reused across many places, simply by changing its arguments.
- Compiletime computation: Because the template arguments are evaluated at compile time, the attribute can encode values that affect optimisation, diagnostics, or code generation.
- Consistency with existing templates: The same syntax and rules that govern class templates now also apply to attributes, reducing the learning curve.
In practice this means a library author can provide a single, highly configurable attribute that replaces dozens of specialised attributes that would otherwise be needed.
Basic Syntax and Rules
The grammar for an attribute template follows the normal attribute syntax with a few additions:
- The identifier before the optional argument list must refer to a
classorstructthat has been declared as an attribute template (typically with the[[using]]attribute). - The argument list, if present, must be a wellformed templateargumentlist as defined in the standard.
- Only the primary template may be used; partial specialisations are not considered for attribute matching.
- An attribute template may be placed in any attributespecifiersequence, just like a nontemplate attribute.
Declaring an attribute template is straightforward. The standard library itself uses this mechanism for the [[likely]] and [[unlikely]] attributes in some implementations, though they are not templates. Userdefined examples look like this:
namespace my_namespace { template<typename T, int N> struct trace { // The body is usually empty; the presence of the template // is enough for the compiler to recognise the attribute. };}// Register the template as a valid attribute[[using my_namespace::trace]]; Practical Use Cases
1. Compiletime Logging and Tracing
Imagine you want to annotate functions with a trace level and a tag that a static analysis tool will later read. With a traditional approach you would need a separate attribute for each level, e.g., [[trace_debug]], [[trace_info]], etc. Using an attribute template you can collapse them into a single definition:
template<int Level, typename Tag>struct trace_attr { };[[using trace_attr]];// Usage[[trace_attr<2, struct MyTag>]]int compute(int x); The static analyser can retrieve Level and Tag at compile time and decide whether to emit extra diagnostics or generate additional instrumentation code.
2. PlatformSpecific Optimisations
Suppose a library provides a vector implementation that can be tuned for different SIMD widths. An attribute template can convey the required width to the compiler:
template<int SimdWidth>struct simd_opt { };[[using simd_opt]];[[simd_opt<256>]]void fast_transform(float* data, std::size_t n); A compiler that recognises simd_opt could then generate AVX256 instructions, while other compilers may simply ignore the attribute.
3. PolicyBased Design
In policybased class templates it is common to pass a policy type that changes behaviour. By turning the policy into an attribute template you can attach the policy directly to a function or class without affecting its type signature:
template<typename Policy>struct enforce_policy { };[[using enforce_policy]];[[enforce_policy<MyPolicy>]]void critical_section(); The implementation of critical_section can query the policy with __has_attribute (or a similar compilerspecific facility) and act accordingly.
Interaction with Existing Language Features
Attribute templates coexist with other attribute mechanisms:
- Standard attributes: You can combine a standard attribute and a template attribute in the same attributespecifiersequence:
[[nodiscard, my_namespace::trace<int>]]. - Attribute namespaces: As with regular attributes, the identifier can be qualified. The qualification is part of the attribute name, not a separate namespace mechanism.
- Macro expansion: Because the attribute syntax is not macroexpandable, you cannot generate an attribute template via a macro. However you can generate the surrounding code that contains the attribute.
- Reflection (C++23): The upcoming reflection facilities will be able to query attribute templates just like any other attribute, exposing the template arguments to the program.
Compiler Support
As of mid2024, the major compilers have the following status:
- Clang 15+ Full support for attribute templates, including the
[[using]]registration syntax. - GCC 13+ Supports attribute templates, though the registration is performed via a pragma:
#pragma attribute [[my_namespace::trace]]. - MSVC 19.35+ Implements attribute templates with the same
[[using]]syntax used by Clang.
When a compiler does not recognise a particular attribute template, the attribute is silently ignored, which matches the behaviour of ordinary unknown attributes.
Guidelines for Designing Attribute Templates
To make attribute templates useful and maintainable, follow these best practices:
- Keep the definition lightweight. The attribute struct should usually be empty; its purpose is to carry template arguments.
- Prefer constexpr values. If the attribute needs to expose a constant, make it a static constexpr member so that tools can read it without instantiating the full type.
- Document the semantics clearly. Because the attribute has no runtime effect by default, developers must rely on external documentation or tooling.
- Avoid side effects. The attributes template arguments should not trigger instantiation of heavy templates unless a compiler explicitly uses the attribute.
- Register with
[[using]]or an equivalent pragma. This step informs the compiler that the identifier is intended as an attribute template.
Future Directions
The C++ committee continues to explore extensions to the attribute system. Potential enhancements include:
- Allowing attribute templates to be partially specialised for specific argument patterns, enabling more finegrained control.
- Integrating attribute templates directly with the reflection TS so that programs can introspect their own attributes at compile time.
- Providing a standard library set of generic attribute templates for common tasks such as logging, tracing, and performance hints.
As the ecosystem matures, we can expect more libraries and frameworks to adopt attribute templates as a concise way to convey compiletime metadata.
Conclusion
Attribute templates are a powerful addition to modern C++, bridging the gap between traditional attributes and the expressive world of templates. By allowing compiletime parameters inside an attribute, they enable developers to write more declarative, reusable, and toolfriendly code. Whether you need finegrained tracing, platformspecific optimisation hints, or policy enforcement without cluttering signatures, attribute templates provide a clean solution that integrates seamlessly with existing language features and compiler diagnostics.
To start experimenting, declare a simple template struct, register it with [[using]], and attach it to a function. Observe how your chosen compiler reacts, and then gradually expand the design to suit the needs of your project.
