Member Detector惯用法

enter image description here

With enough practice, any interface is intuitive. Anonymous

//C++ 中的 "member detector"(成员检测器)是一种惯用法, //用于在编译时检测类是否具有特定的成员函数或成员变量。 //在早期的 C++ 版本中,我们无法直接检测类是否拥有某个成员, //通常需要通过模板特化或使用 SFINAE(Substitution Failure Is Not An Error)技术来实现。 //但是,这些方法通常比较复杂和繁琐。 //C++17 引入了 "member detector" 惯用法, //它使用了 void_t、decltype 和 SFINAE 技术,使得检测类成员变得更加简单和直观。

例子1

#include <iostream>
#include <type_traits>

// 定义void_t别名模板
template<typename...>
using void_t = void;

// 检查类型T是否有成员函数foo
template<typename T, typename = void>
struct has_foo : std::false_type {};

template<typename T>
struct has_foo<T, void_t<decltype(std::declval<T>().foo())>> : std::true_type {};

// 测试类
struct A {
    void foo() {}
};

struct B {};

int main() {
    std::cout << std::boolalpha;
    std::cout << has_foo<A>::value << std::endl; // 输出 true,因为A有foo成员函数
    std::cout << has_foo<B>::value << std::endl; // 输出 false,因为B没有foo成员函数
    return 0;
}

例子2

#include <iostream>
#include <type_traits>

template <typename...>
struct void_t {
    using type = void;
};

template<typename T, typename = void>
struct has_foo : std::false_type {};

template<typename T>
struct has_foo<T, typename void_t<decltype(std::declval<T>().foo())>::type> : std::true_type {};

struct A {
    void foo() {}
};

struct B {};

int main() {
    std::cout << std::boolalpha;
    std::cout << has_foo<A>::value << std::endl; // 输出 true,因为A有foo成员函数
    std::cout << has_foo<B>::value << std::endl; // 输出 false,因为B没有foo成员函数
    return 0;
}

代码解释

template <typename...>
struct void_t {
    using type = void;
};  

template<typename...>
using void_t = void; 

比较有什么不一样?

这两个代码片段实现的功能是相同的,它们都定义了一个模板 void_t,用于在模板元编程中进行类型萃取,使得无论传入多少模板参数,都将它们忽略,并将 void 作为结果类型。

主要的不同点在于它们的定义方式和使用语法:

第一个代码片段使用的是模板结构体的定义方式,即通过 struct 关键字来定义模板 void_t,然后在模板结构体内部定义了一个 type 成员,该成员类型为 void。

第二个代码片段使用的是模板别名的定义方式,即通过 using 关键字来定义模板别名 void_t,并将其直接赋值为 void。

虽然定义方式不同,但它们的功能是一样的。通常情况下,第二种方式更加简洁,并且更易读,特别是在C++11及之后的版本中,使用模板别名可以使代码更加清晰和简洁。

两种void_t定义使用方式也有差异

两种定义方式会影响到使用 void_t模板时的语法。让我们来看看它们之间的使用差异:

使用模板结构体方式定义的 void_t

template <typename...>
struct void_t {
    using type = void;
};

在使用时,需要通过 typename void_t<T>::type 来访问 void_t 结构体内的 type 成员。例如:

typename void_t<T>::type

使用模板别名方式定义的 void_t

template<typename...>
using void_t = void;

在使用时,直接使用 void_t 即可,无需再访问额外的成员。例如:

void_t<T>

因此,使用模板别名方式定义的 void_t更为简洁,使得代码更加清晰易读。这也是为什么在实际应用中,通常会优先选择使用模板别名的方式来定义 void_t

模板类特化

解释一下这两个C++模板类定义:

template<typename T, typename = void>
struct has_foo : std::false_type {};

template<typename T>
struct has_foo<T, void_t<decltype(std::declval<T>().foo())>> : std::true_type {};

这两个模板类定义实现了一个名为 has_foo 的类型特化检查,用于检查一个类型是否具有名为 foo 的成员函数。让我们逐个解释:

  • 第一个模板类定义:
template<typename T, typename = void>
struct has_foo : std::false_type {};

这个模板类是一个通用的模板类,有两个模板参数,其中第一个参数是待检查类型 T,而第二个参数使用了默认模板参数 void。如果一个类型没有特定的成员函数 foo,则这个模板类会被匹配,并且 value 成员将被设置为 false,表示该类型不具有 foo 成员函数。

  • 第二个模板类定义:
template<typename T>
struct has_foo<T, void_t<decltype(std::declval<T>().foo())>> : std::true_type {};

这个模板类是对 has_foo 的特化。它与通用模板类相比,有一个额外的模板参数,该参数是 void_t<decltype(std::declval<T>().foo())>这个额外的参数利用了模板元编程技巧,用于在编译时检查类型 T 是否具有名为foo 的成员函数。如果 T 类型具有 foo 成员函数,则这个特化模板类会被匹配,并且 value 成员将被设置为true,表示该类型具有foo` 成员函数。

这两个模板类的组合使用允许在编译时确定一个类型是否具有特定的成员函数,这是一种常见的模板元编程技术,通常用于实现SFINAE(Substitution Failure Is Not An Error)机制。

decltype用法

void_t<decltype(std::declval<T>().foo())>

  1. std::declval<T>()std::declval 是一个模板函数,它返回一个类型为T的右值引用。它通常用于在模板上下文中创建一个临时值,而无需实际地构造对象。
    1. std::declval<T>().foo():在这里,我们假设 T 是一个类类型,并尝试调用它的成员函数 foo()。这并不会实际调用 foo() 函数,因为 std::declval<T>() 只是创建了一个临时值,而不是真正的对象。这个步骤是为了获取 foo() 成员函数的返回类型。
    2. decltype(std::declval<T>().foo())decltype 是一个关键字,用于获取表达式的类型。这里,decltype 用于获取调用 std::declval<T>().foo() 的类型,即 foo() 成员函数的返回类型。
    3. void_t<decltype(std::declval<T>().foo())>:这个表达式利用了之前定义的 void_t 模板。它将decltype(std::declval<T>().foo()) 作为模板参数,产生了一个类型。由于 void_t 的定义中,无论传入什么模板参数,都将返回 void 类型,因此这个表达式最终得到的类型就是 void。

总结起来,void_t<decltype(std::declval<T>().foo())> 的作用是获取 T 类型的成员函数 foo() 的返回类型,并将其转换为 void 类型。这在模板元编程中常用于类型萃取SFINAE技术中。

如法炮制可以实现检测成员变量的例子

#include <iostream>
#include <type_traits>

// 定义void_t别名模板
template<typename...>
using void_t = void;

// 检查类型T是否有成员变量value
template<typename T, typename = void>
struct has_value : std::false_type {};

template<typename T>
struct has_value<T, void_t<decltype(T::value)>> : std::true_type {};

// 测试类
struct A {
    static constexpr bool value = true;
};

struct B {};

struct C {
    int value;
};

int main() {
    std::cout << std::boolalpha;
    std::cout << has_value<A>::value << std::endl; // 输出 true,因为A有value成员变量
    std::cout << has_value<B>::value << std::endl; // 输出 false,因为B没有value成员变量
    std::cout << has_value<C>::value << std::endl; // 输出 true, 因为C有value成员变量
    return 0;
}

评论