Question

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.

Was it helpful?

Solution

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).

OTHER TIPS

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.

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.

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);
 }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top