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
时,它与其所在的成员函数有着相同的protected
,private
访问权限,且为按引用传递 -
按值捕获的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
用文字解释的话,auto
与decltype
都是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>
上例中typename
与class
并无任何差别。初学者选择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?