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>::type
can 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?
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With