Sygaldry
|
Copyright 2023 Travis J. West, https://traviswest.ca, Input Devices and Music Interaction Laboratory (IDMIL), Centre for Interdisciplinary Research in Music Media and Technology (CIRMMT), McGill University, Montréal, Canada, and Univ. Lille, Inria, CNRS, Centrale Lille, UMR 9189 CRIStAL, F-59000 Lille, France
SPDX-License-Identifier: MIT
At the time of writing we detect a main subroutine of a component as a static method main
or a function call operator that takes as arguments the components inputs, outputs, and/or parts, if any exist, as well as possibly additional throughpoints and/or plugins, and returns void
. This turns out to be a bit involved to determine based only on the component's type. We need to check that the component has a main
method or an operator()
, and if so whether it accepts and returns the expected arguments.
It's easy to check whether the given type has a function call operator; we can simply check if it's possible to take its address. But this doesn't allow us to check the return type is void
:
A similar check for the main
method doesn't prove anything; main
could just as well be a static variable, and even if it is a method, we still don't know if it returns void.
More generally, we would like to be able to reflect over any function (whether free or member), access its return and argument types, and tell whether member functions are const, volatile, and/or noexcept qualified. We require more robust function reflection.
We need to do some template metaprogramming. We define a function reflection meta-function (i.e. a template struct used to take in types and return types). In the base case, when its argument is not a function, it declares that function reflection doesn't exist for this type by setting its type alias flag exists
false.
Note that we use the boolean constant types from type_traits
instead of defining e.g. static constexpr bool exists = false;
. We expect the function_reflection
facility to be used in template metaprograms more often than not, where boolean constant types are more ergonomic.
We then specialize the struct for the case where the argument is a function, making the return type and list of arguments seperately available as types in the scope of the template, as well as several flags describing the context and qualification of the function type. We define a trivial type list template to carry around the list of argument types.
So that our reflection mechanism will also work with pointers to functions and member functions, we specialize the template for these cases, as well as adding additional information for cv-noexcept qualified member function. We are able to save ourselves a bit of repetition by inheriting the basic exists
, return_type
, and arguments
type aliases from the base function case, shadowing the default false
flags where the specialization would have them true
.
Finally, we define a concept to detect when function reflection is available.
We would also like to be able to reflect on function values, such as pointers to functions, pointer to members, and eventually pointers to function objects. This leads to the following metafunction, with use of std::decay
to avoid having to make specializations for every combination of possibly const value, lvalue reference, and rvalue reference. Because this metafunction is defined in terms of the previous function_type_reflection
facility, we can also more strictly constrain its inputs to those for which reflection is possible.