We therefore only need a declaration, not the definition, to use a function or object's name in these contexts
std::declval<T>() returns T&&
std::declval<T&>() returns T&
decltype( foo(std::declval<T>()) ) returns foo's return type when foo is called with T&&
declval allows us to provide a declaration without having to evaluate the expression (ie: in an unevaluated context) - useful for SFINAE etc
Example: testing for copy-assignability
template<class T>
class is_copy_assignable
{
template<class U, class=decltype(declval<U&>()=declval<const U&>())>
static true_type try_assignment(U&&);
template<class U>
static false_type try_assignment(...); // catch-all fallback
public:
using type = decltype(try_assignment(declval<T>()));
};
How this works:
try_assignment(...) will match anything, but is also always the worst match, so if the other try_assignment can match, it will.
type will be the return type of try_assignment, which will either be true_type or false_type
the true_type overload will only work if the expression U& = const U& is valid - ie: if it is copy assignable
We use a second template parameter to allow SFINAE to kick in. It is unnamed because we only use it for SFINAE.
Example: testing for copy-assignability, and requiring an lvalue reference return type
The above example doesn't force a requirement on the copy assignment returning an lvalue reference.
If we assign an alias template to the returned type:
template<class T>
using copy_assignment_t = decltype(declval<T&>() = declval<const T&>());
We can then check whether that is a T& in a SFINAE specialisation
struct is_copy_assignable
: std::false_type {};
template<class T>
struct is_copy_assignable<T, void_t<copy_assignment_t<T>>>
: std::is_same<copy_assignment_t<T>,T&> {};
No comments:
Post a Comment