Channels ▼
RSS

C/C++

Extracting Function Parameter and Return Types in C++


Metaprgramming has become more and more popular and is now an essential part of modern C++ programming. By utilizing type information, it is possible implement generic programming and perform some compile time optimizations and checks. In this article, I use template metaprogramming to perform type extraction on a function and show an application of this technique.

To demonstrate one of the possible practical applications, I've created an open-source app that functions like a basic command interpreter or REPL. A new command is registered by providing a function pointer. The command class then has enough information to evaluate an input string, parse input parameters (check their number and types, convert if needed), invoke a command, and provide a result.

Here is an excerpt from the code that registers a new command; in this case, an add function:

int f_add(int, int);
dconsole::BaseCmd* pcmd = dconsole::createCmd("add", "an addition function", f_add);

(Other examples can be found in the linked files in src\cmd_test.cpp) After entering the aforementioned code into the command line, if you try to invoke the next string: add 5 4, the command interpreter automatically parses it, performs the addition, and outputs a result. In the event you entered invalid parameters, it will tell you what is wrong:

input: "add not a number"

output: "add: bad arguments, usage RV: INT add INT INT;"

Figure 1 a screenshot of my test app.


Figure 1: A REPL implementation showing argument type checking occurring in real time.

Implementation

In this implementation, I will use the concept of type lists introduced by Andrei Alexandrescu.

Here is the basic class to implement type lists:

template <class T, class U>
struct TypeList {
    typedef T Head;
    typedef U Tail;
};

To create a type list, I use this class this way:

typedef TypeList< int, TypeList<float, TypeList<char, NullType> > > MyTypeList;

The NullType is a special, so-called "terminator" type. It helps to find the end of a list and can be simply defined as class NullType {} .

Now, assuming that I can easily create a type list that enumerates all my function types (return type and input parameters), I could perform an automatic type check and conversion of data received as a string. So, if the parameters are passed as strings (as in the REPL), they need some method to check them automatically. What is the easiest way of doing this?

I suppose having something like this would be the easiest:

bool foo(int, int);
Cmd* pcmd = createCmd(foo);

Now using this class, I can handle queries like this: pcmd->run("5 10");

The class itself will check the parameters and perform a call if the parameters are correct. Even though in this article I am not discussing the operation of parsing string and converting it to respective types, you can see an example of it in my sample program, which demonstrates an application of the described technique by implementing a debug console for the application.

Now, let's see how to implement createCmd to automatically create a type list. In order to implement this function, I will use templates. Here is the declaration of the function:

// first parameter - command name, second - function type;
template <typename R, typename T> CmdBase* createCmd(const std::string& name, T );

Here, the name is passed as a first parameter followed by pointer to the function. Implementation of this function is straightforward: It is used as a wrapper for the actual class that will do all the work.

Here is the implementation for a single-parameter version ( T can also be void for functions with no parameters). I just pass all parameters to the cmdCreator class.


template <typename R, typename T>
CmdBase* createCmd(const std::string& name, R (*fffp)(T))
{
    typedef void (*fffp_t)(T);
    return cmdCreator<fffp_t>::createCmd(name, fffp);
}

Implementation for two or more partameters looks pretty similar.:

template <typename R, typename T1, typename T2>
CmdBase* createCmd(const std::string& name, R (*fffp)(T1, T2))
{
    typedef R (*fffp_t)(T1, T2);
    return cmdCreator<fffp_t>::createCmd(name, fffp);
}

Now let's proceed to the cmdCreator class.

Declaration looks very simple: template <typename T> struct cmdCreator;

Here is the implementation for the function with one or none (void) input parameters:

template <typename R, typename T> struct cmdCreator< R (*)(T) > {
    static CmdBase* createCmd(const std::string& name, R (*fffp)(T)) {
        return new TypeList< R, TypeList<T, NullType> > (name, fffp);
    }
};

As you can see, the static function createCmd is the heart of our system. It creates type list in a correct way based on templates parameters. For demonstration purposes, here is the implementation for functions with two parameters:

template <typename R, typename T1, typename T2> struct cmdCreator< R (*)(T1, T2) > {
    static CmdBase* createCmd(const std::string& name, R (*fffp)(T1, T2)) {
        return new TypeList< R, TypeList<T1, TypeList<T2, NullType> > > (name, fffp);
    }
};

Notice that I create class TypeList while returning pointer to CmdBase. As I will show later, I will derive the TypeList class from BaseCmd to benefit from polymorphism.

Now I have a way to create the correct TypeList, but how can I apply this?

Let's consider the earlier example and first extend TypeList to add automatic type checking.

For simplicity, let's assume that we have already parsed the input string and created a list of parameters, which are wrapped in a Variant class. The Variant class is a useful concept. For additional info you may check out Boost.Variant or any other implementation. Here is how I implement type checking:

template <class T, class U>
struct TypeList: public CmdBase  
{
    //...
    enum { myIndex = NumEl< TypeList<Head, Tail> >::value };
	
    bool check(const std::vector< Variant >& params)
    {
        if(params.size() < myIndex)
            return false;
        return checkParams(params, 0);
    }

    bool static checkParams(const std::vector< Variant >& params, int offset = 0)
    {
        if(!params[offset].is<Tail::Head>())
            return false;
        return Tail::checkParams(params, offset+1);
    }

};

I also have to define a specialization for TypeList< R, NullType> class, because we want the recursion in the above function to end.

template <class R> struct TypeList< R, NullType>: public CmdBase 
{
    enum { myIndex = NumEl< TypeList<Head, Tail> >::value };
    //...
    
    void check(const std::vector< Variant >& params)
    {
// same as above
    }
    bool static checkParams(const std::vector< Variant >& params, int offset = 0)
    {
          return true;
    }

};

I introduced one more helper template class, NumEl, which keeps the counts for the type index in the type list. The declaration look like this: 



template < class TypeList> struct  NumEl;

Here is the implementation:

//Specialization for terminator class
template <class T>
struct NumEl<TypeList<T, NullType> > {
    enum { value  = 0 };
};

As you can see, it uses recursion for its Tail type:

template <class Head, class Tail>
struct NumEl<TypeList<Head, Tail> > {
private:
    enum { temp = NumEl<Tail>::value };
public:
    enum { value  = 1 + temp };
};

Now let's implement the run()method. I will also use the helper template class executor for which I will use same technique as for the cmdCreator class. But first, I'll provide a run()method. All that it does is call executor::run method, providing correct specialization.

virtual void run(const std::vector<Variant>& params)
{
    if( check(params) )
        executor<myfp, myIndex>::run(m_fptr_, params);
}

The executor class is declared as: template <typename T, int N> struct executor;

The first template parameter T is a function pointer that is passed by the TypeList class. The second template parameter is the number of function types. As you see, I use the myIndex compile-time constant, which was calculated with the help of our NumEl helper class.

The implementation for two parameters is:

template <typename T> struct executor<T, 2> 
{
    static void run(T fp, const std::vector<Variant>& vec) {
        fp( vec[0], vec[1] );
    }
};

Implementation for any other number of parameters is pretty straightforward.

As you can see, I use myfp , which is the pointer to the function. But how do I get its type? In cmdCreator, I pass function pointer as second parameter of TypeList. Our type list does not have any template parameter for it, so how can we construct it? For this I use a trick also based on templates.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video