Reputation: 1668
I was watching the latest C9 lecture and noticed something interesting..
In his introduction to type_traits, Stephan uses the following (as he says, contrived) example:
template <typename T>
void foo(T t, true_type)
{
std::cout << t << " is integral";
}
template <typename T>
void foo(T t, false_type)
{
std::cout << t << " is not integral";
}
template <typename T>
void bar(T t)
{
foo(t, typename is_integral<T>::type()
);
}
This seems to be far more complicated than:
template <typename T>
void foo(T t)
{
if(std::is_integral<T>::value
)
std::cout << "integral";
else
std::cout << "not integral";
}
Is there something wrong with the latter way of doing it? Is his way better? Why?
Thanks.
Upvotes: 5
Views: 3994
Reputation: 4659
The example below should illustrate the difference. Let's add struct X:
struct X
{
X(int)
{
}
};
and modify one foo as below:
template <typename T> void foo(T t, true_type)
{
std::cout << t << " is integral";
X x(t);
}
template <typename T> void foo(T t, false_type)
{
std::cout << t << " is not integral";
}
Then:
template <typename T> void bar(T t)
{
foo(t, typename is_integral<T>::type());
}
Will still compile for all T types (including integer types; it may cause warning but will compile).
The other equivalent code:
template <typename T> void foo(T t)
{
if(std::is_integral<T>::value)
{
std::cout << "integral";
X x(t);
}
else
std::cout << "not integral";
}
will often fail to compile as you will not be able to instantiate X for types other then integral and eventually floating point and user defined classes which have operator int() (operator short() or operator long() will not do).
Upvotes: 10
Reputation: 361732
Using first approach, you can implement static-dispatch without usingif/else
or switch
.
template <typename T>
void Dispatch(T t)
{
//Call foo(T, true_type) or foo(T, false_type)
//depending upon the *type* of second parameter.
foo(t, typename is_integral<T>::type());
}
Using second approach, you've to implement this using if/else
or switch
block,
template <typename T>
void Dispatch(T t)
{
//Call foo(T, true_type) or foo(T, false_type)
//depending upon the *value* of value.
if(std::is_integral<T>::value)
foo(t, true_type());
else
foo(t, false_type());
}
But if you want to implement your Dispatch()
function without using if/else
, and at the same time you want to use std::is_integral<T>::value
, then you have to re-write your foo()
function, like this,
template <bool b>
void foo(T t)
{
std::cout << t << " is integral";
}
template <>
void foo<false>(T t)
{
std::cout << t << " is not integral";
}
And your Dispatch()
function would look like,
template <typename T>
void Dispatch(T t)
{
//using std::is_integral<T>::value!
const bool selector = (bool) std::is_integral<T>::value;
foo<selector>(t);
}
Upvotes: 0
Reputation: 6375
Basically first option uses knowledge about "integrality" of the type at compile-time and the second option - moves this knowledge to run-time.
This means that we may use for integral types code which is not compilable for non-integral types.
Upvotes: 14
Reputation: 7125
For such a small, contrived example, there is not much advantage to doing it the first way. The advantage comes when you have more complex situations. It's essentially analogous to using inheritance-based polymorphism or if/switch statements in object-oriented programming. The more complex solution allows you greater flexibility; you can easily add types without modifying existing code.
If you know all of the types you will ever need (e.g. you are using a boolean, as the example), then the simpler solution may be better. But if you do not have fixed requirements (and when are requirements ever fixed?), the more complex, but more flexible solution will probably be easier in the long run.
Upvotes: 1