萃取的实现(三)

发布于:2025-02-19 ⋅ 阅读:(26) ⋅ 点赞:(0)

探测成员

        基于SFINAE,判断一个给定类型T,是否含有名为x的成员。

探测类型成员

        判断一个给定类型T,是否含有类型成员size_type,源码如下:

#include <type_traits>
#include <iostream>
#include <vector>

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

template <typename , typename = void_t<>>
struct has_sizetype_t : std::false_type {};

template <typename T>
struct has_sizetype_t<T, void_t<typename T::size_type>> : std::true_type {};

int main(int argc, char **argv) {
    std::cout << (has_sizetype_t<int>::value ? "true" : "false") << std::endl;
    std::cout << (has_sizetype_t<std::vector<int>>::value ? "true" : "false") << std::endl;
    return 0;
}

        注意:如果类型成员为private成员,萃取模板没有访问该类型的特殊权限,has_sizetype_t会返回false

        对于普通类型,上述模板能够准确做出判断,但如果模板参数为引用类型,如std::vector<int>&,上述模板处理结果确是false。为了能兼容这种情况,可以在偏特化使用std::removereference,如下:

template <typename T>
struct has_sizetype_t<T, void_t<typename std::remove_reference<T>::type::size_type>> : std::true_type {};

        对于注入类的名字,上述检测模板也会返回true,如下:

struct size_type {};
struct size_able : size_type {};

//...
std::cout << (has_sizetype_t<size_able>::value ? "true" : "false") << std::endl;

探测任意类型成员

         has_sizetype_t能够有效的判断一个类型T是否包含类型成员size_type,如果把size_type换成其他类型呢?最简单也是最暴力的做法,就是仿照has_sizetype_t再实现一个萃取模板。但为每个类型成员都实现一个萃取模板,不太合理,把类型成员参数化,才是最优的选择。然而不幸的是,目前还没有语言机制可以被用来描述“潜在” 的名字。但是可以通过宏来实现这一功能,源码如下:

#include <type_traits>
#include <iostream>
#include <string>
#include <vector>

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

#define define_has_member_type(member_type) \
    template <typename , typename = void_t<>> \
    struct has_##member_type##_t : std::false_type {}; \
                            \
    template <typename T> \
    struct has_##member_type##_t<T, void_t<typename std::remove_reference<T>::type::member_type>> : std::true_type {};
    //struct has_##member_type##_t<T, void_t<typename T::member_type>> : std::true_type {};

define_has_member_type(size_type)
define_has_member_type(str_type)

class mystring {
public:
    using str_type = std::string;
};

class myint {
public:
    using int_type = int;
};

int main(int argc, char **argv) {
    std::cout << (has_str_type_t<mystring&&>::value ? "true" : "false") << std::endl;
    std::cout << (has_str_type_t<myint>::value ? "true" : "false") << std::endl;
    
    std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl;
    std::cout << (has_size_type_t<std::vector<int>>::value ? "true" : "false") << std::endl;
    std::cout << (has_size_type_t<std::vector<int>&&>::value ? "true" : "false") << std::endl;
    return 0;
}

探测非类型成员

        对上述萃取模板做简单的修改,便可以得到探测非类型成员的萃取模板,如下:

#include <type_traits>
#include <iostream>

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

#define define_has_member(member) \
    template <typename, typename = void_t<>> \
    struct has_##member##_t : std::false_type {}; \
    \
    template <typename T> \
    struct has_##member##_t<T, void_t<decltype(&T::member)>> : std::true_type {};

enum myenum {
    failed = 0,
    succeeded = 1
};

class myclass {
public:
    int value;
    myenum status;
    static int instance_number;
};

class myclass2 {
public:
    using status = myenum;
};

define_has_member(value)
define_has_member(status)
define_has_member(instance_number)

