Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does std::is_assignable work?

Tags:

c++

c++11

Here's the implementation of std::is_assignable, I have spent few hours trying to get how it can statically figure out the type of the templated object but couldn't.

In the standard is_assignable states that both sides of the assignment are converted to std::add_rvalue_reference<T>::type. I don't get that sentence nor see how std::add_rvalue_reference<T>::typecan be used to predict the type of the object.

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

like image 914
Kam Avatar asked Sep 29 '14 00:09

Kam


People also ask

Is trivially assignable?

Trivially-copy-assignable requires just operations involving copy-assignment to be trivial. You can have a trivially-copy-assignable class with, for example, a non-trivial destructor or move constructor. Such a class won't be trivially-copyable.


1 Answers

Here's the same code with some of the lexical obfuscation taken out:

 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 the dirty work. That is, the value of variable value will depend on which test() function is chosen based on overload resolution. One overload takes an integer and the other takes a C variadic argument (specified by the ellipsis). If there is a substitution failure that occurs on the first overload, the second overload will be chosen.

If a substitution failure does occur, it will have come from the expression declval<Tp1>() = declval<Up1>(). declval<T>() is a function declaration that "returns" a value of the type std::add_rvalue_reference<T>::type. This function is mainly used in unevaluated contexts like decltype(), sizeof(), noexcept(), etc, in order to get an instance of a type without explicitly calling a constructor (because that type may not have an accessible constructor). If you'd like to know why add_rvalue_reference is the chosen return type, see this post.

Once you get an instance of the type, you can call member/non-member functions on those instances. The member function used is operator=(). If a class doesn't have an assignment operator (or has an inaccessible one) there will be a substitution failure. The fallback (variadic) version of test() will instead be chosen.

The reason for the difference in argument types (int vs ...) is because ... has the lowest conversion rank and it acts as a "last resort" for overload resolution. We can't just leave the parameters empty or else we'll get a redeclaration error.

As for the return type of test - if a substitution 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 a substitution failure does happen, the the fallback version that returns a type indicating failure is chosen. These types are differentiated through a check for their sizes. We check for success by comparing with 1.

like image 85
David G Avatar answered Sep 22 '22 05:09

David G