1. Revision history
Since [P3385R6]:
-
Remove support for
[[ assume ]] -
Rebase wording
Since [P3385R5]:
-
Merge [P3678R0] into current paper following SG7 recommendation
-
Address feedback from Sofia
-
Remove
appertain -
Add
metafunctionhas_attribute
Since [P3385R4]:
-
Remove in-place splicer syntax
-
Add
metafunctionappertain -
Augment implementation feedback
Since [P3385R3]:
-
Rework wording around lookup of splice-expression
-
Update authors
-
Rebase on [P2996R9]
Since [P3385R2]:
-
Address scoped attributes
-
Address attribute argument clause
Since [P3385R1]:
-
Fix various issues with samples
-
Fix wording
-
Rebase on [P2996R11]
Since [P3385R0]:
-
Reword ignorability section to mention set of rules
-
Discuss argument clause in
equal operatorinfo -
Discuss appertaining to null statement in reflect expression
2. Introduction
Attributes are used to a great extent, and there is new attributes being added to the language somewhat regularly. As reflection makes its way into our standard, we are missing a way for generic code to look into the attributes appertaining to an entity. That is what this proposal aims to tackle by introducing the building blocks.
2.1. Motivation
A core motivation shows up when looking at design
namespace std :: meta { struct data_member_options { struct name_type { template < typename T > requires constructible_from < u8string , T > consteval name_type ( T && ); template < typename T > requires constructible_from < string , T > consteval name_type ( T && ); }; optional < name_type > name ; optional < int > alignment ; optional < int > bit_width ; bool no_unique_address = false; vector < info > annotations ; }; }
Here we have 2 attributes showing up in and . We have no way to appertain , , or any other attributes. Additionally, if one wants to explicitly tell their compiler to enforce this attribute, they may want to tag out of caution.
Synthesizing enumerators in [P4033R0] also leverage a generic way to carry attributes for the feature to be complete...
Having a uniform vehicle to solve this design concern is a major motivation for this paper, committee time should not be spent addressing "Should we add a field to data_member_options ?" everytime we think about standardizing an attribute.
We also expect a number of applications for attribute introspection to happen in the context of code generation [P2237R0], where for example, one may want to skip over members, explicitly tag python bindings with @ decorators, etc.
The following example demonstrates cloning an aggregate while leaving out any deprecated members:
constexpr auto ctx = std :: meta :: access_context :: current (); struct User { [[ deprecated ]] std :: string name ; [[ deprecated ]] std :: string country ; std :: string uuidv5 ; std :: string countryIsoCode ; }; template < class T > struct MigratedT { struct impl ; consteval { std :: vector < std :: meta :: info > migratedMembers = {}; for ( auto member : nonstatic_data_members_of ( ^^ T , ctx )) { if ( ! std :: meta :: has_attribute ( member , ^^ [[ deprecated ]])) { migratedMembers . push_back ( data_member_spec ( std :: meta :: type_of ( member ), {. name = std :: meta :: identifier_of ( member )} )); } } define_aggregate ( ^^ impl , migratedMembers ); } }; using MigratedUser = MigratedT < User >:: impl ; static_assert ( std :: meta :: nonstatic_data_members_of ( ^^ User , ctx ). size () == 4 ); static_assert ( std :: meta :: nonstatic_data_members_of ( ^^ MigratedUser , ctx ). size () == 2 ); int main () { MigratedUser newUser ; // Uncomment the following line to show the deprecated fields are gone // newUser.name = "bob"; // // error: no member named 'name' in 'MigratedT<User>::impl' // 142 | newUser.name = "bob"; // newUser . uuidv5 = "bob" ; }
link.
3. Scope
Before longer discussions, we can give a simplified view of what the initial support is expected to be
| Category | Sample | Rationale |
|---|---|---|
| 🟢 No argument |
| |
| 🟢 Trivial argument |
| |
| 🟡 Complex argument |
| Custom parsing rules for arguments |
| 🔴 Unknown |
| Undefined parsing |
Complex here refers handwaving-ly to our implementation experience dealing with positional arguments. Experienced implementers may feel differently, what remains true is that we want to leave the choice to implementers to opt out for problematic attributes.
3.1. Argument clause
Let us recap here what are the attributes 9.13 [dcl.attr] found in the standard and their argument clause
| Attribute | Argument-clause |
|---|---|
| assume | conditional-expression |
| deprecated | unevaluated-string |
| fallthrough | N/A |
| indeterminate | N/A |
| likely | N/A |
| maybe_unused | N/A |
| nodiscard | unevaluated-string |
| noreturn | N/A |
| no_unique_address | N/A |
| unlikely | N/A |
Feedback post Wroclaw was unanimous on treating the argument clause as a salient property (and so starting from the second revision ).
While it brings no concern for attributes like , it is more an open question [1] when it comes to an attribute like accepting an expression as argument… Should compare equal to ?
Ultimately to not force a particular strategy until progress is made on reflection of expressions, the current proposal does not allow .
In our experimental implementation, we show a possible strategy where we do not transform the expression into a canonical representation before evaluating equality via profiling and so is not the same as .
3.2. Support, optionality and self consistency
Here we introduce an alternative terminology to guide the conversation in the context of reflection and attributes
-
Unknown attributes are unsupported (e.g,
).[[ my :: attribute ]] -
Standard attributes with
argument are unsupported (e.g,conditional - expression ).[[ assume ]] -
Every other standard attributes are supported (e.g.,
)[[ nodiscard ]] -
Vendor specific attributes are conditionally (up to an implementation) supported (e.g.,
is supported,^^ [[ gnu :: constructor ( 100 )]] is not)^^ [[ clang :: availability ( macos , introduced = 10.4 , deprecated = 10.6 , obsoleted = 10.7 )]]
With this category in place, we make the following design choice
-
Creating reflection of unsupported attribute is ill-formed (diagnostic required)
-
does not return unsupported attributesattributes_of
This is done to allow implementers the room to grow the set of supported attributes w/o worrying about breaking code. The current practice around creating reflection of problematic constructs (such as ) is to be ill-formed and so we’ll follow that here.
Diagnostic in those cicrumstances are not hard to emit, and so we should do so.
4. Proposed Features
4.1. Reflection expression
Our proposal advocates to support reflect expression like
constexpr auto r = ^^ [[ nodiscard ( "keepMe" )]];
The result is a reflection value embedding salient property of the attribute which are the attribute namespace, token and the argument clause if any.
4.2. Metafunctions
We propose to add a couple of metafunctions to what is available in . In addition, we will extend support to attributes in the other metafunctions when it makes sense.
4.2.1. attributes_of
namespace std :: meta { consteval auto attributes_of ( info construct ) -> vector < info > ; }
returns a vector of reflections representing all individual attributes that appertain to .
Simple example follows
enum class [[ nodiscard ( "Error discarded" )]] ErrorCode { Disconnected , ConfigurationIncorrect , OutdatedCredentials , }; static_assert ( attributes_of ( ^^ ErrorCode )[ 0 ] == ^^ [[ nodiscard ( "Error discarded" )]]);
In the case where an entity is legally redeclared with different attribute arguments, return one of those.
enum class ErrorCode ; enum class [[ nodiscard ( "Error discarded" )]] ErrorCode ; enum class [[ nodiscard ]] ErrorCode { Disconnected , ConfigurationIncorrect , OutdatedCredentials , }; // Either of [[nodiscard("Error discarded")]] or [[nodiscard]] static_assert ( attributes_of ( ^^ ErrorCode ). size () == 1 );
4.2.2. has_attribute
namespace std :: meta { enum class attribute_comparison { ignore_namespace , // Namespace is ignored during the comparison ignore_argument , // Arguments are ignored during the comparison }; consteval auto has_attribute ( info construct , info attribute ) -> bool ; consteval auto has_attribute ( info construct , info attribute , attribute_comparison policy ) -> bool ; }
returns true if the specified is found appertaining to , false otherwise.
Simple example follows
struct [[ clang :: consumable ( unconsumed )]] F { [[ clang :: callable_when ( unconsumed )]] void f () {} }; static_assert ( std :: meta :: has_attribute ( ^^ F :: f , ^^ [[ clang :: callable_when ( unconsumed )]]));
The overload with parameter allows a combo of flags to dictate what part of an attribute are meaningful to the comparison. This comes in handy when we want to find out an attribute, ignoring the vendor prefix and or the particular message that is being attached here and there.
[[ gnu :: deprecated ( "Standard deprecated" )]] void f () { } // Ignore both the namespace and the argument static_assert ( std :: meta :: has_attribute ( ^^ f , ^^ [[ deprecated ]], std :: meta :: attribute_comparison :: ignore_namespace | std :: meta :: attribute_comparison :: ignore_argument ));
4.2.3. is_attribute
namespace std :: meta { consteval auto is_attribute ( info r ) -> bool ; }
returns true if represents an attribute, it returns false otherwise. Its use is trivial
static_assert ( is_attribute ( ^^ [[ nodiscard ]]));
4.2.4. identifier_of, display_string_of
Given a reflection designating an attribute, (resp. ) should return a (resp. ) corresponding to the .
A sample follows
static_assert ( identifier_of ( ^^ [[ clang :: warn_unused_result ( "message" )]]) == "clang::warn_unused_result" ); static_assert ( identifier_of ( ^^ [[ nodiscard ( "message" )]]) == "nodiscard" );
Given a reflection that designates an individual attribute, (resp. ) returns an unspecified non-empty (resp. ). Implementations are encouraged to produce text that is helpful in identifying the reflected attribute for display purpose. In the preceding example we could imagine printing as it might be better fitted for diagnostics.
4.2.5. data_member_spec, define_aggregate
To support arbitrary attributes appertaining to data members, we’ll need to augment to encode attributes we want to attach here.
The structure changes thusly:
namespace std :: meta {
struct data_member_options {
struct name_type {
template < typename T > requires constructible_from < u8string , T >
consteval name_type ( T && );
template < typename T > requires constructible_from < string , T >
consteval name_type ( T && );
};
optional < name_type > name ;
optional < int > alignment ;
optional < int > bit_width ;
bool no_unique_address = false;
[[ deprecated ]] bool no_unique_address = false;
vector < info > attributes ;
};
}
From there building an aggregate piecewise proceeds as usual
struct Empty {}; struct [[ nodiscard ]] S ; consteval { define_aggregate ( ^^ S , { data_member_spec ( ^^ int , {. name = "i" }), data_member_spec ( ^^ Empty , {. name = "e" , . attributes = { ^^ [[ msvc :: no_unique_address ]]}}) }); } // Equivalent to // struct [[nodiscard]] S { // int i; // [[msvc::no_unique_address]] struct Empty { } e; // };
Passing attributes through the above proposed approach is well in line with the philosophy of leveraging as the opaque vehicle to carry every and all reflections.
5. Proposed wording
5.1. Language
5.1.1. 6.9.2 [basic.fundamental] Fundamental types
Augment the description of found in paragraph §17 to add attribute as a valid representation to the current enumerated list
A value of type...is called a reflection. There exists a unique null reflection; every other reflection is a representation ofstd :: meta :: info
— a data member description ([class.mem.general]), or— an attribute ([dcl.attr])
Update in paragraph §18 to remove attributes from the list
Recommended practice: Implementations should not represent other constructs specified in this document, such as using-declarators, partial template specializations, attributes, placeholder types, statements, or expressions, as values of type.std :: meta :: info
5.1.2. 7.6.2.10 [expr.reflect] The reflection operator
Edit reflect-expression production rule to support reflecting over attributes
reflect-expression:
^^ ::
reflection-name^^
type-id^^
id-expression^^
attribute^^ [[ ]]
Add a new paragraph at the bottom [expr.reflect] to describe the new rule attribute
A reflect-expression of the formfor attribute described in this document [dcl.attr], represents said attribute. For an attribute^^ [[ attribute ]] with attribute-tokenr [dcl.attr.assume], computing the reflection ofassume is ill-formed. For an attributer non described in this document, computing the reflection ofr is ill-formed absent implementation-defined guarantees with respect to said attribute.r
5.1.3. 7.6.10 [expr.eq] Equality Operators
Update 7.6.10 [expr.eq] paragraph §6 to add a clause for comparing reflection of attributes
...
(6.7) represent equal data member descriptions ([class.mem.general]),
(6.7+) represent identical attribute ([dcl.attr])
[Example:
static_assert ( ^^ [[ nodiscard ]] == ^^ [[ nodiscard ]]); static_assert ( ^^ [[ nodiscard ( "keep" )]] == ^^ [[ nodiscard ( "keep" )]]); static_assert ( ^^ [[ nodiscard ]] != ^^ [[ deprecated ]]); static_assert ( ^^ [[ nodiscard ( "keep" )]] != ^^ [[ nodiscard ( "keep too" )]]); static_assert ( ^^ [[ nodiscard ( "keep" )]] != ^^ [[ nodiscard ]]); — end example]
and they compare unequal otherwise.
5.1.4. 9.13.1 [dcl.attr.grammar] Attribute syntax and semantics
Add a new paragraph at the end to describe when are two attributes considered identical. We compare the attribute tokens which must match, and their clause for simple clause.
For any two attributes r1 and r2, r1 and r2 are identical if their attribute-token are identical and- r1 and r2 accept no attribute-argument-clause, or- r1 and r2 accept optional attribute-argument-clause and they are both empty or- r1 and r2 accept attribute-argument-clause of the formtype-id( and the type-ids denote the same type or) - r1 and r2 accept attribute-argument-clause of the formunevaluated-string( and balanced-token-seqs of r1 and r2 are identical.) Otherwise r1 and r2 are not identical. (Note: Identity between attributes not described in this document is implementation defined)
5.2. Library
5.2.1. 21.4.1 [meta.syn] Header <meta> synopsis
Add to the [meta.reflection.queries] section from the synopsis, the metafunctions , and along with the enumeration.
namespace std :: meta { // ... [meta.reflection.queries], reflection queries ... consteval bool is_attribute ( info r ); consteval vector < info > attributes_of ( info r ); enum class attribute_comparison { ignore_namespace , ignore_argument , }; consteval bool has_attribute ( info r , info a ); consteval bool has_attribute ( info r , info a , attribute_comparison flags ); }
5.2.2. 21.4.6 [meta.reflection.names] Reflection names and locations
Introduce a subclause to describing the return value to be true for attribute reflection. Renumber the last clause appropriately.
consteval bool has_identifier ( info r ); [1] Returns:
...
(1.13+) — Otherwise, if r represents an attribute, thentrue
Introduce a subclause to , , describing the return value of attribute reflection to be the . Renumber the last clause appropriately.
consteval string_view identifier_of ( info r );
consteval u8string_view u8identifier_of ( info r ); [3] Returns: An NTMBS, encoded with E, determined as follows:
...— Otherwise, ifrepresents an attributer , then the attribute-token ofa a
5.2.3. 21.4.7 [meta.reflection.queries] Reflection queries
Add the new clauses to support new metafunctions, and the new enumeration.
consteval bool is_attribute ( info r ); Returns:
trueifrepresents an attribute. Otherwise,r false.
consteval vector < info > attributes_of ( info r ); Returns: A vector
containing reflections of all attributes appertaining to the entity represented byv , such thatr iis_attribute ( v is true for every attribute vi in) . The ordering ofv is unspecified.v
Add a new table to describe the comparison policy between attributes. Add a new clause to describe
enum class attribute_comparison { ignore_namespace = unspecified , ignore_argument = unspecified , }; The type
is an implementation-defined bitmask type ([bitmask.types]). Setting its elements has the effect listed in Table (*) [tab:meta.reflection.queries]attribute_comparison
effects [tab:meta.reflection.queries]attribute_comparison
Element Effect(s) if set ignore_namespace Specifies that the attribute-namespace is ignored when comparing attributes ignore_argument Specifies that the attribute-argument-clause is ignored when comparing attributes
consteval bool has_attribute ( info r , info a ); Returns: True if
was found appertaining to the constructa .r Throws:
unlessmeta :: exception isis_attribute ( a ) true.
consteval bool has_attribute ( info r , info a , attribute_comparison flags ); Returns: True if
was found appertaining to the constructa . The bitmasks specified inr determine which components of an attribute are considered significant for matching purpose.flags Throws:
unlessmeta :: exception isis_attribute ( a ) true.
5.2.4. 21.4.17 [meta.reflection.define.aggregate] Reflection class definition generation
Change definition to deprecate , and add the data member.
namespace std :: meta {
struct data_member_options {
struct name - type {
template < class T >
requires constructible_from < u8string , T >
consteval name - type ( T && );
template < class T >
requires constructible_from < string , T >
consteval name - type ( T && );
private :
variant < u8string , string > contents ;
};
optional < name - type > name ;
optional < int > alignment ;
optional < int > bit_width ;
bool no_unique_address = false;
[[ deprecated ( "Use .attributes" )]] bool no_unique_address = false;
vector < info > attributes = {};
vector < info > annotations ;
};
}
Describe the contribution from this new member in Returns component.
Returns: A reflection of a data member description (T, N, A, W, NUA, ANN, AT) (11.4.1 [class.mem.general]) where...- AT is the value held by.options . attributes
Describe the new member effect on
Let C be the type represented by class_type and rk be the Kth reflection value in mdescrs. For every rk in mdescrs, let (TK, NK, AK, WK, NUAK, ANNK, ATK) be the corresponding data member description represented by rk.
Constants When:...- For every rk in ATk,kis_attribute ( r is true for every k)
Effects: Produces an injected declaration D ([expr.const]) that defines C and has properties as follows:...For every attribute reflection r in ATk, r appertains to Mk...
5.3. Feature-test macro
The attribute reflection feature is guarded behind a macro. Augment 15.12 [cpp.predefined]
__cpp_impl_reflection_attributes 2026XXL
6. Feedback
6.1. Poll
6.1.1. P3385R1: SG7, Nov 2024, WG21 meetings in Wroclaw
-
SG7 encourages more work on reflection of attributes as described in the paper: No objection to unanimous consent
6.1.2. P3385R2: SG7, Dec 2024, Telecon
-
SG7 wants to support namespaced attributes: No objection to unanimous consent.
-
SG7 wants to support "easy" arguments of attributes: No objection to unanimous consent.
-
SG7 wants to support arguments (full expressions) of attributes: Not consensus.
-
SG7 considers the paper high-priority and forwards it to LEWG and EWG for C++26: Not consensus.
-
SG7 forwards this paper to LEWG and EWG for C++29: Not consensus.
6.1.3. P3385R3: SG7, Feb 2025, Hagenberg
-
SG7 wants to support token source type arguments: Not consensus
-
SG7 wants to forward to EWG and LEWG as is: Consensus
6.1.4. P3385R6: SG7/EWG, June 2025, Sofia
-
SG7 wants to allow arbitrary attributes support via define_aggregate: Consensus (recommendation to merge into P3385)
-
EWG would prefer to see this paper without the ability to appertain an attribute to an entity : Consensus
-
EWG encourages more work in the direction of the paper that better exposes the details of the attributes from a querying perspective: Consensus
-
EWG encourages more work on reflecting attributes in the direction of the paper: Not consensus
6.2. Implementation
The features presented here are available on compiler explorer [2].
[1]: It is mostly an academic question since can only appertain to the null statement, no calls to could return such a reflection. The only way to get one is to construct one explicitly via and the utility of doing so is null.
[2]: Compiler explorer