Your second sample code will not compile, which is a sign of the difference between overload resolution at compile time and conditional branching of runtime to โchooseโ which code to execute.
- "Overloading the function to accept the
true_type or false_type " allows you to make this choice at compile time if the decision depends only on the types and constants of compilation time. - "Using an
if check" is necessary if the selection cannot be made before the run time, when some variable values โโare known.
In your example, bool match = my_list.contains(my_value) is obviously unknown before running the program, so you cannot use overloading.
But the difference is important for templates , where the choice is not only "which way to execute", but also "which code to create and compile, and then call." The code from your linked video is more like that:
Consider this (incorrect) code (omitting #include and std:: 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 using iterators that are not random access (for example,
std::list<T>::iterator ), it will not actually be able to compile (with an error pointing to the line return last - first; ). The compiler must create an instance and compile the entire body of the function, including the if and else branches (although only one needs to be executed), and the last - first expression is invalid for iterators without RA. - Even if this is compiled, we will test at runtime (with the appropriate overhead) for a condition that we could test as soon as compilation time, and compile unnecessary parts of the code. (The compiler can optimize this, but this is a concept.)
To fix this, you can:
// (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 (maybe here) with types directly:
distance(InIt first, InIt last) { return distanceImpl(first, last, Tag()); } distanceImpl(InIt first, InIt last, random_access_iterator_tag) { return last - first; } distanceImpl(InIt first, InIt last, input_iterator_tag) { Diff n = 0; return n; }
Now only the โcorrectโ distanceImpl will be created and called (the selection is made at compile time).
This works because 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 also known at compile time. The compiler can decide what kind of congestion needs to be called, only on the basis of types (this is the permission of congestion).
Note. Despite the fact that "tags" are passed by value, they are used only as unnamed, unused parameters for "sending" (their value is not used, only their type), and the compiler can optimize them.
You can also read Point 47: Use attribute classes for type information from Scott Meyers Effective C ++, Third Edition a>.