How does std :: is_assignable work?

Here's the implementation of std :: is_assignable , I spent several hours trying to figure out how it can statically determine the type of the template object, but could not.

The standard is_assignable states that both sides of an assignment are converted to std::add_rvalue_reference<T>::type . I do not receive this offer and do not see how std::add_rvalue_reference<T>::type can be used to predict the type of an object.

Can anyone give me a simple explanation that I can use as a first step to understand how std :: is_assignable works?

+5
source share
1 answer

Here is the same code with some lexical obfuscation:

  1043 template <typename Tp, typename Up> 1044 class is_assignable_helper 1046 { 1047 template <typename Tp1, typename Up1> 1048 static decltype(declval<Tp1>() = declval<Up1>(), one()) 1049 test(int); 1050 1051 template<typename, typename> 1052 static two test(...); 1053 1054 public: 1055 static constexpr bool value = sizeof(test<Tp, Up>(0)) == 1; 1056 }; 

This class uses SFINAE to do dirty work. That is, the value of the value variable will depend on which function test() selected based on overload resolution. One overload takes an integer, and the other takes a variational argument C (given by an ellipsis). If a replacement failure occurs during the first overload, a second overload will be selected.

If the replacement fails, it will be obtained from the expression declval<Tp1>() = declval<Up1>() . declval<T>() is a function declaration that "returns" a value of type std::add_rvalue_reference<T>::type . This function is mainly used in unappraised contexts such as decltype() , sizeof() , noexcept() , etc., to get an instance of a type without calling the constructor explicitly (because this type may not have an accessible constructor). If you want to know why add_rvalue_reference is the selected return type, see this post .

Once you get an instance of a type, you can call member / non-member functions on those instances. The member function used is operator=() . If the class does not have an assignment operator (or has an unavailable one), the replacement will fail. Instead, the backup (variational) version of test() will be selected.

The reason for the difference in argument types ( int vs ... ) is that ... has the lowest conversion rank and acts as a "last resort" to resolve overloading. We cannot just leave the parameters empty, otherwise we will get a re-allocation error.

As for the return type, test - if a replacement failure does not occur (a value of type Up can be assigned to a value of type Tp ), then test() returns a type indicating success. If the replacement fails, a backup version is selected that returns a type indicating failure. These types are differentiated by checking their sizes. We test success by comparing with 1 .

+6
source

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


All Articles