右值引用与完美转发、变长模板参数与折叠表达式、variant的使用、tuple的使用

通常字面量是右值,但字符串字面量是左值

常量左值引用的作用

常量左值引用 不仅可以引用左值,还能引用右值:const int &x = 11;
而非常量左值引用 只能引用左值
class X
{
public:
    X(){}
    X(const X&){}
    X& operator = (const X&){return *this;}
};
X make_x()
{
    return X();//返回右值,这里发生拷贝构造,恰好const X&是常量左值引用,可以引用右值
}

一旦使用常量左值引用,就无法再修改该对象的内容了

右值引用的作用

右值引用可以延长右值的生命周期。右值引用不能引用左值

使用右值引用可以减少调用拷贝构造(实际上被优化过优化都一样,加命令行参数 -fno-elide-constructors关闭优化才能看到效果)
g++ test1.cpp -fno-elide-constructors

#include <iostream>
class X
{
public:
    X(){
        std::cout<<"构造"<<std::endl;
    }
    ~X(){
        std::cout<<"析构"<<std::endl;
    }
    X(const X&){
        std::cout<<"拷贝"<<std::endl;
    }
    X& operator = (const X&){
        return *this;
    }

};

X make_x()
{
    return X();//返回右值,这里发生拷贝构造,恰好const X&是常量左值引用,可以引用右值
}

int main()
{
    //1.发生2次拷贝构造才得到了x1
    X x1 = make_x();
    //2.使用右值引用减少拷贝
    X&& x2 = make_x();
}

左值右值

  • 泛左值:左值、将亡值

  • 右值:将亡值、纯右值

  • 将亡值的产生途径

    • 使用static_cast 将 泛左值 转为 右值引用
    • 临时量实质化:已知临时对象是纯右值,当要访问该对象的成员时,该临时对象实质化为将亡值
  • 怎么将左值数据转移到另一个左值:

    前面说到,右值引用不能引用左值。但可以先将其转为将亡值再来做。
    int i = 0;
    int &&j = static_cast<int &&> (i);

    X &&a = X();//a是右值引用
    X b = a;//拷贝
    X c = static_cast<X &&> (a);//移动
    //此后不能再使用a了
    

    注意:右值引用是左值

  • 传入临时对象时,函数内部使用上述技巧的好处:

    • 常规做法:

      //接收右值引用,而右值引用 可引用 右值, 不可引用 左值。右值引用是左值。因此这里只能传右值
      void test(X &&a)
      {
          //a是左值
          X b(a);//没有必要的拷贝,a是一个将亡值传进来的,这里直接move更好
      }
      int main()
      {
          //传入将亡值
          test(X());//无论实参是左值还是右值,形参一定是左值
          return 0;
      }
      
    • 改进做法:

      //接收右值引用,而右值引用 可引用 右值, 不可引用 左值。右值引用是左值。因此这里只能传右值
      void test(X &&a)
      {
          //a是左值
          X b(static_cast<X &&> (a));//a被强转为右值,就会调用移动构造来创建b,而不是拷贝
      }
      int main()
      {
          //传入将亡值
          test(X());//无论实参是左值还是右值,形参一定是左值
          return 0;
      }
      
    • c++11提供了左值转右值的方法,不需要自己写static_cast了
      std::move(a)即可转为右值引用

  • 引用折叠规则
    发生引用折叠时,任一引用为左值引用 则 结果为左值引用。否则为右值引用
    引用折叠

  • 实现完美转发:调用std::forward(t)或者写static_cast进行强转

    #include <iostream>
    using namespace std;
    void process(int & i)
    {
        cout<<"左值引用的函数"<<endl;
    }
    
    void process(int && i)
    {
        cout<<"右值引用的函数"<<endl;
    }
    
    template <typename T>
    void myfun(T && t)
    {
        //不能直接process(t),否则无论t是int还是int&,都只能调process(int &)
        //应当将t强转为T &&,即多折叠上&&,这样对于int,就恢复成了右值引用;对于int&,就保持不变
        process(static_cast<T &&> (t));
        // process(std::forward<T>(t));
    }
    int main()
    {   
        int a=3;
        myfun(a);//传入a,T被推导为int &
        myfun(2);//传入2,T被推导为int
        return 0;
    }
    

总结

