Page 14 - HRM-00-v1
P. 14

  LANGUAGE FEATURES
 What are unevaluated operands in C++?
 The choice trick is instead widely used and combines different aspects of the language. It may seem complicated initially, but it’s really simple and allows us to solve a common problem with a very compact code.
Umm, does it? We still have to define a lot of classes to detect proper- ties (note that has_f serves only the purpose of probing a type for the member function f, but we want to also detect g and h in our example). Moreover, now we have to put everything in the body of the same func- tion; that can be confusing and isn’t desired in all cases.
How can we simplify this using one of the operators above? First, let’s introduce the choice class:
template<std::size_t N> struct choice: choice<N-1> {};
template<>
struct choice<0> {};
The class is defined in such a way that choice<N> inherits from choice<N-1>andsoonuntilchoice<0>.Itmeansthatwecanusechoice<N> as an argument to a function that requires choice<M> as long as M < N.
We can now rewrite the first group of functions in a smarter way by means of this tool and using decltype as shown in the previous sec- tions to probe (but not to evaluate!) our types and their compile-time properties:
template<typename T>
auto invoke(choice<3>)
-> decltype(std::declval<T>().h(), void()) { /* ... */ }
template<typename T>
auto invoke(choice<2>)
-> decltype(std::declval<T>().g(), void()) { /* ... */ }
template<typename T>
auto invoke(choice<1>)
-> decltype(std::declval<T>().f(), void()) { /* ... */ }
template<typename T> void invoke(choice<0>) { /* ... */ }
template<typename T> void invoke() {
invoke<T>(choice<100>{});
}
How does it work? Because of the rules of the language, the first func- tion that matches the given arguments is as follows:
template<typename T>
auto invoke(choice<3>)
-> decltype(std::declval<T>().h(), void()) { /* ... */ }
Here we use decltype to probe a compile-time property for the type T. Probe, not evaluate. Therefore, we have no side effects here.
SFINAE does the rest for us. In case the type T has a member h, we enter the first function. Otherwise, we receive a soft error, but the compiler continues to probe the other functions to turn it into a hard error and return to us. This isn’t even an option actually because of our fallback that will accept everything that doesn’t match one of the previous cases:
template<typename T> void invoke(choice<0>) { /* ... */ }
Another important thing that perhaps doesn’t immediately catch our attention is that there is no need to resort to the detection idiom to test our types, which relieves us from having to write a lot of code. Finally, we have as many functions as there are rules, something that is definitely easy to maintain and to reason on.
           14 | Human Readable Magazine
CONCLUSION
e have seen how the C++ language offers a few very interest-
W
Obviously the uses and abuses of decltype aren’t limited to this exam- ple, but I’ll leave the rest for future articles, hoping that you enjoyed what you’ve read so far.è hello@humanreadablemag.com
ing operators. Some aren’t very useful when you want to do SFINAE; others can be used within certain limits, but one in particu- lar seems to be good enough for most of the cases: decltype.
The choice trick is instead widely used and combines different as- pects of the language. It may seem complicated initially, but it’s really simple and allows us to solve a common problem with a very compact code. You’ve probably already encountered it in a simpler form, where the overload is solved by a combination of int and char, but the way it works is exactly the same. We just walked through an extended ver- sion of it.
 Illustration by pikisuperstar - www.freepik.com




























































   12   13   14   15   16