Document Number: P0670R2, ISO/IEC JTC1 SC22 WG21
Audience:EWG, LEWG
Date:2017-11-08
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);
void func(std::string);
using func_call_m = reflexpr(func(123));
using func_m = get_callable_t<func_call_m>; // reflects void func(int)
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 calls 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:

The way to obtain a Callable might seem complicated. But instead of inventing a new syntax, for instance reflexpr(foo(int,int)), re-using the mechanism of matching the address of an overloaded function name reflexpr((void(&)(int, int))foo), or syntax similar to function definition reflexpr(void foo(int,int)), we rely on the existing, familiar rules for function overload resolution and generate the Callable only when a concrete overload is selected. This approach also works for constructors and operators, for instance reflexpr(std::string()) reflects the call of the default constructor of string; reflexpr(1+1) reflects the call of the built-in addition operator for type int.

FunctionParameter

template <class Object> concept FunctionParameter = /* implementation defined */;

Requires Named and ScopeMember. Represents a function parameter. Unlike Variable, it does not offer a get_pointer interface. The name of a parameter is one of the names used in an unspecified declaration. If at least one of the declarations does not specify the parameter name, then get_name is allowed to return an empty string. 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.

Operations

template <typename T>
requires FunctionParameter<T>
struct is_ellipsis;

Indicates that a FunctionParameter reflects an ellipsis.

template <typename T>
requires FunctionParameter<T>
struct has_default_value;

Indicates that a FunctionParameter has a default value.

Callable

template <class Object> concept Callable = /* implementation defined */;

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 <typename T>
requires Callable<T>
struct get_parameters;

Returns an ObjectSequence of FunctionParameters of the reflected Callable.

template <typename T>
requires Callable<T>
struct is_constexpr;

template <typename T>
requires Callable<T>
struct is_noexcept;

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

template <typename T>
requires Callable<T>
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 <typename T>
requires Callable<T>
struct is_deleted;

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

FunctionCallExpression

template <class Object> concept FunctionCallExpression = /* implementation defined */;

Requires Object. Reflects a call of a concrete function or other callable.

Operations

template <typename T>
requires FunctionCallExpression<T>
struct get_callable;

Returns the Callable that was invoked in a FunctionCallExpression.

Function

template <class Object> concept Function = /* implementation defined */;

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

Operations

template <typename T>
requires Function<T>
struct get_pointer;

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

RecordMemberFunction

template <class Object> concept RecordMemberFunction = /* implementation defined */;

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

Operations

template <typename T>
requires RecordMemberFunction<T>
struct is_static;

Returns whether this is a static member function.

template <typename T>
requires RecordMemberFunction<T>
struct is_const;

template <typename T>
requires RecordMemberFunction<T>
struct is_volatile;

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

template <typename T>
requires RecordMemberFunction<T>
struct has_lvalueref_qualifier;

template <typename T>
requires RecordMemberFunction<T>
struct has_rvalueref_qualifier;

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

template <typename T>
requires RecordMemberFunction<T>
struct is_virtual;

template <typename T>
requires RecordMemberFunction<T>
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<get_callable_t<reflexpr(std::declval<B>().X())>> is true, irrespectively of whether virtual is implicit of explicit.

template <typename T>
requires RecordMemberFunction<T>
struct is_override;

template <typename T>
requires RecordMemberFunction<T>
struct is_final;

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

SpecialMemberFunction

template <class Object> concept SpecialMemberFunction = /* implementation defined */;

Requires RecordMember. Represents a special member function.

Operations

template <typename T>
requires SpecialMemberFunction<T>
struct is_implicitly_declared;

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

template <typename T>
requires SpecialMemberFunction<T>
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 Constructor = /* implementation defined */;

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 <typename T>
requires Constructor<T>
struct is_explicit;

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

Destructor

template <class Object> concept Destructor = /* implementation defined */;

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

template <typename T>
requires Destructor<T>
struct is_virtual;

template <typename T>
requires Destructor<T>
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<get_callable_t<reflexpr(std::declval<B>.~B())>> is true, irrespectively of whether virtual is implicit of explicit.

Operator

template <class Object> concept Operator = /* implementation defined */;

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 ConversionOperator = /* implementation defined */;

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

Operations

template <typename T>
requires ConversionOperator<T>
struct is_explicit;

Returns whether the function is declared as explicit.

Lambda

template <class Object> concept Lambda = /* implementation defined */;

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

Operations

template <typename T>
requires Lambda<T>
struct get_captures;

Returns an ObjectSequence of LambdaCaptures.

template <typename T>
requires Lambda<T>
struct uses_default_copy_capture;

Returns whether the capture-default is =.

template <typename T>
requires Lambda<T>
struct uses_default_reference_capture;

Returns whether the capture-default is &.

template <typename T>
requires Lambda<T>
struct is_call_operator_const;

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

LambdaCapture

template <class Object> concept LambdaCapture = /* implementation defined */;

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 <typename T>
requires LambdaCapture<T>
struct is_explicitly_captured;

Returns whether the entity was captured explicitly.

template <typename T>
requires LambdaCapture<T>
struct is_init_capture;

Returns whether the entity is an init-capture.

Extending Record

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

template <typename T>
requires Record<T>
struct get_public_member_functions;

template <typename T>
requires Record<T>
struct get_accessible_member_functions;

template <typename T>
requires Record<T>
struct get_member_functions;

Returns an ObjectSequence of RecordMemberFunctionss 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). This includes static member functions, but not friend functions.

template <typename T>
requires Record<T>
struct get_constructors;

template <typename T>
requires Record<T>
struct get_destructors;

template <typename T>
requires Record<T>
struct get_operators;

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

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