Function overload to get the true_type or false_type vs parameter with if check?

Are there any advantages to overloading a method / function to accept a true_type or false_type parameter compared to using a single if ? I see more and more code using overloaded methods with true_type and false_type .

A brief example using the if

 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); 
+6
source share
3 answers

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:

 /* 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 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>.

+10
source

Such overloading is useful for optimizing compilation time. Note that in the example that you reference typedef , it is used to determine the type that matches std::true_type or std::false_type . This type is evaluated at compile time. Creating a type value in a subsequent function call is simply necessary for calling the function: you cannot call a function with a type as an argument.

You cannot overload based on value variable. Overloading is based on type.

Code

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

does not compile, since 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 at compile time, you can use the overload functions for true_type and false_type , thereby removing extra checks at run time. But if the expression is not constant, you should use if .

+3
source

In C ++, usually you cannot explicitly get the type of an object due to the lack of the Reflection property. If you want to work according to the type of object, you need such an overload of functions.

If you want to use the If statement, you need to define some additional element or method for each associated class in order to obtain class information that may not be resolved in some contexts, and it introduces additional overhead. As a more practical way, you can use typeid instead of this additional element, and the object identifier can be compared with the proposed class identifier at run time.

In addition, if classes are very different from each other (for example, they do not have a common base), and they are defined and intended for completely separate puspose, it is good design practice to process them in separate methods instead of one conditional one.

0
source

Source: https://habr.com/ru/post/948414/


All Articles