Using Generic Lambdas for SFINAE

In C++ Templates, The Complete Guide (2e), the author gives the following example trait definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename F, typename... Args,
typename = decltype(std::declval<F>()(std::declval<Args&&>()...))>
std::true_type isValidImpl(void*);

// fallback if helper SFINAE’d out:
template<typename F, typename... Args>
std::false_type isValidImpl(...);

// define a lambda that takes a lambda f and returns whether calling f with args is valid
inline constexpr
auto isValid = [](auto f) {
    return [](auto&&... args) {
    return decltype(isValidImpl<decltype(f),
                                decltype(args)&&...
                                >(nullptr)){};
    };
};


My confusion is about the following line in the isValidImpl instantiation:

decltype(args)&&...

What I don't understand is what the purpose of && is here. Is it a forwarding reference? Or just a plain rvalue reference? Is it using reference collapsing rules? Could someone explain why this is here?
Last edited on
Some authors write && when it isn't strictly necessary. decltype(args)... is already a list of reference types, so decltype(args)&&... is exactly the same list of types.

The pack expansion std::declval<Args&&>()... on line 2 exhibits the same pattern. The && does nothing, AFAICT, except perhaps improve code clarity in the author's opinion.
Last edited on
Thanks. One more question. This is another example the author gives:
1
2
constexpr auto hasFirst = isValid([](auto x) -> decltype((void)valueT(x).first) {
});


My question is what's the purpose of casting to void in the decltype expression? Earlier the author said it was to avoid an overloaded operator, like a comma operator. But I don't think the member access operator can be overloaded, can it? So why cast to void here.

Also, what are the precedence rules going on here? Is it the result of valueT(x) that's being cast to void, and then the first member is accessed? Or is it that the first member is accessed on valueT(x) and then the entire expression is cast to void?
If I may jump in..

what are the precedence rules going on here?

Entry expression is cast to void, otherwise member access would be not valid.
Member access operators . and -> have higher precendence than C style cast

My question is what's the purpose of casting to void in the decltype expression?

This declytype expression in this SFINAE evaluation, means the lambda is added to overload resolution only if valueT(x) has member first

The expression is casted to void to ensure lambda return type is void, otherwise the return type would be type of first
Last edited on
Ahh that makes sense. Thank you.
Registered users can post here. Sign in or register to post.