Channels ▼
RSS

C/C++

Extracting Function Parameter and Return Types in C++


Let's delve further into source code to understand how it works. If we look back at the point where TypeList is created (cmdCreator::createCmd) we actually have all information that we need: R, T1, T2, etc. we just need some way to convert TypeList<R, TypeList<T1, TypeList<T2, ....> > > to R (*fptr)(T1, T2, ...);

The helper classes I use to do this conversion I call TypeConv (TypeConv1, TypeConv2, etc. for the number of supported types).

I'll start from the simplest specialization for terminal classes, because it shows very clearly the idea. The cass has only one typedef, that's all:

template <typename T>
struct TypeConv< TypeList<T, NullType> > {
    typedef T (*fp)(void);
};

Functions with one parameter looks similar:

template <typename T, typename Tp>
struct TypeConv1<  Tp, TypeList<T, NullType> > {
    typedef Tp (*fp)(T); 
};

Now let's examine generic version of TypeConv

template <class T, class U>
struct TypeConv<TypeList<T, U> > {
    typedef typename    TypeConv1<  T,  
        TypeList<typename U::Head, typename U::Tail>
        > ::fp  fp;    
};

This code basically creates a typedef in a recursive way. The first parameter of TypeConv1 is a Head type of TypeList and the second is Tail of TypeConv's template parameter U, which was reformatted as TypeList<typename U::Head, typename U::Tail>. Here is TypeConv1 class:

template <class T, class U, typename Tp>
struct TypeConv1<Tp, TypeList<T, U> > {
    typedef typename    TypeConv2<
        Tp,
        T,
        TypeList<typename U::Head, typename U::Tail>
    > ::fp  fp;
};

This code calls TypeConv2 (which looks similar). If, for example, U were the NullType , then terminal version specialization would be used. Otherwise, the generic approach is used and recursion continues until U would be NullType.

Having implemented those classes, I can add them with just two lines into the TypeList class:

template <class T, class U>
struct TypeList: public CmdBase {
    typedef T Head;
    typedef U Tail;
    typedef TypeList<T, U> MyType;

    // 2 lines :-)
    typedef typename TypeConv< MyType >::fp myfp;
    myfp m_fptr_;

    TypeList(std::string name, myfp fptr):CmdBase(name), m_fptr_(fptr) {}
    //...
};

That's it!

I certainly could add another template parameter for the type of the function, but I wanted to show how we can use template metaprogramming to achieve the same result using information that we already have. This "type conversion" can be used for other different purposes.

In the same way, I can add functionality to automatically print usage information for the function. One possible implementation could look like this:

const std::string getUsage()
{
        std::string str;
        std::string rv = Type2String<Head>::name();
        str.append("RV: ").append(rv); str.append(" ").append(getName()).append(" ");
        str.append( MyType::get_usage() ); str.append(" ");
        return str;
}

static std::string get_usage()
{
        std::string p = Type2String<Tail::Head>::name();
        p.append(" ");
        p.append( Tail::get_usage() );
        return p;
}

I used the same approach here: recursive calls for Tail type. Type2String is just a simple class that returns a string name for a type. I did not show it here because its implementation is trivial, but you can see everything in the accompanying code.

To sum up, I'll provide here is full code for the TypeList class:

template <class T, class U>
struct TypeList: public CmdBase 
{
    typedef T Head;
    typedef U Tail;
    typedef TypeList<T, U> MyType;
    enum { myIndex = NumEl< TypeList<T, U> >::value };

    typedef typename TypeConv< MyType >::fp myfp;
    myfp m_fptr_;

    TypeList(std::string name, myfp fptr):CmdBase(name), m_fptr_(fptr) {}

    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);
    }

    static std::string get_usage()
    {
        std::string p = dconsole::BaseCmd::Type2String<Tail::Head>::name();
        p.append(" ");
        p.append( Tail::get_usage() );
        return p;
    }

    const std::string getUsage()
    {
        std::string str;
        std::string rv = dconsole::BaseCmd::Type2String<Head>::name();
        str.append("RV: ").append(rv); str.append(" ").append(getName()).append(" ");
        str.append( MyType::get_usage() ); str.append(" ");
        return str;
    }

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

Further improvements

What should be done with the return value? It's up to you. I just print it into my debug console output, so the user will see the result of an executed command. If you try to use the output value, recall that it might be void.

Another thing you can add is a compile-time check for function parameters to be of a fundamental class, if you would like to constrain them.

Conclusion

By providing type extraction for functions, I now have a flexible mechanism to inspect function type parameters for different needs.

The code provided in this article was applied to create a debug console (like many games have) and can be found here: http://dconsole.googlecode.com.


Sergii Biloshytski started as an embedded programmer, but changed course and became a game programmer. At Ubisoft, he's been working on such titles as Blazing Angels, From DUST, Assassins Creed, and others.


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