Reputation: 333
I came across with this code in an std::optional
implementation :
template <class T, class U>
struct is_assignable
{
template <class X, class Y>
constexpr static bool has_assign(...) { return false; }
template <class X, class Y, size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true)) >
// the comma operator is necessary for the cases where operator= returns void
constexpr static bool has_assign(bool) { return true; }
constexpr static bool value = has_assign<T, U>(true);
};
The part that I cant understand how it works or how it is evaluated is size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true))
I know that if the assign operation fails it will fall back to the first definition of has_assign that returns false, but i don't know why it has the , true)
part.
I did some test with structs that returns void on the assign operator and removing the , true
part in sizeof
gives me the same results.
Upvotes: 7
Views: 301
Reputation: 303880
In order to apply sizeof()
, you need a complete type. But returning a complete type isn't a requirement of assignability, hence:
sizeof((std::declval<X>() = std::declval<Y>(), true))
~~~~~~~~~~~~~~~~~~ expr ~~~~~~~~~~~~~~~~~~~~~
if the assignment is valid for those two types, then we have sizeof(expr)
where the type of expr
is bool
(because true
). So if the assignment is valid, we get some real size
. Otherwise, substitution failure.
But this is an unnecessarily cryptic way of writing this code. Moreover, it's not even correct because I could write a type like:
struct Evil {
template <class T> Evil operator=(T&& ); // assignable from anything
void operator,(bool); // mwahahaha
};
and now your sizeof()
still doesn't work.
Instead, prefer simply:
class = decltype(std::declval<X>() = std::declval<Y>())
This accomplishes the same result - either substitution failure or not - without needed to care at all about what the type of the result is or to handle special cases.
Upvotes: 8
Reputation: 171177
In principle, the type of the expression std::declval<X>() = std::declval<Y>()
(that is, the return type of the operator =
involved) can be arbitrary, including an incomplete type or void
. In such case, SFINAE wouldn't kick in, since the expression is valid. However, you'd then get an error from applying sizeof
to an incomplete type. (Note that some compilers define sizeof(void) == 1
as an extension, but that cannot be universally relied upon).
Adding , true
after the SFINAE expression fixes this by discarding the type of the assignment (whatever it is), and applying sizeof
to true
instead (which is perfectly valid).
As indicated by Barry in the comments, a more direct approach would be to use the type of the assignment in decltype
instead of in sizeof
, like this:
template <class X, class Y, class S = decltype(std::declval<X>() = std::declval<Y>()) >
constexpr static bool has_assign(bool) { return true; }
Upvotes: 8