Question: Have difficulty understanding the syntax of generic lambdas for SFINAE-based traits

I am reading some examples of SFINAE-based traits, but unable to make sense out of the one related to generic lambdas in C++17 (isvalid.hpp).
I can understand that it roughly contains some major parts in order to implement a type trait such as isDefaultConstructible or hasFirst trait (isvalid1.cpp):
1. Helper functions using SFINAE technique:
#include <type_traits>

// helper: checking validity of f(args...) for F f and Args... args:
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(...);

2. Generic lambda to determine the validity:
// 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)){};
                        };
               };

3. Type helper template:
// helper template to represent a type as a value
template<typename T>
struct TypeT {
    using Type = T;
};

// helper to wrap a type as a value
template<typename T>
constexpr auto type = TypeT<T>{};

// helper to unwrap a wrapped type in unevaluated contexts
template<typename T>
T valueT(TypeT<T>);  // no definition needed

4. Finally, compose them into isDefaultConstructible trait to check whether a type is default constructible:
constexpr auto isDefaultConstructible
    = isValid([](auto x) -> decltype((void)decltype(valueT(x))()) {
        });

It is used like this (Live Demo):
struct S {
    S() = delete;
};

int main() {
    std::cout << std::boolalpha;
    std::cout << "int: " << isDefaultConstructible(type<int>) << std::endl;    // true
    std::cout << "int&: " << isDefaultConstructible(type<int&>) << std::endl;  // false
    std::cout << "S: " << isDefaultConstructible(type<S>) << std::endl;        // false

    return 0;
}

However, some of the syntax are so complicated and I cannot figure out.
My questions are:

With respect to 1, as for std::declval<F>()(std::declval<Args&&>()...), does it mean that it is an F type functor taking Args type constructor? And why it uses forwarding reference Args&& instead of simply Args?

With respect to 2, as for decltype(isValidImpl<decltype(f), decltype(args)&&...>(nullptr)){} , I also cannot understand why it passes forwarding reference decltype(args)&& instead of simply decltype(args)?

With respect to 4, as for decltype((void)decltype(valueT(x))()), what is the purpose of (void) casting here? ((void) casting can also be found in isvalid1.cpp for hasFirst trait) All I can find about void casting is Casting to void to avoid use of overloaded user-defined Comma operator, but it seems it is not the case here.


Thanks for any insights.

P.S. For one who wants more detail could check C++ Templates: The Complete Guide, 2nd - 19.4.3 Using Generic Lambdas for SFINAE. The author also mentioned that some of the techniques are used widely in Boost.Hana, so I also listen to Louis Dionne's talk about it. Yet, it only helps me a little to understand the code snippet above. (It is still a great talk about the evolution of C++ metaprogramming)


Answer:

F is a function object callable with Args...
For the sake of mental model, picture std::declval<F>() as a "fully constructed object of type F". std::declval is there just in case F is not default-constructible and still needs to be used in unevaluated contexts.
For a default-constructible type this would be equivalent:
F()(std::declval<Args&&>()...); In essence it's a call to F's constructor and then call to its operator() with forwarded Args. But imagine one type is constructible with int, another one is default-constructible, yet another one requires a string. Without some unevaluated constructor-like metafunction it would be impossible to cover all those cases.
You can read more on that in Alexandrescu's Modern C++ Design: Generic Programming and Design Patterns Applied.

Adding && to the argument type is effectively perfect-forwarding it. It may look obscure, but it's just a shorthand for decltype(std::forward<decltype(args)>(args)). See the implementation of std::forward and reference collapsing rules for more details. Keep in mind though, that this snippet adds rvalue-reference that collapses to the correct one when combined with the original type, not a forwarding one.

As it was stated in the comments: the type is not really needed, possibilty exists it cannot be returned, its presence there is just to check expression's correctness, afterwards it can be discarded.