Determining names, types, and default values of function parameters

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.

How to do it…

Let's execute the following steps to determine names, types, and default values of function parameters:

  1. Import std.traits. The Phobos module makes the task easier and is much more readable than the direct implementation.
  2. Get the function symbol. You may use the name directly (do not take the address of it, as we want to work on the function itself, and not on the pointer) by using __traits(getMember) or __traits(getOverloads).
  3. Isolate the return value with ReturnType!func. You may use this in any context in which you will use a type.
  4. Get the parameter types with ParameterTypeTuple!func. You may declare a variable with this type and fill arguments with it.
  5. Get the parameter names with ParameterIdentifierTuple!func. You may alias this to a shorter name if you like.
  6. Get the parameter default values with ParameterDefaultValueTuple!func. As with the identifiers, you may not declare a variable of this type, but you may alias it to another name.
  7. Loop over or index the parameter tuples to inspect individual parameters.

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")

Tip

Inside a function, you may use typeof(return) to get the return type, even if it is inferred.

How it works…

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.

There's more…

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.202