• c++新标准有用的语法特性



    最近读了一些开源库,发现开源库都会尽量使用最新的语言特性。大量开源库使用c++的新标准,一些新语法,新的模板元编程技术。如果不懂新标准增加的语法的话,这些开源库是很难读懂的,因此也很难用到自己的项目中。这也从侧面反映了,如果要写好一个底层库或者框架,需要对语言的掌握到一定的程度。这些新标准的加入,简化了一些编码工作。比如之前几十行代码解决的问题,现在只要几行就可以解决了;扩展了语言特性,使其看起来更加“现代化”;解决了之前不能解决或者需要很复杂的技巧才能解决的一些问题;

    下面就对最近在读开源库过程中遇到的一些新的语法特性做一个总结。

    代码片段1

    template<typename T, typename = void>
    inline constexpr bool IsReflected_v = false;
    
    • 1
    • 2

    特性1:变量模板

    定义变量使用模板的形式,可以一次定义多个同类变量。

    c++14 新特性变量模板,之前参数化变量的实现方法为:1:类模板的静态数据成员,2:constexpr函数模板

    特性2:头文件变量定义

    inline关键字新用法,在变量定义前加入inline关键字,即使这个变量被多个头文件包含,也可编译通过。

    之前,需要在cpp中定义,然后在h中使用extern关键字。

    代码片段2

    template<typename T, typename F>
    constexpr auto forEachField(T&& obj, F&& f) {
    	return forEachField(
            std::forward<T>(obj),
    		std::forward<F>(f),
    		std::make_index_sequence<std::decay_t<T>::_field_count_>{});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    constexpr函数

    在函数前加constexpr,这样定义的函数,在编译的时候就能得到其返回值。

    万能引用

    在模板函数中*&&*代表的是万能引用,万能引用一般与如下的完美转发配合使用。

    std::forward

    完美转发:按照参数原来的类型转发到另一个函数,因为std::forward可以保存参数的左值或右值特性。

    template<class T> 
    void wrapper(T&& arg) 
    {   
    	foo(std::forward<T>(arg)); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    make_index_sequence

    c++14加入的新特性,std::make_inedex_sequence的作用是产生一个0,1,2,3,…,N-1的数列,该数列在编译期已确定。该特性一般在函数模板中使用,与index_sequence配合使用。

    std::decay_t

    为类型T应用从左值到右值(lvalue - to - rvalue)、数组到指针(array - to - pointer)和函数到指针(function - to - pointer)的隐式转换。转换将移除类型T的cv限定符(const和volatile限定符),并定义结果类型为成员decay::type的类型。这种转换很类似于当函数的所有参数按值传递时发生转换。

    代码片段3

    	template<typename T, typename F, size_t... Is>
    	constexpr auto forEachField(T&& obj, F&& f, std::index_sequence<Is...>) 
        {
    		using TDECAY = std::decay_t<T>;
    
    		if constexpr (std::is_same_v<decltype(f(std::declval<DummyFieldInfo>())), Result>) 
            {
    			Result res{ Result::SUCCESS };
    			(void)(((res = f(typename TDECAY::template FIELD<T, Is>
    				(std::forward<T>(obj)) )) == Result::SUCCESS) && ...);
    			return res;
    		}
    		else 
            {
    			(f(typename TDECAY::template FIELD<T, Is>(std::forward<T>(obj))), ...);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可变参数模板

    c++11引入了可变参数模板。如上的代码片段中,模板参数包含size_t… Is,代表可变参数模板。在类型后面跟… ,即为可变参数模板。本例中可变参数为一组变长的常数,也可以为多个同类型的参数。

    index_sequence

    代表一组数列,在本例中作为可变参数模板。

    C++可变参数模板的展开方式 …

    上面的代码中:

    (void)(((res = f(typename TDECAY::template FIELD<T, Is> 
    	(std::forward<T>(obj)) )) == Result::SUCCESS) && ...);
    
    • 1
    • 2

    && …代表参数展开,代表前面的代码段

    ((res = f(typename TDECAY::template FIELD<T, Is>(std::forward<T>(obj)) )) == Result::SUCCESS)
    
    • 1

    展开N次,并用&&连接。

    这种方式是c++17引入的折叠表达式,分为左折叠和右折叠。

    • args + …表示:(a + (b + (c + d))),这叫做右折叠

    • … + args表示:(((a + b) + c) + d),这叫做左折叠

    • args + … + init表示:(a + (b + (c + (d + init)))),这叫做右折叠

    • init + … + args表示:((((a + init) + b) + c) + d),这叫做左折叠

    在c++11中,要展开参数,有两种方式,一种是递归展开,另一种是使用逗号表达式。

    模板中类型转换

    typename std::decay_t<T>::template FIELD<T, Is>
    
    • 1

    取到T类型中指定的模板类型FIELD

    std::is_same_v

    比较两个类型是否相同,在编译期可知道。和if constexpr配合使用。使函数在编译期就确定执行流程。

    代码片段4

    template<typename T>
    struct CompoundDeserializeTraits<T
        , std::enable_if_t<PrimitiveDeserializeTraits<T>::value>>
    {
        template<typename ELEM_TYPE>
        static Result deserialize(T& obj, ELEM_TYPE node)
        {
            if (!node.isValid())
            {
                return Result::ERR_MISSING_FIELD;
            }
            return PrimitiveDeserializeTraits<T>::deserialize(obj, node.getValueText());
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    std::enable_if_t

    当PrimitiveDeserializeTraits::value为真时,优先匹配这个模板。

    详细解释:

    基础模板为:

    template<typename T, typename = void>
    struct CompoundDeserializeTraits;
    
    • 1
    • 2

    判断所用的基础类型为:

    template<typename T, typename = void>
    struct PrimitiveDeserializeTraits : std::false_type {};
    
    • 1
    • 2

    PrimitiveDeserializeTraits::value默认为假。

    判断所有的基础类型偏特为:

    //数字类型特化
    template<typename Number>
    struct PrimitiveDeserializeTraits<Number, std::enable_if_t<std::is_arithmetic_v<Number>>>
        : std::true_type
    {
        static Result deserialize(Number& num, std::optional<std::string> valueText) 
        {
            if (!valueText.has_value()) 
            { 
                return Result::ERR_EXTRACTING_FIELD;
            }
            // do not treat int8_t/uint8_t as char type
            if constexpr (std::is_same_v<Number, int8_t> || std::is_same_v<Number, uint8_t>) 
            {
                num = std::stol(*valueText, nullptr, detail::isHex(*valueText) ? 16 : 10);
                return Result::SUCCESS;
            }
            else 
            {
                std::stringstream ss;
                ss << *valueText;
                if (detail::isHex(*valueText)) { ss << std::hex; }
                ss >> num;
                return ss.fail() ? Result::ERR_EXTRACTING_FIELD : Result::SUCCESS;
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    当传入的是数类型时,PrimitiveDeserializeTraits::value为真。

  • 相关阅读:
    解决redis从服务器未配置主服务器密码导致数据未同步&磁盘爆满问题
    什么是IOC和什么是AOP
    javascript二维数组(9)toString的用法
    傅里叶级数与傅里叶变换_Part7_离散傅里叶变换的性质
    不同类型的变量与零究竟是如何比较
    cesium 实体无法拾取
    大数据-之LibrA数据库系统告警处理(ALM-25005 Nscd服务异常)
    代码随想录 | 单调栈part01 part02 part03
    lintcode 631 · 最大矩阵II【矩阵 中等 vip】
    Echarts设置环形图中心文字
  • 原文地址:https://blog.csdn.net/iamqianrenzhan/article/details/126574634