Document Number: P0670R0, ISO/IEC JTC1 SC22 WG21
Audience:SG7 (Reflection), EWG, LEWG
Date:2017-06-18
Authors:Matúš Chochlík (chochlik@gmail.com)
Axel Naumann (axel@cern.ch)
David Sankel (camior@gmail.com)

Static reflection of functions

Table of Contents

Introduction

P0194 introduced static reflection for types and variables. This paper adds static reflection of functions.

Reflection proposed here behaves as follows:


void func(int);
using func_overload_m = reflexpr(func);
using func_m = get_element_t<0, get_overloads_t<func_overload_m>>;
using param0_m = get_element_t<0, get_parameters_t<func_m>>;
cout << get_name_v<get_type_t<param0_m>> << '\n'; // prints "int"

The functionality introduced here allows for reflection of concrete functions; This enables, for instance, GUI generation, building a catalogue for remote procedure calls, documentation generation, and signal/slot frameworks. Like P0194, this proposal omits attributes, templates, and reification (i.e. conversion of a meta object to the base level): all warrant a separate paper. We would welcome a paper especially on static reflection of attributes, matching the interface-style of P0194 and this paper! Linkage and friends will be part of a follow-up paper to P0194; they will have a combined "effect" on P0194 and this paper.

Interplay with other proposals

Most notably, this proposal relies on P0194 and the Concepts TS.

P0385 discusses use cases, rationale, design decisions, and the future evolution of the proposed reflection facility. It also has usage examples and replies to frequently asked questions.

Concepts and Operations

Following P0194's lead, function reflection requires a couple new concepts to restrict operations on the meta level. It builds upon the concepts introduced by P0194 and, like P0194, all declarations are inside the reflect namespace. Some of the concepts below extend P0194's concepts. Whenever a concept A requires another concept B, the operations defined for concept B are also available for concept A, building a graph similar to an inheritance graph.