问:说一个c++11提出的非常重要的概念
右值引用:用来延长右值的生命周期,还实现了与之相关的移动语义和完美转发,许多常用容器都已经实现了移动构造和移动赋值

变长模板参数与折叠表达式

  • 要求1:实现变长参数求和

    c++17中 …有两种展开方式,一种是return (ts + ...)以ts自身开始展开,另一种是return (0 + ... + ts)指定一个初始值,然后展开。这里示例使用+ … ,实际上还可以有* …,甚至逗号 …展开。

    #include <iostream>
    template<class ...Ts>
    auto func(Ts ...ts)
    {
        //return (ts + ...)等价于return (1+2+3)
        return (0 + ... + ts);//这里写上0是为了有一个初始值,否则func()为空参数时会报错
    }
    int main() {
        std::cout<<func()<<std::endl;
        std::cout<<func(1,2,3)<<std::endl;
        return 0;
    }
    

    小细节:如果这样调用func((1,2,3)),会返回3,因为(1,2,3)是逗号表达式,表达式结果是其中的最后一个元素值

  • 要求2:想要ts[0]、ts[1]这样遍历打印参数

    使用逗号表达式,把()当作{},把逗号当作分号使用,相当于一个for循环。例如(myprint(ts), …)就会让参数依次执行myprint。

    #include <iostream>
    template<class T>
    void myprint(T a)
    {
        std::cout<< a;
    }
    
    template<class T0, class ...Ts>
    auto func(T0 t0, Ts ...ts)
    {
        myprint(t0);
        ((myprint(","), myprint(ts)), ...);
        myprint("\n");
    }
    
    int main() {
        func(1,2,3);//1,2,3
        return 0;
    }
    

    如果是在c++11中,无法使用逗号…来展开,可以像下面这样写函数模板。

    auto func(){}
    
    template<class T0, class ...Ts>
    auto func(T0 t0, Ts ...ts)
    {
        myprint(t0);
        myprint(",");
        func(ts...);//1,2,3,
    }
    

    如果想要最后一个参数时不多打印一个逗号,要么利用c++17将myprint(“,”)写在if constexpr分支里,要么使用SFINAE来实现。下面是使用SFINAE(匹配失败不是错误)来实现的

    #include <iostream>
    #include <numeric>
    template<class T>
    void myprint(T a)
    {
        std::cout<< a;
    }
    
    auto func(){
        myprint("\n");
    }
    
    template<class T0, class ...Ts, std::enable_if_t<sizeof...(Ts) == 0, int> = 0>
    auto func(T0 t0, Ts ...ts)
    {
        myprint(t0);
        func(ts...);
    }
    
    template<class T0, class ...Ts, std::enable_if_t<sizeof...(Ts) != 0, int> = 0>
    auto func(T0 t0, Ts ...ts)
    {
        myprint(t0);
        myprint(",");
        func(ts...);
    }
    
    int main() {
        func(1,2,3);//1,2,3
        return 0;
    }
    

    实际上还可以结合lambda表达式使用

    ([&](){
        myprint(",");
        myprint(ts);
    }(), ...);
    
  • 要求3:怎么找到参数类型的共同类型(例如int和float共同类型是float,Cat和Animal共同类型是Animal)
    方法1:using T = std::common_type_t<Ts...>;
    方法2:利用decltype

    using type = decltype(1?int():double());得到的type是double,因为?后面的值在比较前要先提升到同一类型,这里提升为double。
    例如:下面的res类型为double
    double x = 3.5;
    int y = 4;
    auto res = (0 ? x : y);

    //这里使用三目运算符配合decltype得到共同类型。
    //之所以不使用decltype(T1()+T2()),
    //是因为T1和T2类型可能不是数值类型,可能是子类与父类的关系。
    template<class T1, class T2>
    struct common_type
    {
        using type = decltype(0?T1():T2());
        //如果T1和T2没有构造函数,可以使用declval凭空构建
        //using type = decltype(0?std::declval<T1>():std::declval<T2>());
    };
    
    int main() {
        using what = typename common_type<const char *, char *>::type;
        return 0;
    }
    

    拓展1:多使用模板偏特化来获取多个类型的共同类型

    #include <iostream>
    #include <typeinfo>
    //求两个类型的共同类型
    template<class T1, class T2>
    struct common_type_two
    {
        using type = decltype(0?std::declval<T1>():std::declval<T2>());
    };
    //主模板
    template<class ...Ts>
    struct common_type
    {    
    };
    //特化:结束条件,只有一个类型时,直接返回自身
    template<class T0>
    struct common_type<T0>
    {    
        using type = T0;
    };
    //特化
    template<class T0, class T1, class ...Ts>
    struct common_type<T0,T1,Ts...>
    {
        using type = typename common_type_two<T0,typename common_type<T1,Ts...>::type>::type;
        
    };
    
    int main() {
        using what = typename common_type<double, int, float>::type;
        return 0;
    }
    

    拓展2:使用编译期函数搭配条件编译来实现

    #include <typeinfo>
    #include <iostream>
    
    template <class T0, class ...Ts>
    constexpr auto get_common_type(T0 t0, Ts ...ts)
    {
        if constexpr (sizeof...(Ts) == 0){
            return t0;
        }
        else{
            return 0 ? t0 : get_common_type(ts...);
        }
    }
    int main() {
        using what = decltype(int(), double(), float());
        return 0;
    }
    

    拓展2的问题:如果是想得到子类和父类的共同类型,不能使用std::declval来凭空构造,因为这里是真的要调用构造函数得到对象来传递参数到get_common_type。
    拓展2的解决:但我们实际上需要的根本不是对象t0,而是它的类型,因此可以使用dummy(c++20才有),下面自己实现了一个dummy,T类型没有构造函数,但dummy可以有,因此可以传递dummy类型的变量。只需要最后调用dummy提供的返回T类型凭空构造的方法,对其decltype,就能获得最终T类型。

    #include <typeinfo>
    #include <iostream>
    
    template <class T>
    struct dummy
    {   //这里自己定义的declval直接调用std库的declval进行凭空构造,外层decltype一下就能得到T类型
        static constexpr T declval()
        {
            return std::declval<T>();
        }
    };
    
    template <class T0, class ...Ts>
    constexpr auto get_common_type(dummy<T0> t0, dummy<Ts> ...ts)
    {
        if constexpr (sizeof...(Ts) == 0){
            return t0;
        }
        else{ //t0.declval()是dummy<Animal>
            return dummy< decltype(0 ? t0.declval() : get_common_type(ts...).declval()) >();
        }
        
    }
    struct Animal
    {};
    struct Cat:Animal
    {
        Cat(Cat &&) = delete;//删除移动,则拷贝与默认构造都没了
    };
    
    int main() {
        using what = decltype(get_common_type(dummy<Animal>(), dummy<Cat>()).declval());
        return 0;
    }
    
  • 要求4:想要变长参数类型全是int类型

    只需要使用c++20里的requires结合变长模板参数。如果想要能转换成int的都可以,那么将下面的is_same_v改为is_convertible_v

    template <class ...Ts>
    //为了避免无参数时报错,不要写成requires((std::is_same_v<Ts, int> && ...))
    requires((true && ... && std::is_same_v<Ts, int>))
    void myvec(Ts ...ts)
    {}
    
  • 要求5:判断类型T0 是否为 …Ts中的其中一个类型

    即T0要与…Ts进行 “或” 比较,使用std::is_same_v<T,Ts>即可比较,再搭配上变长模板参数…

    template<class T, class ...Ts>
    struct is_same_any
    {
        static constexpr bool value = (false || ... || std::is_same_v<T,Ts>);
    };
    
  • 要求6:之前求共同类型时,需要把各种类型(int float double)等一起传来传去,不如写成tuple类型

    //1.定义tuple类型
    using Tup = std::tuple<int, float, double>;
    //2.把之前的common_type结构体用tuple包装一遍,就可以这样使用了
    //using what = tuple_apply_common_type<Tup>::type;
    //主模板
    template<class Tup>
    struct tuple_apply_common_type
    {};
    //特化
    template<class ...Ts>
    struct tuple_apply_common_type<std::tuple<Ts...>>
    {	//直接调用之前写的求共同类型
        using type = typename common_type<Ts...>::type;
    };
    
  • variant的使用

    #include <variant>
    int main() {
        //1.获知当前类型在variant的索引(从0开始)
        // std::variant<int, std::string, double> v = 3.5;//如果没赋值,默认为第一个类型
        std::variant<int, std::string, double> v {std::in_place_index<2>,2.5};//明确写出要启用的数据类型
        // std::variant<int, std::string, double> v {std::in_place_type<double>, 2.5};//与上面等价
        std::cout<< v.index() <<std::endl;//2
        //2.get获取值
        std::cout<< std::get<double>(v) <<std::endl;//2.5,如果写成了get<int>会报错std::bad_variant_access
        //3.get_if获取值,得到的是指针,如果类型不正确会返回nullptr,使得有可以在访问前进行安全判断
        int* p = std::get_if<int>(&v);//get_if只接受指针
        std::cout<< (p == nullptr) <<std::endl;//安全判断:得知p为空指针
        //4.检查当前variant是不是int类型
        std::cout<< std::holds_alternative<int>(v) <<std::endl;//0 不是
        //5.可切换的类型个数
        std::cout<< std::variant_size_v<decltype(v)> <<std::endl;//3
        //6.访问
        std::visit([](auto && arg){
            std::cout<<arg<<std::endl;//2.5
        },v);
        //打印nh yx
        std::visit([](const auto &t){std::cout<<t<<" yx"<<std::endl;},std::variant<int, float, std::string> ("nh"));
    
        return 0;
    }
    
  • tuple的使用

    //1.创建元组
    std::tuple<int,double> a1(2, 2.5);
    //2.访问与修改
    std::get<0>(a1) = 3;//修改
    std::cout<< std::get<0>(a1) <<std::endl;//3
    //3.元组的元素个数
    std::cout<< std::tuple_size<decltype(a1)>::value <<std::endl;//2
    //4.解包
    int m;
    double n;
    std::tie(m,n) = a1;
    //5.获取元素类型
    using what = std::tuple_element<0, decltype(a1)>::type;//int
    //元组拼接
    std::tuple<float, char> a2;
    auto res = std::tuple_cat(a1, a2);//std::tuple<int, double, float, char> res
    
  • 要求7:tuple类型的连接

    template<class Tup1, class Tup2>
    struct tuple_cat
    {
    };
    
    template<class ...T1s, class ...T2s>
    struct tuple_cat<std::tuple<T1s...>,std::tuple<T2s...>>
    {
        using type = std::tuple<T1s..., T2s...>;
    };
    int main() {
        using what = tuple_cat<Tup1, Tup2>::type;
        return 0;
    }
    
  • 要求8:tuple的push_front

    template<class T1, class T2>
    struct tuple_push_front{};
    
    template<class T1, class ...T2s>
    struct tuple_push_front<T1, std::tuple<T2s...>>
    {    
        using type = std::tuple<T1, T2s...>;
    };
    //使用
    using what2 = tuple_push_front<char, Tup1>::type;
    
  • 要求9:获取tuple的第一个索引类型
    方式1:使用std::tuple_element_t获取指定下标的索引类型

    template<class T>
    struct tuple_getfirst
    {};
    
    template<class ...Ts>
    struct tuple_getfirst<std::tuple<Ts...>>
    {
        using type = std::tuple_element_t<0,std::tuple<Ts...>>;
    };
    

    方式2:自己实现

    template<class T>//如果tuple是空的,就会落入这里,因此其实可以在这里设置一个默认值
    struct tuple_getfirst
    {};
    
    template<class T0, class ...Ts>
    struct tuple_getfirst<std::tuple<T0, Ts...>>
    {
        using type = T0;
    };
    
  • 要求10:实现tuple_element获取任意下标的索引类型
    利用类模板递归

    #include <tuple>
    //主模板
    template<int index, class T>
    struct tuple_element
    {};
    //特化:index为0时
    template<class T0, class ...Ts>
    struct tuple_element<0, std::tuple<T0, Ts...>>
    {
        using type = T0;
    };
    //特化
    template<int index, class T0, class ...Ts>
    struct tuple_element<index, std::tuple<T0, Ts...>>
    {
        using type = typename tuple_element<index-1, std::tuple<Ts...>>::type;
    };
    
    int main() {
        using Tup1 = std::tuple<int,float,char>;
        using what = tuple_element<1,Tup1>::type;//float
        return 0;
    }
    
  • 要求11:判断tuple里的类型是不是全为integral(int char short)
    利用c++17的…折叠表达式

    template<class T>
    struct tuple_all_integral
    {};
    
    template<class ...Ts>
    struct tuple_all_integral<std::tuple<Ts...>>
    {
        static constexpr bool value = (true && ... && std::is_integral_v<Ts>);
    };
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值