In the previous two recipes, we got a list of functions, including overloads and signatures in the form of types. To do really interesting things with function reflection, we need to dig into the details, isolating the return type and function parameters.
Let's execute the following steps to determine names, types, and default values of function parameters:
std.traits
. The Phobos module makes the task easier and is much more readable than the direct implementation.__traits(getMember)
or __traits(getOverloads)
.ReturnType!func
. You may use this in any context in which you will use a type.ParameterTypeTuple!func
. You may declare a variable with this type and fill arguments with it.ParameterIdentifierTuple!func
. You may alias this to a shorter name if you like.ParameterDefaultValueTuple!func
. As with the identifiers, you may not declare a variable of this type, but you may alias it to another name.The code is as follows:
import std.traits; void showFunctionDetails(alias func)() { import std.stdio; writeln(" Name: ", __traits(identifier, func)); writeln(" Returns: ", ReturnType!func.stringof); writeln(" Arguments: ", ParameterTypeTuple!func.stringof); writeln(" Arg names: ", ParameterIdentifierTuple!func.stringof); writeln("Arg defaults: ", ParameterDefaultValueTuple!func.stringof); } int testFunction(int arg1, string str = "value") { return 0; } void main() { showFunctionDetails!testFunction(); }
Running the program will print the following output:
Name: testFunction Returns: int Arguments: (int, string) Arg names: tuple("arg1", "str") Arg defaults: tuple((void), "value")
Here, we drill into functions with four helpers from Phobos' std.traits
module: ReturnType
, ParameterTypeTuple
, ParameterIdentifierTuple
, and ParameterDefaultValueTuple
.
ReturnType
and ParameterTypeTuple
both yield types with which you can declare variables (if the type is not void
). All the tuple methods yield a loopable and indexable entity, but aside from ParameterTypeTuple
, they give a list of symbols. Thus, they must be treated like a symbol, renamed, and passed around as aliases. The order of entries is the same as the order of parameters.
The implementation of these methods use combinations of advanced is
expressions and __traits
functions to get their information. First, they extract the specific type of the callable function given to it with a series of is
expressions and static if/else
statements, similar to how we categorized types in the earlier recipe. This is to support regular functions, delegates, and function objects (for example, structs
that implement opCall
) uniformly. The function type it extracts is not the same as the function pointer. It instead extracts a particular function symbol because that gives most of the information for future compile-time reflection.
Then, the methods use a special form of the is
expression to extract data. For example, the ReturnType
template's source code is as follows:
template ReturnType(func...) if (func.length == 1 && isCallable!func) { static if (is(FunctionTypeOf!func R == return)) alias R ReturnType; else static assert(0, "argument has no return type"); }
First, it accepts any one symbol as long as it is callable. Then, it extracts the function type and gets the return value out of it.
This form of the is
expression is similar to what we used previously, for example, is(foo == class)
, but there's one item added here; the R
parameter. In this form, if the condition passes, forming a valid type, then an alias named R
will be introduced for the specific type that passed the test. Since we're checking for a return value, the type that passes is the return type, and as such, R
becomes an alias for it.
The alias R ReturnType;
line is an example of the eponymous template trick. If a template has one member (a function, a type, an alias, or anything else) with the same name as the template itself, that item acts like the template's return value. At the usage point, the template is replaced with that member. Thus, here, the usage of ReturnType!func
will refer to the alias R
, the return type extracted with the is
expression.
The other methods have similar implementations, though ParameterIdentifierTuple
and ParameterDefaultTypeTuple
are a little more complex because they both extract specific data from the same source: is(typeof(func) P == __parameters)
, which contains the names, types, and default values. The std.traits
functions filter them out to one concern at a time to make the consuming code easier to read.
We'll return to these methods in the final recipe of this chapter, where we'll use them to build a dynamic function caller.
Other information about functions is available through the std.traits
module. For example, ParameterStorageClassTuple
will tell you if arguments are scope
, out
, or ref
. The functionLinkage
method returns a string identifying the calling convention (for example, "C"
or "D"
). The functionAttributes
method tells you whether the function is nothrow
, safe
, @property
, and so on. You can also create new functions based on the old functions by using SetFunctionAttributes
. Check the documentation at http://dlang.org/phobos/std_traits.html to learn more.
3.139.97.202