Document Number: Date: Project: Reply to: N3984 2014-05-07 SG7 - Reflection Cleiton Santoia Silva <[email protected]> Daniel Auresco <[email protected]> Adding attribute reflection to C++. Contents Contents ii 1 Intro 1 2 Motivation 2 3 Impact on Standard 3.1 What to forbid . . . 3.2 Avoid break old code 3.3 What we get today . 3.4 What to allow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 4 4 Design decisions 4.1 Typed attributes . . . . . . . . . . . . . . . . 4.2 Attribute Instances . . . . . . . . . . . . . . . 4.3 Type traits . . . . . . . . . . . . . . . . . . . 4.4 Attribute Inheritance . . . . . . . . . . . . . . 4.5 Which attributes attachment can be reflected 4.6 More complete examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 7 7 8 8 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Technical Specification 10 5.1 Attempted Wording . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 6 Acknowledgments 12 6.1 Few acks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Bibliography Contents 13 ii 1 Intro [intro] C++ reflection mechanism could benefit from a more complete set o functionality, if we define how to reflect attributes as well as types. This proposal main idea is to make attributes typed, and use the standard attribute syntax [[]] to attachment but, instead of attach a typed attribute to a target, we propose to attach a constexpr instance of an attribute to a target. After that, we also propose to reflect them using the syntax typedef<[[T]]> and typedef<[[T]] requires C>... based on [SA14, N3951] to get attributes of type T, restricted by constexpr bool function C. However, by current standard, the compiler implementers and vendors are free to use any tokens they need, including keywords. Make attributes typed will lead to some problems, and we show some ways to address them. But before that, let’s introduce an easy Get and Set idiom example: struct serializable { }; struct getter { string name; }; struct setter { string name; }; struct [[serializable]] X { private: int index_; public: [[getter{"index"}]] int index() const { return index; } [[setter{"index"}]] void set_index(int i) { index_ = i; } }; auto mytuple1 = make_tuple(typedef<[[SomeType]]>...); // auto mytuple1 = make_tuple(serializable()); auto mytuple2 = make_tuple(typedef<[[&SomeType::index]]>...); // auto mytuple2 = make_tuple(getter{"index"}); auto mytuple3 = make_tuple(typedef<[[&SomeType::set_index]]>...); // auto mytuple3 = make_tuple(setter{"index"}); Intro 1 2 Motivation [motivation] After defining reflection in C++, it’s easy to imagine an augmented version of it, with more information attached to types. And a common way to do this is using attributes, they help to create new ways to pass information from the source code to the frameworks, to choose which members you will use and which you will discard in some process, or improve meta programming techniques like type selection. All of this is expected from a reflection mechanism, and also we can enumerate some more. — Serialization — Remote Procedure Calls — Event Driven Development — Test Driven Development — GUI Property Editors — Database-Object mapping interface — Documentation — Describe Axioms, Constraints and Rules over Types Motivation 2 3 3.1 Impact on Standard What to forbid [impact] [impact.forbid] In order to take a little more controlled environment we pretend to forbid few things. — Forbid attributes to be keywords: The definition from [dcl.attr.grammar] says that If a keyword (2.12) or an alternative token (2.6) that satisfies the syntactic requirements of an identifier (2.11) is contained in an attribute-token, it is considered an identifier. No name lookup (3.4) is performed on any of the identifiers contained in an attribute-token. today , none of the standard attributes are keywords alignas, noreturn, carries_dependency and deprecated, so it still in time. Also, they need to participate of the name look-up, just as a common types and instantiated as constexpr. — Attribute balanced braces, they can be [] or () but we propose to forbid [] because it’s confusing since it’s not a standard C++ constructor idiom, and allow only as standard initialization and () as common constructor call. 3.2 Avoid break old code [impact.old] C++ attributes allows compiler implementers to define their custom features that does not need to be standardized, so we must keep this use. To allow this and avoid break old code and allow attributes to be typed we propose the following : — First, make all attributes typed, for standard defined attributes like [[deprecated]] we propose to define their type as a common class or structure; — According to [dcl.attr.grammar] (7.6.3), the implementers can put attributes in a non-standard attributenamespace, the standard does not say that attribute-namespace is a common namespace, but since attributes does not participate of a name look-up we conclude that namespace != attribute-namespace. But since now the attributes will become common types, may be it’s a good idea to put standard attributes in std namespace and allow implementers to create a new namespace std::imp that stands for implementation defined. Where implementers can put their non-standardizable features that needs a namespace. And also, create freely their own namespaces inside of this, where they can put their own attributes. [ Example: GNU may create std::imp::gcc to put gnu_inline attribute class. — end example ] — Also, implementers will be free to add an implicit using std::imp::xxx to any compilation unit to make their attributes work as typed and not break old code. This will help the migration from non-typed do typed attributes. 3.3 What we get today [impact.today] We will not propose forbid any place that attributes can be attached today, but we need to specify few new ones, and define also new ways to understand what is allowed by the standard. Today, attributes can be attached to the following items. — enums, lambdas, classes, base classes, arrays — member objects, member functions, member operators, member types, constructors, destructors, friend declaration, bit fields — functions, function parameters, operators — variables § 3.3 3 — template functions, template classes — statements, for parameters, ranged for parameters, conditions — labels — try blocks, exception declaration — expressions — using alias, using namespace directive — trailling return types 3.4 What to allow [impact.allow] Now we show the few important new places that you should be able to attach attributes. — Surprisingly in enumerator-definitions [dcl.enum] it cannot be attached today, the standard says: enumerator-list: enumerator-definition enumerator-list , enumerator-definition enumerator-definition: enumerator enumerator = constant-expression enumerator: identifier Enumerator should change to: enumerator: attribute-specifier-seqopt identifier — Attributes attached to class inside the class definition, by the current standard, class attributes must lay between class name and the start of the class definition. class-head: class-key attribute-specifier-seqopt class-head-name class-virt-specifieropt base-clauseopt class-key attribute-specifier-seqopt base-clauseopt Looking at the standard in item [class.mem] of [ISO14, N3797] we got this: member-declaration: attribute-specifier-seqopt decl-specifier-seqopt member-declarator-listopt ; function-definition using-declaration static_assert-declaration template-declaration alias-declaration empty-declaration In the first line, it says that attribute-specifier-seq can appear before a member-declarator-list that can be optional, allows it at the end of the class, just before "}", so this is possible: struct X { int a; int b; [[ Att{1, ‘‘hello’’} ]]; }; § 3.4 4 The standard address this case directly with a restriction in 9.2.7 that says a attribute-seqopt cannot appear without a, member-declarator-list, but this restriction should be removed. This way it’s not bounded to any particular member, it becomes a class attribute. It’s a good place to put some like Property attributes. Also, this can help human view of attributes. — Another issue that can help human view, comes from great idea in [Tom14, N3955] that leads rapidly to adding group attributes on it to attach attributes to many member at once; class AnotherType { public [[qt::slot]]: void fun(); void gun(); }; Using this syntax should means that each member have their own instance of qt::slot attribute, but this is pointless, since all instances will get the exact same constexpr values, we could instantiate only one object and share it to members fun() and gun(). § 3.4 5 4 4.1 Design decisions Typed attributes [design] [design.typed] Among few other problems, attributes are not typed nor part of type system, characteristic they share with macros and throws specification. These two features of C++ become deprecated over the years for different reasons, but not participate in type system is their common problem. So the propose is about tree things: — Turn attributes into typed attributes; — Allow any type to be attached to target. The way of attachment is the usual general attribute [[]] syntax as defined in [dcl.attr.grammar]. — Retrieve them via reflection. This proposal is based on [SA14, N3951] syntax, the first idea is to use typedef<[[T]]>... and typedef<[[T]] requires C>... syntax to get attributes. Also propose new type traits to help reflecting them. If you think that this syntax is ugly, you probably right, but it’s ugliness is derived from ugly [[]] attribute C++ syntax. Soon after publishing the proposal [SA14, N3951], this syntax received a big criticism on the forum, in favor to drop changes in keywords and use a as-if-lib approach like reflect<T>::attribute<C>::types..., but when we tried to define the interface of it we find something like this: template <typename T> struct reflect { template <typename C> struct attribute { ??? types...; // <-what we put here ? } } What kind of function or typedef or directive have a return type of variadic template ? Well, today, we have tr2::bases from [Spe09, N2965]: template<typename _Tp> struct bases { typedef __reflection_typelist<__bases(_Tp)...> type; }; What is the interface of __bases(_Tp) ? In this case, it’s need a language support. If you look at a [Spe12, N3416] you can find some ideas about typelists, that can turn __bases(_Tp) interface into a plain standard. May be variadic template expansion is just a struct version of lambda algorithms, structs with unnamed members, a more pure form of lambda types, a type part of a tuple. But, avoid being too theoretical, we propose an approach, does not depend of implement the whole typelists issue and does not harm the interface forcing not-standard features and brings us the benefit ot template expansion. We think that as-if-lib should be used since it’s possible to do it without violating any C++ rule. So, as soon as we got some advances in this issue, we would be glad to update this proposal and add typelists to it, but until there, we still go for typedef<[[T]]>..., anyway if you prefer, you can imagine the samples using the syntax reflect<T>::attribute<C>::types... without loosing anything of the rest of the proposal. § 4.1 6 4.2 Attribute Instances [design.instances] Since attributes will be typed, instead of attach the type of the attribute to target type, we can attach an instance of an attribute type to a target type, instantiate it at compile time, and them use object information to improve their use. So [[A]] is not a typed attribute “A” anymore, but it becomes a constexpr instance of type A, and constructing A using the syntax of standard initialization, we can keep attribute attachment short like this: [ Example: class A { int x; int y; }; class [[A{10,20}]] T { }; — end example ] 4.3 Type traits 4.3.1 Applying type traits [design.traits] [design.traits.apply] The proposed method to applying type traits in typedef<[[T]] requires C>... is analogue to [SA14, N3951], typedef<T requires C>..., in both cases C is expected to be a template callable function bool constexpr that returns true for all typed that should be returned by typedef<[[T]] requires C>.... It’s important to show at least one example of this usage; [ Example: struct TestCase { string nm; }; struct Serializable {}; template <typename T> constexpr bool is_test() { return std::is_same<T, TestCase>::value; } struct X [[Serializable, TestCase{"test1"}]] { int mem1(); int mem2; }; auto my_tuple = make_tuple(typedef<[[X]] requires is_test>... >); // auto my_tuple = make_tuple(TestCase{"test1"}); <- ignores Serializable — end example ] 4.3.2 Has Attribute type trait [design.traits.attribute] As expected we should create a type trait to discover if a type has an attribute attached to it, so we propose std::has_attribute<X,A> that returns true if type X has attached a attribute A or a attribute derived from A; [ Example: struct att {}; struct deprecated : att { std::string why; }; struct X [[deprecated{"this is problematic"}]] {}; void foo() { std::cout << std::boolalpha << std::has_attribute<X, att>::value << std::endl; } § 4.3.2 7 output: true — end example ] 4.4 Attribute Inheritance [design.inheritance] Does the class and member attributes should be inherited ? Derived classes and members are inherited, and also you can set their access, but you cannot define a access specifier to an attribute. This means that you cannot decide what is “passed away” to derived classes and what should be hidden from them. Also, in C++ if a base class has virtual methods, they can be re-declared in derived classes, in this case, according to standard, you can re-declare new attributes as well, do this new attributes should override the previous or should they be added to previous function attributes ? Instead of defining if the class and member attributes must be inherited or not, since exists uses for both cases, we propose the creation of a std::is_derived<T> type trait that could solve this problem. Applying this trait as a constraint when you reflect the attributes will bring what you want. So we propose some new traits. — is_derived<M> return true if T is a inherited member type, data or function, and false otherwise [ Example: struct A { int x; }; struct B : A { }; — end example ] std::is_derived<B::x>::value is true, but std::is_derived<A::x>::value false, since x is defined in A; — is_derived_from<T,X> The use for this trait is to check if a member T was derived from class X; 4.5 Which attributes attachment can be reflected [design.attachment] Not all attribute can be reflected, since not all attachments occurs in classes or typed items, we must define which cases is possible to reflect attributes. — classes — class members ( classes, enums, object members and function members, including constructors and destructors ) — functions — lambda types — parameters of functions 4.6 More complete examples 4.6.1 Property idiom [design.complete] [design.complete.property] By the end let’s show a sample implementation of a Property idiom: struct struct struct struct serializable {}; desc { std::string description; }; getter { std::string name; }; setter { std::string name; }; template< typename _Reader, typename _Writer, typename _Notifier > struct prop { std::string name; § 4.6.1 8 _Reader reader; _Writer writer; _Notifier notifier; constexpr property(_Reader r, _Writer w, _Notifier n) : reader(r), writer(w), notifier(n) {} }; class [[serializable]] X { private: [[getter{"index"}]] int index_; // it is a member attribute [[setter{"index"}]] void set_index(int i) { index_ = index; } enum Status { Half_empty [[desc{"It’s about a half"}]], Half_full [[desc{"Optimistic approach to half"}]] }; int val_; virtual void notifier() = 0; public: void set_val(int v) { val_ = val; } [[ prop{"val", val_, set_val, notifier} ]] // it is a class attribute }; auto mytuple = make_tuple(typedef<[[SomeType]]>...); /∗ auto mytuple = make_tuple( serializable (), property (" val ", &X::val_, &X::set_val, &X::notifier )); ∗/ 4.6.2 Attribute Idioms [design.complete.idioms] Just to show how things can get complicated fast, by the definition of standard, the placement of an attribute attachment can occurs after the template definition of a class declaration, and now being part of the name look-up, it’s possible to create an analogue of CRTP a Curious Recurrent Attribute Pattern1 : template<typename T> struct A {}; class C [[A<C>]] {}; Just for fun let’s show another few more idioms: template<typename ...T> class Mixim [[T()...]] {}; struct A { template<typename ...T> U(const T&...t) {} }; struct B {}; class VariadicCtor [[A(10, 20, 30, 1.0, B(), "what was that?")]] {}; struct X {}; struct Y [[X]] {}; struct Z [[Y]] {}; // Y is an attribute that has attributes 1) Avoid the anachronism § 4.6.2 9 5 5.1 Technical Specification [spec] Attempted Wording [spec.wording] It’s not all wording needed about attribute reflection, it’s just small new changes in standard where attributes should be placed. — [dcl.enum]. enumerator: attribute-specifier-seqopt identifier attribute-specifier-seqopt — [class]. class-head: class-key attribute-specifier-seqopt class-head-name specifieropt base-clauseopt class-key attribute-specifier-seqopt base-clauseopt attribute-specifier-seqopt class-virt- — [class.mem]. 7 The optional attribute-specifier-seq in a member-declaration appertains to each of the entities declared by the member-declarators; it shall not appear if the optional member-declarator-list is omitted. If it appear when the optional member-declarator-list is omitted, it shall be a class attribute. — [dcl.attr.grammar] 1 Attributes specify additional information for various source constructs such as types, variables, names, blocks, or translation units. attribute-specifier-seq: attribute-specifier-seqopt attribute-specifier attribute-specifier: [ [ attribute-list ] ] alignment-specifier alignment-specifier: alignas ( type-id ...opt ) alignas ( constant-expression ...opt ) attribute-list: attributeopt attribute-list , attributeopt attribute ... attribute-list , attribute ... attribute: type-specifier initializeropt attribute-token attribute-argument-clauseopt attribute-token: identifier attribute-scoped-token attribute-scoped-token: attribute-namespace :: identifier attribute-namespace: identifier attribute-argument-clause: ( balanced-token-seq ) § 5.1 10 balanced-token-seq: balanced-tokenopt balanced-token-seq balanced-token balanced-token: ( balanced-token-seq ) [ balanced-token-seq ] { balanced-token-seq } any token other than a parenthesis, a bracket, or a brace 2 [ Note: For each individual attribute, the form of the balanced-token-seq will be specified. — end note ] 3 In an attribute-list, an ellipsis may appear only if that attribute’s specification permits it. An attribute followed by an ellipsis is a pack expansion (14.5.3). An attribute-specifier that contains no attributes has no effect. The order in which the attribute-tokens appear in an attribute-list is not significant. If a keyword (2.12) or an alternative token (2.6) that satisfies the syntactic requirements of an identifier (2.11) is contained in an attribute-token, it is considered an identifier. No name lookup (3.4) is performed on any of the identifiers contained in an attribute-token. The attribute-token determines additional requirements on the attribute-argument-clause (if any). The use of an attribute-scoped-token is conditionally-supported, with implementation-defined behavior. [ Note: Each implementation should choose a distinctive name for the attribute-namespace in an attribute-scoped-token. — end note ] 4 Each attribute-specifier-seq is said to appertain to some entity or statement, identified by the syntactic context where it appears (Clause 6, Clause 7, Clause 8). If an attribute-specifier-seq that appertains to some entity or statement contains an attribute that is not allowed to apply to that entity or statement, the program is ill-formed. If an attribute-specifier-seq appertains to a friend declaration (11.3), that declaration shall be a definition. No attribute-specifier-seq shall appertain to an explicit instantiation (14.7.2). 5 For an attribute-token not specified in this International Standard, the behavior is implementation-defined. 6 Two consecutive left square bracket tokens shall appear only when introducing an attribute-specifier. [ Note: If two consecutive left square brackets appear where an attribute-specifier is not allowed, the program is ill-formed even if the brackets match an alternative grammar production. — end note ] 7 In the attribute definition, before type-specifier the compiler must add a implicit constexpr. 8 The attribute information must be carried from the definition to the point of type trait calls. [ Example: struct Att { }; struct X { int v1; [[Att]] int v2; }; template< typename T > bool foo(T t) { return std::has_attribute<T,Att>::value; } The call to foo must distinguish foo(&X::v1) from foo(&X::v2) — end example ] § 5.1 11 6 6.1 Acknowledgments Few acks [ack] [ack.ack] Thanks to forum people: Sean Middleditch, Tiago Macieira, Andrew Tomazos, Ville Voutilainen for reading and feedback and Oliver Gottart for that good [Gof14, MOC] form wich we pick some ideas. § 6.1 12 Bibliography [Gof14] Oliver Goffart. Can qt’s moc be replaced by c++ reflection? Technical report, Woboq, 2014. [ISO14] C++ ISO. N3797 programming languages c++. Technical report, Programming Language C++, 2014. [SA14] Cleiton Santoia Silva and Daniel Auresco. N3951 - c++ type reflection via variadic template expansion. Technical report, C++ SG7, 2014. [Spe09] Michael Spertus. N2965 - type traits and base classes. Technical report, Symantec, 2009. [Spe12] Mike Spertus. N3416 - packaging parameter packs. Technical report, C++ ISO Standard, 2012. [Tom14] Andrew Tomazos. N3955 - group member specifiers. Technical report, C++ ISO Standard, 2014. § 6.1 13
© Copyright 2026 Paperzz