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?

原文地址:https://www.cnblogs.com/tuapu/p/14171489.html