
int , somewhere it is necessary to establish whether one type is inherited from another, or whether one can be converted type in another. We will consider the mechanism of applying SFINAE in a classic example: checking whether a function member exists in a class with given types of arguments and a return value. I will try in detail and in detail to go through all the stages of creating a verification metafunction and to trace where it comes from. int difference(int val1, int val2) { return val1 - val2; } template<typename T> typename T::difference_type difference(const T& val1, const T& val2) { return val1 - val2; } difference function works great for integer arguments. But with custom data types, subtleties begin. The result of the subtraction is not always of the same type as the operands. So, the difference of two dates is the time interval, which itself is not a date. If the custom type MyDate has a typedef MyInterval difference_type; definition inside it typedef MyInterval difference_type; and the subtraction MyInterval operator - (const MyDate& rhs) const; , the template overload is applicable to it. Calling the difference(date1, date2) will be able to “see” both the template overload, and the version accepting an int , while the template overload will be considered more suitable.MyString type, in which there is no difference_type , during substitution will cause an error: the function would return a non-existent type. Calling the difference with arguments of type MyString can only “see” the int version of the function. This single version will be sufficiently suitable only if the conversion operator to a number is defined in MyString . The construction of val1 - val2 requires a binary minus operator and can also generate a syntax error. It turns out that the template function difference checks the type of the argument for the simultaneous fulfillment of three conditions at once: the presence of a difference_type , the presence of a subtraction operator and the possibility of reducing the result of a subtraction to the type difference_type (conversion is implied by the return operator). But while for types that violate the first condition, this overload is not visible, violation of the second or third condition will cause a compilation error.void foo(int) method in some type. Caring STL, especially starting with version C ++ 11, has already identified many useful type_traits for us, which are mainly located in type_traits and limits headers, but for some reason it is precisely what we boldly planned to do. Metafunction usually looks like a template structure without data, within which the result of an operation is defined: a type specified through typedef with the name type or a static constant value . This agreement adheres to the STL, and we don’t have any reasons to be original, therefore we will adhere to the established pattern. template<typename T> struct has_foo{}; template<typename T> struct has_foo{ static constexpr bool value = true; // , , "". }; has_foo should not cause compilation errors, no matter what parameter we set up. And an error will occur if it suddenly turns out that it turns out that we need two overloads of one test function. One of them, the “detector”, should be syntactically correct only for types that contain the desired method. The other, the “substrate,” must be omnivorous, that is, be sufficiently suitable for any substituted types. At the same time, the “detector” should have a distinct advantage in “suitability” over the “substrate”. The least priority and, at the same time, the most omnivorous in determining overloads is ellipsis (a three-dot designating a variable number of arguments): template<typename T> struct has_foo{ void detect(...); // "" . static constexpr bool value = true; // -, ! }; decltype , which determines the type of expression, and the expression itself is not calculated and is not translated into code. Let's substitute as an expression a call for that method, with arguments of the desired type. Then the decltype answer is the return type of the method. And if there is no method with the same name, or it accepts other types of arguments, then we will get the very controlled error that we wanted. Let the “detector” return the same as foo : template<typename T> struct has_foo{ void detect(...); template<typename U> decltype(U().foo(42)) detect(const U&); static constexpr bool value = true; // ! }; const T& to detect , it turns out that U is the same type as T To check the conformity of the return type, we will later refine the detector or think of something else along the way.has_foo type in has_foo that does not have a default constructor? Of course, a compilation error. It would be more correct to declare any function that returns a value of the desired type. It will not be called anyway, and the desired effect will be achieved. STL took care of this: in the header of the utility there is a declval function: template<typename T> struct has_foo{ void detect(...); template<typename U> decltype(std::declval<U>().foo(42)) detect(const U&); static constexpr bool value = true; // ! }; decltype will help us. The “substrate” type of the return value is always void , and the “detector” - the type returned by the method, that is, in the case when the method meets our requirements ... the same void . Will not work. Let's change the type for int to “substrate”. Then the check is simple: if the call to detect on the object T is of type void , then the “detector” worked and the method fully complies with our requirements. If the type is different, then either the “substrate” has worked, or the method exists, accepts those very arguments, but returns something wrong. We check how carefree STL is, and right there we find the type checking is_same for is_same equality: template<typename T> struct has_foo{ private: // . static int detect(...); // . template<typename U> static decltype(std::declval<U>().foo(42)) detect(const U&); public: static constexpr bool value = std::is_same<void, decltype(detect(std::declval<T>()))>::value; // , . }; declval .enable_if included. Its parameters are a Boolean constant and a type ( void by default). If true passed, then the type is present in metafunction: the one that is passed in the second parameter. If false passed, then there is no type there, which creates that very controlled error. In the light of the considerations listed above in a neat list, it must be remembered that enable_if can “cross out” the function overload only if it is a template, and also take care that the list of uncrowded overloads never remains completely empty. You can use enable_if in specializations of the template class, but in this case it is no longer SFINAE, but something like static_assert .Source: https://habr.com/ru/post/205772/
All Articles