• C++中的auto,decltype,typedef,using以及typename


    auto

    值与指针等推导

    简单的东西大家都懂,这里相当于拾遗

    const int a = 10;
    auto b = a;		// b为 non-const int类型
    const cb = a;		// 明确指明const
    

    auto一般情况下会忽略顶层const,保留底层const(顶层const:指针本身是常量,底层const:指针所指对象是常量)

    int* const apc = &a;
    const int* acp = &a;
    auto p = apc;		// p为 int*类型, 顶层const被忽略
    auto cp = acp;		// cp为 const int*类型,底层const被保留
    

    如果像是acpc这种缝合怪使用auto推导出来的是什么类型大家应该都能猜到吧

    返回值推导

    C++11中加入了trailing return type(尾返回类型),使用auto将返回值类型后置

    template<typename T1, typename T2>
    auto MathPlus(T1 a, T2 b) -> decltype(a + b)
    {
        return a + b;
    }
    

    C++14中,auto的使用更进一步,可以直接推导函数返回值

    template<typename T1, typename T2>
    auto MathPlus(T1 a, T2 b) { return a + b; }
    
    int main()
    {
        std::cout << MathPlus(1 + 2.34) << std::endl;
    }
    

    std::initializer_list的推导

    此功能需要开启 /std:c++17

    C++14中

    auto a{ 1, 2, 3 };		//std::initializer_list
    auto b{ 1 };			//std::initializer_list
    

    C++17中

    auto a{ 1, 2, 3 };		//非法
    auto b{ 1 };			//int
    auto c = { 1, 2, 3 };	        //与C++14相同,皆为std::initializer_list
    auto d = { 1 };			//与C++14相同,皆为std::initializer_list
    

    Lambda表达式推导

    在C++11中,Lambda表达式的参数需要具体的类型声明

    auto MyLambda = [](int a, int b) { return a + b; };
    

    C++14中可以这么做了

    auto MyLambda = [](auto a, auto b){ return a + b; };
    

    这里再复习以下Lambda表达式的使用,Lambda表达式其实是块语法糖,其结构如下

    [函数对象参数](函数参数列表) mutable throw(类型)->返回值类型 { 函数语句 };
    
    • 当捕获的是this时,它与其所在的成员函数有着相同的protectedprivate访问权限,且为按引用传递

    • 按值捕获的non-const变量一律无法在Lambda表达式内修改(const就不用说了,在哪都无法修改),mutable关键字表示可以修改按值捕获进来的副本(注意修改的是拷贝而不是值本身)

    • 当明确Lambda表达式不会抛出异常时,可以使用noexcept修饰

      []() noexcept { /* 函数语句 */ }
      
    • 当Lambda表达式没有捕获任何参数时,它可以转换成为一个函数指针

    通用捕获

    C++14中,可在Capture子句,即[ ]中引入并初始化新的变量。这些变量不需再存在于Lambda表达式的封闭范围内。可以使用任意形式的表达式来初始化,同时表达式会自动推导出变量的类型。捕获初始化的顺序为从左往右执行

    auto unip = std::make_unique<int>(10);
    auto lambda = [ptr = std::move(unip)]() { /* ptr... */ }
    

    Constexpr Lambda

    同样的,此功能需要开启std:c++17

    显式constexpr

    auto lambda = [](int num) constexpr { return num + 10; };
    int arr[lambda(10)];
    

    隐式constexpr

    当Lambda满足constexpr条件时,会自动隐式声明其为constexpr。也就是说上面那个例子其实不加constexpr也可以

    当Lambda转换成函数指针时,需要显式指明函数指针为constexpt

    constexpr int(Funcp*)(int) = lambda;
    int arr[Funcp(100)];
    

    捕获 *this

    同样的,此功能需要开启std:c++17

    在C++14中

    class MyClass
    {
    private:
        int num = 10;
        int sum(int a, int b) { return a + b; }
    public:
        auto MyLambda()
        {
            auto lr = [this]() { num = 100; };				//按引用捕获
            auto lv = [_Myc = *this]() mutable { _Myc.num = 100; };		//按值捕获
            lr();
            lv();
        }
    };
    

    在C++17中,按值捕获的编写无需如此复杂

    auto lv = [*this]() mutable { num = 100; };			//按值捕获
    

    重点:[=]中捕获进的this是个指针,因此修改时会改变原来的值

    auto MyLambda()
    {
        int temp = 20;
        auto lv = [=]() { num = 100; temp = 200; };		//编译器报错,temp无法修改
    }
    

    这里num可以被修改,且修改的是类中的值本身;temp不可以被修改,因为没有mutable修饰,即使有mutable,修改的也是捕获进来的副本,而非值本身

    如果不想修改num,只想修改其副本

    auto MyLambda() { auto lv = [=, _Myc = *this] mutable { _Myc.num = 100; }; }	//C++14
    auto MyLambda() { auto lv = [=, *this] mutable { num = 100; }; }		//C++17
    

    若捕获的是*this,且想要调用成员函数,则需要mutable修饰。虽然感觉很少会这么用,但是还是写一下

    auto MyLambda() { auto lv = [_Myc = *this] mutable { int value = _Myc.sum(1,2 ); }; }
    auto MyLambda() { auto lv = [*this] mutable { int value = sum(1, 2); }; }
    

    附上一篇全英的参考资料Lambda Capture of *this

    Range-base-loop with auto

    参考自知乎-蓝色-range-base-loop中使用auto

    总结:

    • 当你想要拷贝range的元素时,使用for(auto x : range)
    • 当你想要修改range的元素时,使用for(auto&& x : range)
    • 当你想要只读range的元素时,使用for(const auto& x : range)

    template<auto>

    不会,摸了

    decltype

    用文字解释的话,autodecltype都是C++11引入的类型推导。decltype能够从表达式中推断出要定义的变量类型

    decltype(a + b) i;		//假设a是int而b是double,那么i的类型就是表达式(a + b)的类型,即double
    

    decltype处理变量时,它与auto不同,并不会去忽略掉顶层const,原变量是啥它就是啥

    decltype处理函数时,它只是获取函数的返回值类型,并不会去调用函数

    decltype处理表达式时,假设类型为T

    std::string name = "Mikasa";
    int& nr = name, * np = &name;
    decltype((name)) d1;			//string&,ERROR,未初始化的引用
    decltype(*(&name)) d2;			//string&,ERROR,未初始化的引用
    decltype(std::move(name)) d3;	        //string&&,ERROR,未初始化的引用
    decltype(*np) d3;			//string&,ERROR,未初始化的引用
    decltype(nr + 0) d4;			//string
    
    • 若表达式的值类型为纯右值,则推导出T
    • 若表达式的值类型为左值,则推导出T&
    • 若表达式的值类型为将亡值,则推导出T&&

    decltype处理Lambda表达式时,情况十分微妙

    auto f = [](int a, int b) { return a + b; };
    //decltype(f) g = [](int a, int b) { return a * b; };	//ERROR
    decltype(f) g = f;					//OK
    

    即使是完全相同的返回值和函数参数类型,但是编译器仍然会报错,因为每一个Lambda类型都是独有且无名的

    typedef和using

    using是C++11加入拓展typedef的同时也让C++的C++味儿更浓了而不是总有在写C的感觉

    typedef int Status;			//我DNA动了
    

    回归主题,在一些十分复杂的名称面前,我们会选择取别名,比如

    typedef std::vector<std::pair<std::string, std::function<void(int)>>> Selection;
    using Selection = std::vector<std::pair<std::string, std::function<void(int)>>>;	//两种方法等效
    

    个人认为,使用using会令代码的可读性更高一些。但对于函数指针来说,这一区别更加明显

    typedef void(*MyFunc)(int, int);
    using MyFunc = void(*)(int, int);		                //两种方法等效
    using MyClassFunc = void(MyClass::*)(double, std::string)	//成员函数指针
    

    除此之外,using能更方便的为模板取别名(alias templates)

    template<typename T>
    class MyAlloc { T data; };
    template<typename T, typename U>
    class MyVector {
        T data;
        U alloc;
    };
    
    template<typename T>
    using Vec = MyVector<T, MyAlloc<T>>;
    Vec<int> v;			// MyVector<int, MyAlloc<int>> v;
    

    typename

    对于刚学习C++不久的人来说,最常见的typename的使用场所就是模板了

    template<typename T>
    template<class T>
    

    上例中typenameclass并无任何差别。初学者选择typename可能会对模板有更好的了解(毕竟若模板传进来的是int,它是内置类型,看起来不是一个class

    进入正题,使用typename可以明确的告诉编译器,后面跟着的这个名字是类中的类型成员,而不是数据成员(例如静态成员变量)

    class Foo {
    public:
        typedef int FooType;
        int f = 10;
    };
    class Bar {
    public:
        static int b;
    };
    int Bar::b = 10;
    
    template<typename param, typename value>
    class MyClass {
    public:
        Foo::FooType MycData1 = 10;				//直接使用Foo中的类型
        typename param::FooType MycData2 = 10;	//需加typename以指明这是一种类型
    private:
        int MycData3 = value::b;			//直接使用Bar中的成员
    };
    

    使用时需按此顺序传递模板参数MyClass<A, B>

    再来点花里胡哨的,使用MyFunc(const T&)可以获取到参数的模板参数类型

    template<typename T>
    class MyClass {
    public:
        using value_type = T;
    };
    template<typename T>
    void MyFunc(const T& t)
    {
        typename T::value_type data;	//定义一个类型与参数的模板参数相同的变量data
        std::cout << typeid(data).name() << std::endl;
    }
    int main()
    {
        MyClass<int> myc;
        MyFunc(myc);
    }
    

    typedef与typename

    给模板类__type_traits<T>中的has_trivial_destructor类型取别名,叫做trivial_destructor

    typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
    using trivial_destructo = typename __type_traits<T>::has_trivial_destructor;	//C++11的写法
    

    给模板类Registration<PointSource, PointTarget>中的PointCloudSource类型取别名,叫做PointCloudSource

    typedef typename Registration<PointSource, PointTarget>::PointCloudSource PointCloudSource;
    using PointCloudSource = typename Registration<PointSource, PointTarget>::PointCloudSource;	//C++11
    

    template消歧义符

    typename类似,template修饰代表告诉编译器它后面的东西是模板名字

    class Array {
    public:
        template <typename T>
        struct InArray { typedef T ElemT; };
    };
    template <typename T>
    void Foo(const T& arr) {
        //typename T::InArray<int>::ElemT num;			//编译时报错,详见下图
        typename T::template InArray<int>::ElemT num;
    }
    

    更多信息可以查看知乎-C++ 为什么有时候必须额外写 template?

  • 相关阅读:
    Boot.ini
    CCP4 SET
    Install GTK+ GLIB
    C head file
    Changes in Python
    ubuntu
    错误: 配置节中设置 validateRequest=false 可以禁用请求验证
    c++中冒号(:)和双冒号(::)的用法
    C++学习之类和结构体
    C++中双冒号的作用
  • 原文地址:https://www.cnblogs.com/tuapu/p/14171489.html
Copyright © 2020-2023  润新知