Page 13 - HRM-00-v1
P. 13

 LANGUAGE FEATURES
 What are unevaluated operands in C++?
   Because of how std::enable_if_t works, the first function is selected only if T::func has the noexcept qualifier, and in this case the return type is void. Otherwise, the second function is picked up and the re- turn type is int; that is our error code.
Again, remember that the operands of the noexcept operator are un- evaluated and therefore the function call item.func() is only taken in consideration to probe its compile-time feature, and we have no actu- al side effects at runtime when entering the function.
sizeof
sizeof isn’t as good as the two operators above to do SFINAE. Howev- er, one can imagine some interesting uses for it, in particular when it comes to working with something like the small buffer optimization. In this case, we can exploit the properties of this operator to provide different implementations of a class template when the size of the type we use to specialize it fits that of a void *:
template<typename, typename = std::bool_constant<true>> struct can_sbo { /* ... */ };
template<typename T>
struct can_sbo<T, std::bool_constant<sizeof(T) <= sizeof(void *)>> { /* ... */ };
I usedsizeof(T)in the example, but we aren’t constrained to it. In fact, we can use any expression we want. As an example sizeof(T::member). Of course, it won’t be evaluated.
typeid
Let’s go further and see what can offer us typeid. As you know, it’s purpose is to return information about types, nothing less and noth- ing more.
Unfortunately, this operator isn’t very SFINAE-friendly and it’s not worth it to show an example, although one can perhaps build some- thing ad hoc with it.
THE CHOICE TRICK
We have seen how some operators whose operands are not eval- uated can be useful in some cases. Now it’s time to see one of them in action in a real-world case that I’ve faced more than once.
In particular, have you ever worked with templates and found your- self wanting to execute a function if the type has a given property, another function if it has a different property, or a third function as a fallback? Quite common indeed.
The hard way is something that looks like the following:
template<typename T> std::enable_if_t<has_h<T>> invoke() { /* ... */ }
template<typename T> std::enable_if_t<has_g<T> and !has_h<T>> invoke() { /* ... */ }
template<typename T>
std::enable_if_t<!has_g<T> and !has_h<T> and has_f<T>> invoke() { /* ... */ }
template<typename T>
std::enable_if_t<!has_f<T> and !has_g<T> and !has_h<T>> invoke() { /* ... */ }
where has_FUNC is the typical detection idiom: template<typename T, typename = void>
struct has_f: std::false_type {};
template<typename T>
struct has_f<T, std::void_t<decltype(std::declval<T>().f())>> : std::true_type {};
Pretty annoying indeed, and the conditions become more and more complex in order to avoid ambiguities every time we want to add a switch to our cascade.
Fortunately, C++17 introduced if constexpr that clears this a bit:
template<typename T> void invoke() {
if constexpr(has_h<T>) { /* ... */
} else if constexpr(has_g<T>) { /* ... */
} else if constexpr(has_f<T>) { /* ... */
}else{ /* ... */
     } }
 Can we do something similar with it? Actually, yes. As an example, consider the case in which we want to provide two different implementations of the same function: the former throws exceptions in case of errors;
  September 2019 | 13
 





























































   11   12   13   14   15