Proper wording will be provided when needed (and when P0194 has progressed through LWG, increasing to the authors' experience on wording of reflection papers); we believe that missing wording should not hinder SG7's design discussion. The wording is planned to be similar to that of P0194; specifically, it uses nested type and value entities, with the usual using declarations ..._t and ..._v.

Extending operands of reflexpr

P0194 allows certain types and variables as operands of reflexpr. This paper extends this set:

When invoking reflexpr on a name, the resulting OverloadSet comprises all functions (member or not) that are part of the overload set at the point of invocation of the reflexpr operator. Example:

  
    struct A;
    int operator+(A, A);
    struct B {
      B& operator+(B);
      auto op_plus_overloads = reflexpr(operator+);
    };
  

Here, op_plus_overloads contains both int operator+(A, A) and B& B::operator+(B).

The way to generate a Callable might seem baroque. But instead of inventing a new syntax, for instance reflexpr(foo(int,int)), or re-using the mechanism of matching the address of an overloaded function name reflexpr((void(&)(int, int))foo), we decided to use the far more common and legible approach reflexpr(void foo(int,int)), similar to the way a function definition would be introduced. This approach also works for constructors and destructors, for instance reflexpr(std::string::string()) reflects the default constructor of string.

FunctionParameter

template <class Object> concept bool FunctionParameter();

Requires Named and ScopeMember. Represents a function parameter. Unlike Variable, it does not offer a get_pointer interface. The name of a parameter is the name used in the most recent redeclaration at the point of invocation of the reflexpr operator. Its scope is the Callable declaring this FunctionParameter.
Given the lack of relevance that C++ attributes to parameter names in function declarations one might ask: "o really?!" We believe that the answer is "yes": parameter names often carry significant meaning. Examples:
  
    double Gauss(double x, double mean, double width, double height);

    void f() {
      // Don't confuse those!
      func(true /*willLeave*/,
           false /*knewWhatTheyVotedOn*/,
           false /*willBeHappyEverAfter*/);
    }
  
  
The bare combination of type and index is almost meaningless in these cases. There are many reflection applications that can benefit from this, for instance: To put it differently: functions without parameter names are like classes without member names. And tuple is not a replacement for classes.
Another common objection is that multiple declarations could potentially have different parameter names ascribed. This concern is mitigated in two ways:
  1. Modern coding conventions have the declarations for a particular function showing up in exactly one header file.
  2. Modern coding conventions discourage the use of different argument names between function declarations (in a header) and function definitions (in a '.cpp' file). Dedicated compiler warnings exist to protecte against this case.

Callable

template <class Object> concept bool Callable();

Requires Named, ScopeMember and Scope. Represents a function or lambda, including operators, constructors and destructors - i.e. anything this paper is dealing with.

Operations

template <Callable> struct get_parameters;

Returns an ObjectSequence of FunctionParameters of the reflected Callable.

template <Callable> struct is_constexpr;
template <Callable> struct is_noexcept;

Returns whether the function was declared as constexpr or noexcept, respectively.

template <Callable> struct is_inline;

Returns whether the function is an inline function. With struct X{ inline void f(); void g() {} }, is_inline is true for both f and g.

template <Callable> struct is_deleted;

Returns whether the function was defined as = delete before the invocation of reflexpr.

OverloadSet

template <class Object> concept bool OverloadSet();

Requires Named. Represents a sequence of overloads with a given name. Example: reflexpr(sin) returns a type that satisfies OverloadSet.

Operations

template <OverloadSet> struct get_overloads;

Returns an ObjectSequence of Callables of a given name, e.g. get_overloads_t<reflexpr(sin)> returns a ObjectSequence of all sin overloads. This includes template specializations at the point of invocation of the reflexpr operator. The order of the elements is the order of declaration, ignoring possible redeclarations.

Function

template <class Object> concept bool Function();

Requires Callable and Typed. Represents a function or lambda, excluding constructors and destructors.

Operations

template <Function> struct get_pointer;

Returns a pointer to the function. This is a pointer-to-member for non-static member functions, and a function pointer otherwise. It is ill-formed to invoke this for deleted functions. Example: auto p_sin = get_pointer_v<reflexpr(double sin(double))> holds the address of sin(double).

RecordMemberFunction

template <class Object> concept bool RecordMemberFunction();

Requires RecordMember and Function. Represents a member function, excluding constructors and destructors.

Operations

template <RecordMemberFunction> struct is_static;

Returns whether this is a static member function.

template <RecordMemberFunction> struct is_const;
template <RecordMemberFunction> struct is_volatile;

Returns whether the function is declared as const or volatile, respectively.

template <RecordMemberFunction> struct has_lvalueref_qualifier;
template <RecordMemberFunction> struct has_rvalueref_qualifier;

Returns whether the function is declared as with a ref-qualifier being & or &&, respectively.

template <RecordMemberFunction> struct is_virtual;
template <RecordMemberFunction> struct is_pure_virtual;

Returns whether the function is a virtual or pure virtual function, respectively. For
  
    struct A { virtual void X(); };
    struct B: A { void X(); };
  
the value of is_virtual_v<reflexpr(void B::X())> is true, irrespectively of whether virtual is implicit of explicit.

template <RecordMemberFunction> struct is_override;
template <RecordMemberFunction> struct is_final;

Returns whether the function is declared as override or final, respectively.

SpecialMemberFunction

template <class Object> concept bool SpecialMemberFunction();

Requires RecordMember. Represents a special member function.

Operations

template <SpecialMemberFunction> struct is_implicitly_declared;

Returns whether the special member function is known to be implicitly declared at the point of invocation of reflexpr.

template <SpecialMemberFunction> struct is_defaulted;

Returns whether the function is defined as = default before the invocation of reflexpr, independently of whether the special member function is implicitly or explicitly declared.

Constructor

template <class Object> concept bool Constructor();

Requires Callable and RecordMember. Represents a constructor. The base name of the constructor is the base name of the constructor's class. Even though the standard explicitly says that constructors do not have a name, for usability purposes (e.g. generating messages), having them state the class name is a usability improvement.
Some instances of Constructor might also satisfy SpecialMemberFunction.

Operations

template <Constructor> struct is_explicit;

Returns whether the constructor is known to be declared as explicit.

Destructor

template <class Object> concept bool Destructor();

Requires Callable, SpecialMemberFunction and RecordMember. Represents a destructor. The base name is the base name if the destructor's class, prefixed with '~'.

template <Destructor> struct is_virtual;
template <Destructor> struct is_pure_virtual;

Returns whether the destructor is a virtual or pure virtual function, respectively. For
  
    struct A { virtual ~A(); };
    struct B: A { B(); };
  
the value of is_virtual_v<reflexpr(void B::~B())> is true, irrespectively of whether virtual is implicit of explicit.

Operator

template <class Object> concept bool Operator();

Requires Function. Some instances might implement RecordMemberFunction or SpecialMemberFunction. Represents an operator. The base name is the operator "symbol", for instance "+".

ConversionOperator

template <class Object> concept bool ConversionOperator();

Requires Operator. Represents a conversion operator. The base name is the base name of the operator's target type, for instance "int".

Operations

template <ConversionOperator> struct is_explicit;

Returns whether the function is declared as explicit.

LambdaCapture

template <class Object> concept bool LambdaCapture();

Requires Variable. Represents a lambda capture as introduced by the capture list or by capture defaults. The LambdaCapture's scope is its Lambda.

Operations

template <LambdaCapture> struct is_explicitly_captured;

Returns whether the entity was captured explicitly.

template <LambdaCapture> struct is_init_capture;

Returns whether the entity is an init-capture.

Lambda

template <class Object> concept bool Lambda();

Requires Function. Represents a closure type, excluding those for generic lambdas. Its base name is the empty string.

Operations

template <Lambda> struct get_captures;

Returns an ObjectSequence of LambdaCaptures.

template <Lambda> struct uses_default_copy_capture;

Returns whether the capture-default is =.

template <Lambda> struct uses_default_reference_capture;

Returns whether the capture-default is &.

template <Lambda> struct is_call_operator_const;

Returns false if the lambda was declared as mutable, true otherwise.

Extending Record

This proposal adds the following interfaces to the Record concept of P0194:

template <Record T> struct get_public_member_functions;
template <Record T> struct get_accessible_member_functions;
template <Record T> struct get_member_functions;

Returns an ObjectSequence of OverloadSets representing a class's public member functions (for get_public_member_functions), member functions accessible from the point of invocation of reflexpr (for get_accessible_member_functions) and all member functions, irrespective of their accessibility (for get_member_functions).

template <Record T> struct get_constructors;
template <Record T> struct get_destructors;
template <Record T> struct get_operators;

Returns an ObjectSequence of a class's Constructors, Destructors and Operators, respectively.

template<Record T, class U> get_conversion_op;

Returns an OverloadSet of the ConversionOperators converting the class to T. It is ill-formed to invoke this if no such operator exists.

Acknowledgments

Thanks to Jackie Kay who provided valuable feedback, criticism and suggestions!

References

1. Static reflection. Rationale, design and evolution. p0385

2. Static reflection in a nutshell. p0578