Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading a function to take true_type or false_type parameter vs using an if check?

Are there any benefits of overloading a method/function to take true_type or false_type parameter, compared to using one if statement? I see more and more code using overloaded methods with true_type and false_type parameters.

A short example using if statement

void coutResult(bool match)
{
    if (match)
        cout << "Success." << endl;
    else
        cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);

Compared to using an overloaded function:

void coutResult(true_type)
{
    cout << "Success." << endl;
}

void coutResult(false_type)
{
    cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);
like image 806
Markus Meskanen Avatar asked Jun 30 '13 08:06

Markus Meskanen


2 Answers

Your second example code wouldn't compile, which is a symptom of the difference between compile-time overload resolution and run-time conditional branching to "choose" which code to execute.

  • "Overloading a function to take true_type or false_type parameter" allows to make this choice at compile time if the decision only depends on types and compile-time constants.
  • "Using an if check" is necessary if the choice can't be done until run time when some variable values are known.

In your example the bool match = my_list.contains(my_value) is obviously not known before running the program, so you can't use overloading.

But the difference is most important for templates, where the choice is not only "which path to execute" but "which code to instantiate and compile and then call". The code from your linked video is rather in this spirit:

Consider this (wrong) code (omitting #includes and std::s for brevity):

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    if (is_same<Tag, random_access_iterator_tag>::value)
    {
        return last - first;
    }
    else
    {
        Diff n = 0;
        while (first != last) {
            ++first;
            ++n;
        }
        return n;
    }
}

There are at least two problems here:

  • If you try to call it with iterators that are not random-access (e.g. std::list<T>::iterator), it will actually fail to compile (with an error pointing the line return last - first;). The compiler has to instantiate and compile the entire function body, including both the if and the else branches (even though only one is to be executed), and the expression last - first is invalid for non-RA iterators.
  • Even if that compiled, we would be doing a test at run time (with the related overhead) for a condition that we could have tested as soon as compile time, and compiling unneeded code parts. (The compiler may be able to optimize that, but that's the concept.)

To fix it you can do:

// (assume needed declarations...)

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>());
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, true_type)
{
    return last - first;
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, false_type)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;

    Diff n = 0;
    while (first != last) {
        ++first;
        ++n;
    }
    return n;
}

or alternatively (possible here) with types directly:

/* snip */
distance(InIt first, InIt last)
{
    /* snip */
    return distanceImpl(first, last, Tag());
}

/* snip */
distanceImpl(InIt first, InIt last, random_access_iterator_tag)
{
    return last - first;
}

/* snip */
distanceImpl(InIt first, InIt last, input_iterator_tag)
{
    /* snip */
    Diff n = 0;
    /* snip */
    return n;
}

Now only the "correct" distanceImpl will be instantiated and called (the choice being done at compile time).

This works because the types (e.g. InIt or Tag) are known at compile-time, and is_same<Tag, random_access_iterator_tag>::value is a constant that is known at compile-time too. The compiler can resolve which overload is to be called, only based on types (that's overload resolution).

Note: Even though the "tags" are passed by value, they are only used as unnamed, unused parameters for "dispatch" (their value is not used, only their type) and the compiler can optimize them out.

You can also read Item 47: Use traits classes for information about types from Scott Meyers' Effective C++, Third Edition.

like image 56
gx_ Avatar answered Sep 28 '22 07:09

gx_


Such overloading is useful for compile-time optimizations. Note that in the example you are referring to a typedef is used to define a type which matches std::true_type or std::false_type. This type is evaluated in compile time. Creating a value of the type in a subsequent function call is necessary just to call the function: you cannot call a function with a type as an argument.

You cannot perform overloading based on a value of a variable. Overloading is performed based on type.

The code

bool match = my_list.contains(my_value);
coutResult(match);

doesn't compile as there is no coutResult(bool) function:

error: no matching function for call to ‘coutResult(bool)’ 
note: candidates are: void coutResult(std::true_type) 
note:                 void coutResult(std::false_type)

So, if your expression can be evaluated in compile-time, you may benefit from overloading functions for true_type and false_type thus removing additional checks in run-time. But if the expression is not a constant, you must use if.

like image 25
nullptr Avatar answered Sep 28 '22 09:09

nullptr