int main(int argc, char **argv) {
    std::cout << (has_status_t<myclass2>::value ? "true" : "false") << std::endl;
    std::cout << (has_status_t<std::true_type>::value ? "true" : "false") << std::endl;
    
    std::cout << (has_value_t<myclass>::value ? "true" : "false") << std::endl;
    std::cout << (has_status_t<myclass>::value ? "true" : "false") << std::endl;
    std::cout << (has_instance_number_t<myclass>::value ? "true" : "false") << std::endl;
    
    return 0;
}

        偏特化模板参数中为什么使用void_t<decltype(&T::member)>,而非void_t<decltype(T::member)>?因为使用后者探测数据成员没有问题,但探测成员函数就无能为力了。使用该萃取模板,还需要注意以下几点:

  • 成员必须是非类型成员以及非枚举成员,否则&T::member失效,所以has_kind_type_t,has_ultrasonic_t均检测失败

  • 成员必须是public成员

  • member必须没有歧义,对于has_kind_t<bat>::value,has_name_t<bat>::value,has_move_t<bat>::value,编译其无法确认kind为mammals::kind,还是bird::kind,name为mammals::name,还是bird::name,move为mammals::move,还是bird::move,所以均检测失败

  • 如果存在重载函数,检测失败,如bat拥有两个search版本,所以has_search_t检测失败

        针对重载函数探测失效的问题,目前的方案只能针对不同的函数实现不同的的萃取模板。如下:

template <typename, typename = void_t<>>
struct has_search_void_t : std::false_type {};

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

template <typename, typename = void_t<>>
struct has_search_int_t : std::false_type {};

template <typename T>
struct has_search_int_t<T, void_t<decltype(std::declval<T>().search(0))>> : std::true_type {};

        下面是两种失败的尝试方案:

//方案1
//ERROR:
//: error: expected ')'
//    std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;
//to match this '('
//    std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;
template <typename T, typename ...Args, typename = void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>
constexpr std::true_type has_search_t(void *);

template <typename, typename ...Args>
constexpr std::false_type has_search_t(...);

//...
std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;

//方案2
//ERROR:
//template parameter pack must be the last template parameter
//    template <typename, typename ...Args, typename = void_t<>>
//error: expected expression
//    struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {};
//expected unqualified-id
//    struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {};
template <typename, typename = void_t<>>
struct has_search_void_t : std::false_type {};

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

         除了类的类型成员,变量成员,函数成员外,还可以用于探测表达式,甚至是多个表达式的组合,下面是两个例子:

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

//表达式
template <typename, typename, typename = void_t<>>
struct has_less_t : std::false_type {};

template <typename T1, typename T2>
struct has_less_t<T1, T2, void_t<decltype(std::declval<T1>() < std::declval<T2>())>> : std::true_type {};

//表达式组合
template <typename, typename = void_t<>>
struct has_calc_t : std::false_type {};

template <typename T>
struct has_calc_t<T, void_t<decltype(std::declval<T>() + std::declval<T>(), std::declval<T>() - std::declval<T>(), std::declval<T>() * std::declval<T>(), std::declval<T>() / std::declval<T>())>> : std::true_type {};

用泛型 Lambda 探测成员

        使用萃取的实现(二)【将泛型 Lambdas 用于 SFINAE】中的lambda表达式,也可以实现成员探测,源码如下:

constexpr auto has_size_type = is_valid([](auto &&x)->typename std::decay_t<decltype(x)>::size_type {});
template <typename T>
using has_size_type_t = decltype(has_size_type(std::declval<T>()));

constexpr auto has_begin = is_valid([](auto &&x)->decltype(x.begin()){});
//constexpr auto has_begin = is_valid([](auto &&x)->decltype(std::declval<decltype(x)>().begin()){});
template <typename T>
using has_begin_t = decltype(has_begin(std::declval<T>()));

int main(int argc, char **argv) {
    std::cout << (has_size_type_t<std::string>::value ? "true" : "false") << std::endl;
    std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl;
    std::cout << (has_begin_t<std::string>::value ? "true" : "false") << std::endl;
    std::cout << (has_begin_t<int>::value ? "true" : "false") << std::endl;
    
    return 0;
}

