Line data Source code
1 0 : \cond NEVER 2 : Distributed under the MIT License. 3 : See LICENSE.txt for details. 4 : \endcond 5 : # SFINAE {#sfinae} 6 : 7 : \tableofcontents 8 : 9 : SFINAE, Substitution Failure Is Not An Error, means that if a deduced template 10 : substitution fails, compilation must continue. This can be exploited to make 11 : decisions at compile time. See [here](http://nilsdeppe.com/posts/tmpl-part1) 12 : for a discussion using `std::enable_if` to remove certain functions from 13 : overload resolution or certain template specializations from name lookup. 14 : Another method of controlling name lookup resolution is using 15 : `std::void_t`. `void_t` is a metafunction from types to `void`, that is 16 : 17 : ```cpp 18 : template <typename... Args> 19 : using void_t = void; 20 : ``` 21 : 22 : `void_t` is useful when used in combination with `decltype` and `std::declval` 23 : to probe if a type has certain members. For example, we can implement a type 24 : trait to check if a type `T` is iterable by first have the general definition 25 : inherit from `std::false_type` as follows, 26 : 27 : ```cpp 28 : template <typename T, typename = void> 29 : struct is_iterable : std::false_type {}; 30 : ``` 31 : 32 : Next we will have specialization that uses `void_t` to check if the type `T` 33 : has a `begin()` and `end()` function. 34 : 35 : ```cpp 36 : template <typename T> 37 : struct is_iterable<T, std::void_t<decltype(std::declval<T>().begin(), 38 : std::declval<T>().end())>> 39 : : std::true_type {}; 40 : ``` 41 : 42 : What is happening here? Well, we use `std::declval` to convert the type `T` 43 : to a reference type, which allows us to call member functions inside `decltype` 44 : expressions without having to construct an object. First we try to call the 45 : member function `begin()` on `std::declval<T>()`, and if that succeeds we 46 : throw away the result using the comma operator. Next we try to call `end()` 47 : on `std::declval<T>()`, which, if it succeeds we get the return type of 48 : using `decltype`. Note that `decltype` is important because we can only call 49 : member functions on reference types inside of `decltype`, not just anywhere. 50 : Finally, if all this succeeded use `void_t` to metareturn `void`, otherwise 51 : the template parameters of `void_t` fail to evaluate and the specialization 52 : cannot be resolved during name lookup. We could just as well use 53 : 54 : ```cpp 55 : template <typename T> 56 : struct is_iterable<T, std::void_t<decltype(std::declval<T>().begin()), 57 : decltype(std::declval<T>().end())>> 58 : : std::true_type {}; 59 : ``` 60 : 61 : Which of the two implementations of the `is_iterable` is preferred is simply 62 : a matter of taste, both behave correctly. 63 : 64 : If you're reading closely you might wonder why the `void_t` is necessary at 65 : all, why not just `decltype(...)`? Well the reason is that since the default 66 : template parameter metavalue is `void`, the specialization cannot be resolved 67 : during name lookup unless the second template parameter in the specialization 68 : is either `void` as well or is explicitly specified when the class template 69 : is being invoked. Thus, the clearest implementation probably is 70 : 71 : ```cpp 72 : template <typename T, typename = std::void_t<>> 73 : struct is_iterable : std::false_type {}; 74 : 75 : template <typename T> 76 : struct is_iterable<T, std::void_t<decltype(std::declval<T>().begin(), 77 : std::declval<T>().end())>> 78 : : std::true_type {}; 79 : ``` 80 : 81 : You could now also define a helper type alias and constexpr boolean 82 : 83 : ```cpp 84 : template <typename T> 85 : using is_iterable_t = typename is_iterable<T>::type; 86 : 87 : template <typename T> 88 : constexpr bool is_iterable_v = is_iterable<T>::value; 89 : ```