难以理解基于 SFINAE 的特征的通用 lambda 语法

Luc*_*ien 5 c++ sfinae type-traits c++17

我正在阅读一些基于 SFINAE 的特征的示例,但无法理解与 C++17 中的通用 lambda 相关的示例(isvalid.hpp)。

我可以理解它大致包含一些主要部分,以便实现类型特征,例如isDefaultConstructibleorhasFirst特征(isvalid1.cpp):

1. 使用 SFINAE 技术的辅助函数

#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(...);
Run Code Online (Sandbox Code Playgroud)

2. 确定有效性的通用 lambda

// 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)){};
                        };
               };
Run Code Online (Sandbox Code Playgroud)

3. 输入助手模板

// 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
Run Code Online (Sandbox Code Playgroud)

4.最后,将它们组合成isDefaultConstructibletrait来检查类型是否是默认可构造的

constexpr auto isDefaultConstructible
    = isValid([](auto x) -> decltype((void)decltype(valueT(x))()) {
        });
Run Code Online (Sandbox Code Playgroud)

它的使用方式如下(现场演示):

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;
}
Run Code Online (Sandbox Code Playgroud)

然而,有些语法太复杂,我无法弄清楚。

我的问题是:

  • 相对于 1,至于std::declval<F>()(std::declval<Args&&>()...),是否意味着它是一个F采用Args类型构造函数的类型仿函数?为什么它使用转发引用Args&&而不是简单地使用转发引用Args

  • 对于2,至于decltype(isValidImpl<decltype(f), decltype(args)&&...>(nullptr)){},我也无法理解为什么它传递转发引用decltype(args)&&而不是简单地传递decltype(args)

  • 对于4,至于decltype((void)decltype(valueT(x))()),这里铸造的目的是什么(void)?(也可以在isvalid1.cpp(void)中找到特征的强制转换)我能找到的关于强制转换的所有信息是强制转换为 void 以避免使用重载的用户定义的逗号运算符,但似乎这里的情况并非如此。hasFirstvoid

感谢您的任何见解。


PS 对于想要更多详细信息的人,可以查看C++ 模板:完整指南,第 2 - 19.4.3 使用通用 Lambdas for SFINAE。作者还提到了一些技术在Boost.Hana中被广泛使用,所以我也听了Louis Dionne的谈论。然而,它只能帮助我理解上面的代码片段。(关于C++元编程的演变仍然是一个很好的讨论)

ala*_*ner 1

  1. F 是一个可以用 Args 调用的函数对象...为了思维模型的缘故,将其描绘std::declval<F>()为“F 类型的完全构造的对象”。std::declval是为了防止 F 不可默认构造并且仍然需要在未评估的上下文中使用。对于默认可构造类型,这将是等效的: F()(std::declval<Args&&>()...);本质上,它是对 F 的构造函数的调用,然后使用operator()转发对其进行调用Args。但是想象一下,一种类型可以使用 int 构造,另一种类型可以默认构造,而另一种类型则需要字符串。如果没有一些未评估的类似构造函数的元函数,就不可能涵盖所有这些情况。您可以在 Alexandrescu 的《现代 C++ 设计:通用编程和设计模式的应用》中阅读更多相关内容。

  2. 添加&&到参数类型实际上是完美转发它。它可能看起来晦涩难懂,但它只是 的简写decltype(std::forward<decltype(args)>(args))。有关更多详细信息,请参阅折叠规则的实现std::forward和参考。但请记住,此代码片段添加了右值引用,当与原始类型(而不是转发类型)组合时,该右值引用会折叠为正确的引用。

  3. 正如评论中所述:该类型并不是真正需要的,存在无法返回的可能性,它的存在只是为了检查表达式的正确性,之后可以将其丢弃。