其它的萃取技术

If-Then-Else

        如何根据数值的大小,自动选择合适的整型类型?首先定义一个模板if_then_else模拟选择行为,该模板接收三个模板参数,并且第一个参数为bool常量表达式,剩余的两个参数为类型参数,由第一个参数的值决定哪个类型参数生效;然后,定义一个参数为整型数值的模板,调用if_then_else,推断出合适的类型(由于实现不知道数值的合适类型,因此模板参数的类型使用了auto,需要c++17才能支持)。代码如下:

template <bool COND, typename TrueType, typename FalseType>
struct if_then_else { using type = TrueType; };

template <typename TrueType, typename FalseType>
struct if_then_else<false, TrueType, FalseType> { using type = FalseType; };

template <auto N>
struct small_int {
    using type = typename if_then_else<N <= std::numeric_limits<char> ::max(), char,
                    typename if_then_else<N <= std::numeric_limits<short> ::max(), short,
                        typename if_then_else<N <= std::numeric_limits<int> ::max(), int,
                            typename if_then_else<N <= std::numeric_limits<long>::max(), long,
                                typename if_then_else<N <= std::numeric_limits<long long>::max(), long long,
                                    void>::type>::type>::type>::type>::type;
};

        需要注意的是,和常规的 C++ if-then-else 语句不同,在最终做选择之前,then 和 else 分支 中的模板参数都会被计算,因此两个分支中的代码都不能有问题,否则整个程序就会有问题。 

探测不抛出异常的操作

        有时候我们需要判断一些操作是否会抛出异常,这时我们需要用到noexcept操作符,该操作符对表达式进行编译时检查,如果表达式不抛出任何异常返回true,否则返回false。主要应用场景如下:

  • 显示指明函数是否会抛出异常:noexcept和noexcept(true)等效,表示该函数不抛出任何异常;noexcept(false)表示有可能抛出异常
  • 阻止异常传播和扩散,出现异常直接调用std::terminate终止进程
#include <iostream>

void func1() {
    throw 1;
}

void func2() {
    func1();
}

void func3() noexcept {
    func1();
}

int main(int argc, char **argv) {
    try {
        func1();
    } catch(...) {
        std::cout << "func1" << std::endl;
    }
    
    try {
        func2();
    } catch(...) {
        std::cout << "func2" << std::endl;
    }
    
    try {
        func3(); //阻止异常传播,程序crash
    } catch(...) {
        std::cout << "func3" << std::endl;
    }
    
    return 0;
}
  • 用于模板,增强c++泛型能力
#include <type_traits>
#include <iostream>

template <typename T, typename = std::void_t<>>
struct has_noexcept_move_constructor : std::false_type {};

template <typename T>
struct has_noexcept_move_constructor<T, std::void_t<decltype(T(std::declval<T>()))>> : std::bool_constant<noexcept(T(std::declval<T>()))> {};

class A {
public:
    A(A &&) {
        throw 1;
    }
};

class B {
public:
    B(B &&) = default;
};

int main(int argc, char **argv) {
    std::cout << (has_noexcept_move_constructor<A>::value ? "true" : "false") << std::endl;
    std::cout << (has_noexcept_move_constructor<B>::value ? "true" : "false") << std::endl;
    return 0;
}

萃取的便捷性

        关于萃取最大的问题便是繁琐,对于萃取类型的使用需要添加::type后缀,而且在依赖上下文中,还需要一个typename前缀,例如:

template <typename T>
void print_type_name() {
    std::cout << typeid(typename std::remove_reference<T>::type).name() << std::endl;
}

别名模板和萃取

        使用using创建别名,可以简化类型名称,如下:

template <typename T>
using rmref = typename std::remove_reference<T>::type;

template <typename T>
void print_type_name() {
    std::cout << typeid(rmref<T>).name() << std::endl;
}

        从c++14开始,直接为之引入了相应的别名模板,以_t结尾,对于“typename std::remove_reference<T>::type”,其别名为“std::remove_reference_t<T>”。

变量模板和萃取

        对于返回数值的萃取需要使用一个::value来生成萃取的结果,如下:

template <typename T1, typename T2>
void print_diff() {
    std::cout << (std::is_same<T1, T2>::value ? "true" : "false") << std::endl;
}

        针对上面这种情况,可以使用constexpr修饰的变量模板进行简化(需要在C++14之后),如下:

template <typename T1, typename T2>
constexpr bool issame = std::is_same<T1, T2>::value;

template <typename T1, typename T2>
void print_diff() {
    std::cout << (issame<T1, T2> ? "true" : "false") << std::endl;
}

        从c++17开始,直接引入了与之对应的变量模板,以_v结尾,对于“std::is_same<T1, T2>::value”,对应的变量模板为“std::is_same_v<T1, T2>”。

类型分析

判断基础类型

        判断一个类型是否是基础类型,如int是否是基础类型。代码如下:

template <typename T>
struct is_fundamental : std::false_type {};

template <>
struct is_fundamental<int> : std::true_type {};

template <>
struct is_fundamental<double> : std::true_type {};

int main(int argc, char **argv) {
    
    std::cout << (is_fundamental<double>::value ? "true" : "false") << std::endl;
    std::cout << (is_fundamental<int>::value ? "true" : "false") << std::endl;
    return 0;
}

         为了能够正确的识别出每一个基础类型,只能针对每个基础类型实现一遍is_fundamental。通过宏尽管能少些一些代码,但本质上依旧是对基础类型的穷举。

#define IS_FUNDAMENTAL(T) template <> struct is_fundamental<T> : std::true_type {};

IS_FUNDAMENTAL(float)
IS_FUNDAMENTAL(unsigned long)

判断复合类型

        复合类型是由其它类型构建出来的类型。简单的复合类型包含指针类型,左值以及右值引用 类型,指向成员的指针类型(pointer-to-member types),和数组类型。

//指针
template <typename T>
struct is_pointer : std::false_type {};
template <typename T>
struct is_pointer<T *> : std::true_type {};

//左值引用
template <typename T>
struct is_lvalue_reference : std::false_type {};
template <typename T>
struct is_lvalue_reference<T &> : std::true_type {};

//右值引用
template <typename T>
struct is_rvalue_reference : std::false_type {};
template <typename T>
struct is_rvalue_reference<T &&> : std::true_type {};

//数组
template <typename T>
struct is_array : std::false_type {};
template <typename T>
struct is_array<T[]> : std::true_type {};
template <typename T, std::size_t N>
struct is_array<T[N]> : std::true_type {};

//指向成员的指针
template <typename T>
struct is_member_pointer : std::false_type {};
template <typename T, typename C>
struct is_member_pointer<T C::*> : std::true_type {};

struct S {
    int a = 10;
    int b = 20;
    
    float func1(void) { return 0; }
    float func2(void) { return 1; }
};

using mem_data_ptr_t = int S::*;
using mem_func_ptr_t = float S::*;

int main(int argc, char **argv) {
    std::cout << (is_pointer<int>::value ? "true" : "false") << std::endl;
    std::cout << (is_pointer<int *>::value ? "true" : "false") << std::endl;
    int *pi = &argc;
    std::cout << (is_pointer<decltype(pi)>::value ? "true" : "false") << std::endl;
    std::cout << std::endl;
    
    std::cout << (is_lvalue_reference<int>::value ? "true" : "false") << std::endl;
    std::cout << (is_lvalue_reference<int &>::value ? "true" : "false") << std::endl;
    int &lri = argc;
    std::cout << (is_lvalue_reference<decltype(lri)>::value ? "true" : "false") << std::endl;
    std::cout << std::endl;
    
    std::cout << (is_rvalue_reference<int>::value ? "true" : "false") << std::endl;
    std::cout << (is_rvalue_reference<int &&>::value ? "true" : "false") << std::endl;
    std::cout << (is_rvalue_reference<decltype(std::list<int>())>::value ? "true" : "false") << std::endl;
    //不太确定为什么decltype(std::list<int>())得到的就不是右值引用类型
    std::cout << typeid(decltype(std::list<int>())).name() << std::endl;
    std::list<int> li;
    std::cout << (is_rvalue_reference<decltype(std::move(li))>::value ? "true" : "false") << std::endl;
    std::cout << typeid(decltype(std::move(li))).name() << std::endl;
    std::cout << std::endl;
    
    std::cout << (is_array<int>::value ? "true" : "false") << std::endl;
    std::cout << (is_array<int[]>::value ? "true" : "false") << std::endl;
    std::cout << (is_array<int [3]>::value ? "true" : "false") << std::endl;
    int arr[10]; //int arr[0]; decltype(arr)得到的类型时A0_i,不是数组
    std::cout << (is_array<decltype(arr)>::value ? "true" : "false") << std::endl;
    std::cout << typeid(int[3]).name() << std::endl;
    std::cout << typeid(decltype(arr)).name() << std::endl;
    std::cout << std::endl;
    
    using mem_string_ptr_t = std::string S::*;
    //虽然这个类型不存在,但结果依然返回了true,标注库意识如此
    std::cout << (is_member_pointer<mem_string_ptr_t>::value ? "true" : "false") << std::endl;
    std::cout << (is_member_pointer<mem_data_ptr_t>::value ? "true" : "false") << std::endl;
    std::cout << (is_member_pointer<mem_func_ptr_t>::value ? "true" : "false") << std::endl;

}

识别函数类型

        在讨论如何识别函数类型时,先考虑如何定义一个函数类型。下面是函数类型定义的相关代码:

#include <iostream>

int func_instance(int arg) {
    std::cout << arg << std::endl;
    return 0;
}

int main(int argc, char **argv) {
    typedef int (func_t)(int);
    func_t *func1 = func_instance; ✅
    func1(12);
    
    typedef int (*func_pt)(int);
    func_pt func2 = func_instance; ✅
    func2(14);
    
    return 0;
}

         通过上面的例子,可以看出:只要不同函数类型的返回值和入参一致,既可以表示同一个类型,名称不是特别重要。因此,识别函数类型时,只用到了返回值和入参,只要类型符合Ret(Args...)格式,该类型就是函数类型。这个格式很像去掉了名称的函数类型定义。下面是函数类型识别源码:

#include <type_traits>
#include <iostream>
#include <cstdio>

template <typename T>
struct is_function : std::false_type { constexpr static int flag = 0; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...)> : std::true_type { constexpr static int flag = 1; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...)> : std::true_type { constexpr static int flag = 2; };

class A {
public:
    A(){}
    static A &GetInstance() { static A _inst; return _inst; }
    void Set(int) {}
    int Get(void) const { return 0; }
    int Func(int a) const & { return a; }
};

struct B
{
    static int foo();
    int fun() const&;
};


int main(int argc, char **arg) {
    std::cout << (is_function<int(int)>::value ? "true" : "false") << " " << is_function<int(int)>::flag << std::endl;
    std::cout << (is_function<decltype(printf)>::value ? "true" : "false") << " " << is_function<decltype(printf)>::flag << std::endl;
    std::cout << (is_function<decltype(&A::Set)>::value ? "true" : "false") << " " << is_function<decltype(&A::Set)>::flag << std::endl;
    std::cout << (is_function<const int(void)>::value ? "true" : "false") << " " << is_function<const int(void)>::flag << std::endl;
    std::cout << (is_function<decltype(&A::Get)>::value ? "true" : "false") << " " << is_function<decltype(&A::Get)>::flag << std::endl;
    std::cout << (is_function<decltype(A::GetInstance)>::value ? "true" : "false") << " " << is_function<decltype(A::GetInstance)>::flag << std::endl;
    std::cout << (is_function<decltype(&B::fun)>::value ? "true" : "false") << " " << is_function<decltype(&B::fun)>::flag << std::endl;
    
    std::cout << (is_function<int(int)const>::value ? "true" : "false") << " " << is_function<int(int)const>::flag << std::endl;

 std::cout << (is_function<int(int) &>::value ? "true" : "false") << " " << is_function<int(int) &>::flag << std::endl;
    
    
    std::cout << (std::is_function<decltype(&A::Get)>::value ? "true" : "false") << std::endl;

    return 0;
}

        需要注意几个问题:

  • is_function识别的是函数类型,对于函数需要通过decltype推断出其函数类型,如decltype(printf)
  • is_function可以识别类的静态成员函数,无法识别非静态成员函数
  • is_function不能处理所有函数类型,因为有些函数还包含const和volatile,到c++17之后,还包含noexcept操作符,如int(int) const
  • 此外还应该处理函数的引用情况,关于函数的引用,解释如下(摘自c 成员函数声明()后加&或&&表示什么):

C++中,成员函数声明后添加 & 或 && 表明这个成员函数是针对左值对象还是右值对象进行操作的。具体来说,在成员函数声明后加上 & 表示该成员函数只能被左值对象调用、而加上 && 表示该成员函数只能被右值对象调用。这种技术是C++11引入的,用于支持移动语义和更精细地控制对象的行为。

        更多is_function的重载,如下(非全部):

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const> : std::true_type { constexpr static int flag = 3; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const> : std::true_type { constexpr static int flag = 4; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) volatile> : std::true_type { constexpr static int flag = 5; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) volatile> : std::true_type { constexpr static int flag = 6; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const volatile> : std::true_type { constexpr static int flag = 7; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const volatile> : std::true_type { constexpr static int flag = 8; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) &> : std::true_type { constexpr static int flag = 9; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) &> : std::true_type { constexpr static int flag = 10; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const &> : std::true_type { constexpr static int flag = 11; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const &> : std::true_type { constexpr static int flag = 12; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) volatile &> : std::true_type { constexpr static int flag = 13; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) volatile &> : std::true_type { constexpr static int flag = 14; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const volatile &> : std::true_type { constexpr static int flag = 15; };

template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const volatile &> : std::true_type { constexpr static int flag = 16; };

        以上仅是基础实现,libc++,libstdc++,MS stl还有更为简单的实现,如下:

template<class T>
struct is_function : std::integral_constant<
    bool,
    !std::is_const<const T>::value && !std::is_reference<T>::value
> {};

判断class类型

        原理:只有class类型才可以被用于指向成员的指针类型,即对于T Y::*一类的类型结构,Y只能是class类型,T可以选择任何类型。

#include <type_traits>
#include <iostream>

template <typename T, typename = std::void_t<>>
struct is_class : std::false_type {};

template <typename T>
struct is_class<T, std::void_t<int T::*>> : std::true_type {};

struct s {};
class c {};
union u {};
enum e {};

int main(int argc, char **argv) {
    std::cout << (is_class<s>::value ? "true" : "false") << std::endl;
    std::cout << (is_class<c>::value ? "true" : "false") << std::endl;
    std::cout << (is_class<u>::value ? "true" : "false") << std::endl;
    std::cout << (is_class<e>::value ? "true" : "false") << std::endl;
    auto f = []{};
    std::cout << (is_class<decltype(f)>::value ? "true" : "false") << std::endl;
    std::cout << (is_class<uint64_t>::value ? "true" : "false") << std::endl;
    std::cout << (is_class<int>::value ? "true" : "false") << std::endl;
    return 0;
}

        需要注意两点以下两点:

  • c++语言指出,lambda 表达式的类型是“唯一的,未命名的,非枚举 class 类型”,使用is_class萃取lambda表达式,得到的结果也是true
  • int T::*表达式同样适用于 union类型 

网站公告

今日签到

点亮在社区的每一天
去签到