Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
You can always use such explicit function template specification as in the example above, but it is often convenient to leave off the template
arguments and let the compiler deduce them from the function arguments, like
this:
If both i and j are ints, the compiler
knows that you need min<int>( ), which it then instantiates
automatically. The types must be identical because the template was originally
specified with only one template type argument used for both function
parameters. No standard conversions are applied for function arguments whose
type is specified by a template parameter. For example, if you wanted to find
the minimum of an int and a double, the following attempt at a
call to min( ) would fail:
int z = min(x, j); // x is a double
Since x and j are distinct types, no single
parameter matches the template parameter T in the definition of min( ),
so the call does not match the template declaration. You can work around this
difficulty by casting one argument to the other s type or by reverting to the
fully-specified call syntax, as in:
int z = min<double>(x, j);
This tells the compiler to generate the double
version of min( ), after which j can be promoted to a double
by normal standard conversion rules (because the function min<double>(const
double&, const double&) would then exist).
You might be tempted to require two parameters for min( ),
allowing the types of the arguments to be independent, like this:
template<typename T, typename
U>
const T& min(const T& a,
const U& b) {
return (a < b) ? a : b;
}
This is often a good strategy, but here it is problematic
because min( ) must return a value, and there is no satisfactory
way to determine which type it should be (T or U?).
If the return type of a function template is an independent
template parameter, you must always specify its type explicitly when you call
it, since there is no argument from which to deduce it. Such is the case with
the fromString template below.
//: C05:StringConv.h
// Function templates to convert to and from strings.
#ifndef STRINGCONV_H
#define STRINGCONV_H
#include <string>
#include <sstream>
template<typename T> T fromString(const
std::string& s) {
std::istringstream is(s);
T t;
is >> t;
return t;
}
template<typename T> std::string toString(const
T& t) {
std::ostringstream s;
s << t;
return s.str();
}
#endif // STRINGCONV_H ///:~
These function templates provide conversions to and from std::string
for any types that provide a stream inserter or extractor, respectively. Here s
a test program that includes the use of the standard library complex
number type:
//: C05:StringConvTest.cpp
#include <complex>
#include <iostream>
#include "StringConv.h"
using namespace std;
int main() {
int i = 1234;
cout << "i == \""
<< toString(i) << "\"" << endl;
float x = 567.89;
cout << "x == \"" <<
toString(x) << "\"" << endl;
complex<float> c(1.0, 2.0);
cout << "c == \"" <<
toString(c) << "\"" << endl;
cout << endl;
i =
fromString<int>(string("1234"));
cout << "i == "
<< i << endl;
x = fromString<float>(string("567.89"));
cout << "x == " << x <<
endl;
c = fromString<complex<float>
>(string("(1.0,2.0)"));
cout << "c == " << c <<
endl;
} ///:~
The output is what you d expect:
i == "1234"
x == "567.89"
c == "(1,2)"
i == 1234
x == 567.89
c == (1,2)
Notice that in each of the instantiations of fromString( ),
the template parameter is specified in the call. If you have a function
template with template parameters for the parameter types as well as the return
types, it is important to declare the return type parameter first, otherwise
you won t be able to omit the type parameters for the function parameters. As
an illustration, consider the following well-known function template:
//: C05:ImplicitCast.cpp
template<typename R, typename
P>
R implicit_cast(const P& p) {
return p;
}
int main() {
int i = 1;
float x = implicit_cast<float>(i);
int j = implicit_cast<int>(x);
//! char* p = implicit_cast<char*>(i);
} ///:~
If you interchange R and P in the template
parameter list near the top of the file, it will be impossible to compile this
program because the return type will remain unspecified (the first template
parameter would be the function s parameter type). The last line (which is
commented out) is illegal because there is no standard conversion from int
to char*. implicit_cast is for revealing conversions in your code
that are allowed naturally.
With a little care you can even deduce array dimensions. This
example has an array-initialization function template (init2) that performs
such a deduction:
//: C05:ArraySize.cpp
#include <cstddef>
using std::size_t;
template<size_t R, size_t C, typename T>
void init1(T a[R][C]) {
for(size_t i = 0; i < R; ++i)
for(size_t j = 0; j < C; ++j)
a[i][j] = T();
}
template<size_t R, size_t C, class T>
void init2(T (&a)[R][C]) { // Reference parameter
for(size_t i = 0; i < R; ++i)
for(size_t j = 0; j < C; ++j)
a[i][j] = T();
}
int main() {
int a[10][20];
init1<10,20>(a); // Must specify
init2(a); // Sizes deduced
} ///:~
Array dimensions are not passed as part of a function
parameter s type unless that parameter is passed by pointer or reference. The
function template init2 declares a to be a reference to a
two-dimensional array, so its dimensions R and C are deduced by
the template facility, making init2 a handy way to initialize a
two-dimensional array of any size. The template init1 does not pass the
array by reference, so the sizes must be explicitly specified, although the
type parameter can still be deduced.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |