13 August, 2010

[C++ template] Use one member function to return different class member with different type.

Why would I need a class member function to return different type of member variables?

Sometime you will need a class that its behavior will be a tiny little different base on its role. e.g. server and client. When implementing server/client classes, they both need a send function and a receive function and the implementation of send/recv function on server class are almost the same as in the client class, the only difference in their implementation might be the class member they manipulate.
To simplify the problem, here is a concrete example:
We want to implement an adder class that can add two values and return the result. There will be two types of adders: int_adder and string_adder, and they have to use the same code base because they are using almost identical logic.
So the base class will be look like:
class base_adder
{
    public:
        //T is either inner_int_type or inner_string_type
        T getDataBlock()
        {
        
        }

        void exec()
        {
            //add b to a
        }

        //T is either int or string
        T getResult()
        {

        }

        struct inner_int_type
        {
            inner_int_type():a(0), b(0), result(0){}
            int a, b;
            int result;
        };

        struct inner_string_type
        {
            string a, b;
            string result;
        };

        inner_int_type int_data_block;
        inner_string_type string_data_block;
};

OK, here is the base adder, but remember that we have two types of adder, int_adder and string_adder. How do we distinguish it? The straightforward idea is using constructor and pass the type into it. Then the constructor of the base_adder would be like:

class base_adder
{
        enum
        {
            int_adder,
            string_adder
        };

        base_adder( int type ):m_adder_type(type){}
        ...
};
So far so good, however, let look at the exec() member function first, how do we know which data block to use? Should we use the int_data_block or string_data_block? It is OK to use if...else statement to do this:

This implementation is not efficient because of the extra if...else statement. However, it is not the worst part, let look into the getDataBlock member function. Different types of adder has to return different types of data block (inner_int_type or inner_string_type). We cannot even use if...else statement to do that! Because one member function can only return one type of result. Therefore, the following code is illegal:

When using template type as the return type of a function, you have to specified the return type while using that function: e.g. getDataBlock<inner_int_type>(). I will explain why I hate this later.

You might think of "function overloading"! Yes, we just need to overload getDataBlock function to return different types of data, like this:

You can see that the code are duplicated, and you also need to overload getResult function. If we add more functions like getA(), getB() ... etc. We have to overload them all.
I am looking for the perfect solution to minimize the code duplication. Hence, why not using template to do that? The actual problem I am facing is "I need to know the return type base on the type of adder". To solve this problem, type trait is handy here, and we can also use some trick to deal with exec() and getDataBlock() function:

Everything is working now, following is the using example, you can see that we don't need to take care the return type, we just use it directly without thinking:

Here is the program output:


Even though everything is working now, but you can see that I still use function overloading to return different types of data block, the source codes are still duplicated! I need a way to return different blocks base on the type of adder! The solution is using a new template trick "type2data", the trick works as follow:

The code is neater, no duplication anymore! The "type2data" trick does help.
Nevertheless, it is still not perfect. Consider adding a new method "getResult", the return type of this function is the type of "result" in the data block. We need to write new pair of type2data for this purpose, like this:

Why not using template to implement "type2data" function? OK, the code would be like:

It is a disaster to have a template return type, because you have to use this template function like this:

It ruined my original purpose -> "I don't want to care about the type while using this class". The only solution is to have a universal generic type2data function. It is our final goal. After analyzing the "type2data" function, I found out that defining the return type (RET) as one of the template parameters is unneeded, because the return type must be one of the input type (TYPE_A or TYPE_B). So my final generic type2data is implemented as follow:


The code is simple and short and easy to understand now. Using C++ template, we reduce some runtime overhead like the if...else statement and make the code more flexible and more reusable.
Anyways, if you want to show off your template skills, you can implement the type2data likes below, and it is actually my first version of type2data:

I hope this article is useful to you :)

No comments: