美文网首页
C++模板SFINAE特性与反射机制

C++模板SFINAE特性与反射机制

作者: DayDayUpppppp | 来源:发表于2021-04-17 20:19 被阅读0次

C++模板提供了一个SFINAE(subsitate failure is not an error)的机制(模板匹配失败不是错误),这是模板里面一个非常有意思的特性,利用这个机制可以检查一个结构体是否包含某个成员等操作。c++语言本身没有提供反射机制(也有利用pb实现反射),利用SFINAE机制,可以实现类似于反射的功能。


在介绍SFINAE之前,整理一下模板的基本语法。

  • 基本语法
    C++模板基本的使用方法,使用关键字template, typename,可以实现基本泛型函数或者类。这里就不在赘述了。
// 一个简单的函数模板例子
template<typename T>
T get_max(T a, T b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << a << " " << b << endl;
    auto result = a > b ? a: b;
    return result;
}

// 提供特化版本,处理某些特定类型
template<>
float get_max(float a, float b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << "float : " << a << " " << b << endl;
    auto result = a > b ? a: b;
    return result;
}

// 提供偏特化版本,解决传递指针的问题
template<typename T>
T get_max(T* a, T* b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << *a << " " << *b << endl;
    auto result = *a > *b ? *a: *b;
    return result;
}

// 默认值模板
template<typename T = std::string>
T get_max(string a, string b)  // 默认值这里的参数类型需要直接指定
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << "STRING : " <<a << " " << b << endl;
    return a;
}
  • 关键字decltype的用法
    1)尾置返回类型
    通过decltype指定模板函数的返回类型
// 尾置返回类型
template<typename T1, typename T2>
auto get_max(T1 a, T2 b) -> decltype(b < a ? a : b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << a << " " << b << endl;
    return a > b ? a: b;
}

2)获得变量的类型

int a=8, b=3;
auto c = a + b;  //运行时需要实际执行a+b,哪怕编译时就能推导出类型
decltype(a+b) d; //编译期类型推导
// auto c;  // error // 不可以用auto c; 直接声明变量,必须同时初始化。
  • 模板匹配decay机制
    模板在进行匹配的时候,会进行一个退化,比如const int 如果找不到对应的类型,会退化为int。其实就是把各种引用啊什么的修饰去掉,把cosnt int&退化为int。
// 模板类型退化 decay 
template<typename T>
T get_max_decay(T a, T b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    return a > b ? a: b;
}

int main()
{
    // 模板类型退化 decay 
    // 看着比较抽象,其实就是把各种引用啊什么的修饰去掉,把cosnt int&退化为int
    int const c = 42;
    int i = 1;
    get_max_decay(i, c); // OK: T 被推断为 int,c 中的 const 被 decay 掉
    get_max_decay(c, c); // OK: T 被推断为 int

    int& ir = i;
    get_max_decay(i, ir); // OK: T 被推断为 int, ir 中的引用被 decay 掉

    // ...
}
  • SFINAE 机制
    SFINAE(Substitution Failure Is Not An Error,替换失败并非错误)这个看着很抽象,实际非常简单,是一种常见的模板技巧。比如,利用模板的SFINAE判断一个结构体中是否包含某个成员。
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T>
struct check_has_member_id
{
    // 仅当T是一个类类型时,“U::*”才是存在的,从而这个泛型函数的实例化才是可行的
    // 否则,就将触发SFINAE
    template<typename U>
    static void check(decltype(&U::id)){}

    // // 仅当触发SFINAE时,编译器才会“被迫”选择这个版本
    template<typename U>
    static int check(...){}

    enum {value = std::is_void<decltype(check<T>(NULL))>::value};
};

struct TEST_STRUCT
{
    int rid;
};

struct TEST_STRUCT2
{
    int id;
};

int main()
{
    check_has_member_id<TEST_STRUCT> t1;
    cout << t1.value << endl;

    check_has_member_id<TEST_STRUCT2> t2;
    cout << t2.value << endl;

    check_has_member_id<int> t3;
    cout << t3.value << endl;
    return 0;
}
// g++ --std=c++11  xxx.c

核心的代码是在实例化check_has_member_id对象的时候,通过模板参数T的类型,决定了结构体中对象value的值。而value的值是通过check<T>函数的返回值是否是void决定的。如果T中含有id成员的话,那么就会匹配第一个实例,返回void;如果不包含id的话,会匹配默认的实例,返回int。

利用这个机制还可以做很多类似的判断,比如判断一个类是否是结构体。

#include <iostream>
#include <type_traits>

// 2. 判断变量是否是一个struct 或者 类
// https://www.jianshu.com/p/d09373b83f86
template <typename T>
struct check
{
    template <typename U>
    static void check_class(int U::*) {}

    template <typename U>
    static int check_class(...) {}

    enum { value = std::is_void<decltype(check_class<T>(0))>::value };
};

class myclass {};

int main()
{
    check<myclass> t;
    std::cout << t.value << std::endl;

    check<int> t2;
    std::cout << t2.value << std::endl;
    return 0;
}
  • enable_if 语法

如果T是一个int类型,那么返回值是bool类型。如果不是int的话,就匹配不到找个实例。使用enable_if的好处是控制函数只接受某些类型的(value==true)的参数,否则编译报错。

// 判断class T 是否有某个成员函数
// enable if 
// enable_if example: two ways of using enable_if
#include <iostream>
#include <type_traits>

// 1. the return type (bool) is only valid if T is an integral type:
template <class T>
typename std::enable_if<std::is_integral<T>::value,bool>::type
  is_odd (T i) {return bool(i%2);}

int main() 
{
    short int i = 1;    // code does not compile if type of i is not integral
    std::cout << "i is odd: " << is_odd(i) << std::endl;

    // 编译错误
    // double j = 10.0;
    // std::cout << "i is odd: " << is_odd(j) << std::endl;

    return 0;
}

利用模板的这种机制,可以设计“通用”函数接口。比如,如果work_func<T>(T data) 函数传入的类型T中包含了T::is_print成员就打印“xxxx”,如果不包含就打印“yyy”。


这个事情除了可以用模板来做,利用反射也可以实现。c++本身没有提供反射,利用pb,也可以实现。下面介绍一下基于pb实现的c++反射的机制。

int get_feature(const HeartBeatMessage& hb_msg, const std::string& name) 
{
    const google::protobuf::Descriptor* des = hb_msg.GetDescriptor();
    const google::protobuf::FieldDescriptor* fdes = des->FindFieldByName(name);
    assert(fdes != nullptr);
    const google::protobuf::Reflection* ref = hb_msg.GetReflection();
    cout << ref->GetString(hb_msg, fdes) << endl;
    return 0;
}

int main()
{
    HeartBeatMessage msg;
    fstream input("./heartbeat.db",ios::in|ios::binary);
    if(!msg.ParseFromIstream(&input))
    {
        cerr << "read data from file error." << endl;
        return -1;
    }
    
    // msg 是pb的msg对象,从msg对象里面找到对应“hostname”字段 
    get_feature(msg, "hostName");
}

todo ...


参考:
https://jguegant.github.io/blogs/tech/sfinae-introduction.html
https://my.oschina.net/u/4051725/blog/4458011
https://izualzhy.cn/SFINAE-and-enable_if

stackoverflow上面有很多大佬balalala写了很多,延伸阅读:
https://stackoverflow.com/questions/257288/templated-check-for-the-existence-of-a-class-member-function

相关文章

  • C++模板SFINAE特性与反射机制

    C++模板提供了一个SFINAE(subsitate failure is not an error)的机制(模板...

  • Android之反射机制与JSON解析

    1 反射机制&JSON解析 【 Java的高级特性:反射机制 publicclass Student { pu...

  • java反射学习笔记(一)——反射概述

    [TOC] 反射是什么? 反射(Reflaction in action )是java的高级特性,反射机制是指...

  • Java中反射的用途

    Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对大家以后学习框...

  • Java反射机制

    Java反射机制 前言 网页版的jdk的API离线版API 什么是反射机制 反射是java语言的一个特性,它允程序...

  • C++模板类型推导

    模板是C++的重要特性,是C++标准模板库的基础。模板可以根据数据类型自动生成代码,大大减少重复代码。模板实例化的...

  • 找不到工作的人还在学习(c++4)

    继承与接口 整个c++程序设计全面围绕面向对象的方式进行。类的继承特性是c++的一个非常重要的机制。继承特性可以使...

  • C++中使用QT实现反射机制

    1.反射机制 反射机制在java中是一个非常重要的特性,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道...

  • java反射机制

    java的反射机制 1 JAVA的反射机制是什么?反射机制能做什么?反射机制的优点与缺点2 认识 Class...

  • C++基础一文通(五)泛型 / 模板

    C++另一种编程思想称为 ==泛型编程== ,主要利用的技术就是模板 C++提供两种模板机制:函数模板和类模板 一...

网友评论

      本文标题:C++模板SFINAE特性与反射机制

      本文链接:https://www.haomeiwen.com/subject/aplplltx.html