Monday, February 13, 2012

Argument Deduction

Hello everybody

Till now, we have been seeing the powerful of templates but, like I showed,
sometimes we can get some unexpected results so I like to check out this topic, argument deduction

There are so many times that C++ compiler must conclude that T must
be int or any type, what becomes this?

An example we unexpected:

#include <iostream>
#include <complex>
namespace mySpace{ // getting sure that this is the class we call becouse std::max is implemented
    template < class T > // for any class/typename T
    inline const T& max (const T& a, const T& b){ //
        return a>b?a:b; // where the operator '>' is implemented
    }
}
int main(){
    std::cout << mySpace::max(10,20) << std::endl;// works perfectly as we saw
    //std::cout << mySpace::max(15,21.1) << std::endl;// error: no matching function for call to ‘max(int, double)’
    //std::cout << mySpace::max('a',21) << std::endl;// error: no matching function for call to ‘max(char, int)’
    
    //some ways to fix it:
    
    // static_cast is a powerful tool, and I'm going to explain it while course
    std::cout << mySpace::max(static_cast<double>(15),21.1) << std::endl;
    
    // double(x) call the costructor of double, so 15 is an double
    std::cout << mySpace::max(double(15),21.1) << std::endl;
    
    // this is a bad practice, this cast is highlighting to the compiler that is a double
    //in this case works, but take care it
    std::cout << mySpace::max((double)15,21.1) << std::endl;
    
    //and the first solucion where we really use templates
    //we are forcing that max's call have to be doouble
    std::cout << mySpace::max<double>(15,21.1) << std::endl;
    
    //this examples also works but returning types is unexpected
    //we unlike 97's answer, we wanted a, as char
    //but our template function is unable to do it
    std::cout << mySpace::max<int> ('a',21) << std::endl;
    
    //This works too but don't make much sense
    std::cout << mySpace::max(int('a'),21) << std::endl;
    return 0;
}

This may appear to be a good method to enable passing two call parameters. Thus, we'll 
have two templates types as arguments.


Example:

#include <iostream>
#include <complex>
namespace mySpace{ // getting sure that this is the class we call becouse std::max is implemented
    template < class T1 , class T2 > // for any class/typename T
    inline const T1& max (const T1& a, const T2& b){ //
        return a>b?a:b; // where the operator '>' is implemented
    }
}
int main(){
    std::cout << mySpace::max(10,20) << std::endl;//no problems
    
    //Now, the errors we had, have been fixed but isn't enough
    std::cout << mySpace::max(15,21.1) << std::endl;
    std::cout << mySpace::max('a',21) << std::endl;
    //both of us calls makes this warning
    //main.cpp:14:37:   instantiated from here
    //main.cpp:6:22: warning: returning reference to temporary
    
    return 0;
}
This template method have some issues, for example, we're defining the return type with the first argument, then:

std::cout << mySpace::max(15,21.1) << std::endl;

This call returns 21 (with warning), the comparison works perfectly but at the moment to return value, it's needed an int, so have to truncate the double. In fact, it's not as simple as truncate and the code instance a local variable (temporaly), making that our return definacion as constant is not exaclty correct, so makes a warning.

Therefore, it may sense to define a returning type, a new template, example:

#include <iostream>
#include <complex>
namespace mySpace{ // getting sure that this is the class we call becouse std::max is implemented
    template < class T1, class T2, class RT > // for any class/typename T
    inline RT max (const T1& a, const T2& b){ //Note that return type isn't constant
        return a>b?a:b; // where the operator '>' is implemented
    }
}
int main(){
    //correct but tedious
    std::cout << mySpace::max<int,double,double>(15,21.1) << std::endl; 
    std::cout << mySpace::max<char,int,char>('a',100) << std::endl;
    
    return 0;
}

In this way, we can control types and how RT can't be deduced we have to list explitly each template type..
Another approach is to specify only the return type, in general we must specify all the argument types up to the last argument that cannot be determined by deduction progress.

Thus, if we change the order of the templates we could only specify RT, let's see:

#include <iostream>
#include <complex>
namespace mySpace{ // getting sure that this is the class we call becouse std::max is implemented
    template < class RT, class T1, class T2 > // for any class/typename T
    inline RT max (const T1& a, const T2& b){ //Note that return type isn't constant
        return a>b?a:b; // where the operator '>' is implemented
    }
}
int main(){
    std::cout << mySpace::max<double>(15,21.1) << std::endl; 
    std::cout << mySpace::max<char>('a',100) << std::endl;
    std::cout << mySpace::max<char,char,int>('a',100) << std::endl;
    return 0;
}

No comments:

Post a Comment