C++中模板编程极大地简化了代码的编写,但同时也会带来一些使用上的疑惑,例如一下代码片段,判断两个数是否相等,只要是重载了==
运算符,自定义类型也可以使用该模板实例化对应的比较函数:
/**
* @file comcept.cpp
* @brief
* @author YongDu
* @date 2021-07-20
*/
template <typename T>
auto isEqual(T left, T right) {
return left == right;
}
int main() {
cout << std::boolalpha;
double a = 2.334;
double b = 2.335;
cout << isEqual(2, 2) << endl; // true
cout << isEqual(b - a, 0.001) << endl; // false
return 0;
}
我们知道,浮点数因为在存储中涉及精度问题,所以不能直接去判断两个浮点数是否想定,需要自定义精度去判断。模板函数依然可以正确执行,但结果却不是我们想要的,因此我们需要使用该模板的类型做一些限制。假如说我们限定次函数的类型为整型,当然,这只是举个例子,如果单纯地限定为整型,也就没有使用模板的必要。我们可以采用C++11中的静态断言static_asser
,该断言会在编译期执行检测,判断类型是否为整型,如下:
template <typename T>
auto isEqual(T left, T right) {
static_assert(std::is_integral<T>::value);
return left == right;
}
此时使用浮点数进行比较,便会触发断言,编译失败,如下图所示。还有个问题就是此时断言在函数内部,也就是说必须通过模板实例化对应函数,然后执行该函数时才能触发断言,某些情况下很可能编译成功,在运行期才会发生错误。
C++17提供了concept机制,用来检查模板参数类型,代码如下:
template <typename T>
concept Integeral = std::is_integral<T>::value;
template <Integeral T>
auto isEqual(T left, T right) {
return left == right;
}
int main() {
cout << std::boolalpha;
double a = 2.334;
double b = 2.335;
cout << isEqual(2, 2) << endl; // true
cout << isEqual(b - a, 0.001) << endl; // false
return 0;
}
此时的报错信息更清晰,方便了我们去排查错误。如上的concept使用方法还有以下3种写法,效果是一样的,有人推崇第四种写法,省略了typename关键字,但是我觉得第一种写法更清晰:
// #2
template <typename T>
requires Integeral<T>
auto isEqual(T left, T right) { return left == right; }
// #3
template <typename T>
auto isEqual(T left, T right) requires Integeral<T> {
return left == right;
}
// #4
Integeral auto isEqual(Integeral auto left, Integeral auto right) { return left == right; }
ranges
:代表一段元素,之前版本使用begin
和end
标识一段元素,那么用ranges有什么好处呢?
|
的串行操作成为可能。相关概念:
具体看代码(需要包含
头文件):
简化操作:
std::sort
和std::ranges::sort
比较
std::vector<int> vec{3, 1, 2, 5, 6, 4};
// std::sort()
std::sort(vec.begin(), vec.end());
std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>{cout, " "}); // 1 2 3 4 5 6
// ranges::sort()
std::ranges::sort(vec);
std::ranges::copy(vec, std::ostream_iterator<int>{cout, " "}); // 1 2 3 4 5 6
串联视图:
定义了一个vector,even用来筛选偶数,square返回参数的平方
std::vector<int> vec{1, 2, 3, 4, 5, 6};
auto even = [](int i) { return i % 2 == 0; };
auto square = [](int i) { return i * i; };
auto result = vec | std::views::filter(even) | std::views::transform(square);
/* 此时 result 并没有被计算,只是定义了一系列视图,只有到下面的遍历操作时,result 触发,即所谓延迟计算 */
for (auto i : result) {
cout << i << " "; // 4 16 36
}
过滤和变换:
std::vector vec{1, 2, 3, 4, 5};
auto result = vec | std::views::transform([](int i) { return i * i; }) | std::views::take(3);
cout << std::accumulate(result.begin(), result.end(), 0) << endl; // 14
这个特性没有太多的资料,编译器也没有完全实现,所以根据网上资料大概整理一下:
所谓协程,是一个函数,能在保持状态的时候暂停或者继续。具有关键字:
co_await
:挂起协程,等待其它计算完成
co_return
:退出协程
co_yield
:弹出一个值,挂起协程,等待下一次调用,类似于Python中的生成器
限制:
协程不能使用可变参数、普通返回语句或占位符返回类型(auto或concept)。constexpr函数、构造函数、析构函数和主函数不能是协程。
以下代码是利用协程特性创建一个斐波那契数列的生成器(使用MSVC
编译器):
/**
* @file coroutines.cpp
* @brief
* @author YongDu
* @date 2021-07-20
*/
#include
#include
#include
using std::cout, std::endl;
using namespace std::experimental;
generator<int> fibonacci(size_t num) {
int first = 0, second = 1;
int result;
for (size_t i = 0; i < num; ++i) {
result = first;
co_yield result; // 协程挂起,返回result,等待下一次调用
first = second;
second = result + first;
}
}
int main() {
auto fibs = fibonacci(10U);
for (auto fib : fibs) {
cout << fib << " ";
}
// 0 1 1 2 3 5 8 13 21 34
return 0;
}
如果在MSVC
环境下,记得开启以下两项:
关键字:import
,export
新建测试文件test_module.ixx
和main.cpp
,注意模块文件后缀名为.ixx
,如果为.cpp
文件可能需要去命令行编译,使用方式和include
差不多,但是说使用模块比使用include
效率要高很多:
// test_module.ixx
export module test_module;
namespace _module {
auto _print() { return "hello world"; }
export auto print() { return _print(); }
} // namespace _module
// main.cpp
import test_module;
#include
int main() {
std::cout << _module::print() << std::endl; // hello world
return 0;
}
模块优势:
通过它可以判断编译器是否支持某个功能,例如:
// 语言特性
__has_cpp_attribute(fallthrough)
__cpp_binary_literals
__cpp_char8_t
__cpp_coroutines
// 标准库特性
__cpp_lib_concepts
__cpp_lib_ranges
__cpp_lib_scoped_lock
<=>
(x <=> y) < 0 // 如果 x < y, 返回 true
(x <=> y) > 0 // 如果 x > y, 返回 true
(x <=> y) == 0 // 如果 x == y, 返回 true
可以使用以下代码代替所有的比较运算符:
auto X::operator<=>(const X&) = default;
在C++17中增加了if,switch
语句中的初始化,C++20中增加了范围for循环的初始化,如下:
struct Data {
std::vector<int> values;
};
Data getData() { return Data(); }
int main() {
for (auto data = getData(); auto &value : data.values) {
cout << value << " ";
}
return 0;
}
C++20中需要显式捕获this指针
C++20之前,使用[=]
会隐式捕获到this
指针,C++20中移除了这一行为,如果需要访问this
指针,需要显式捕获:
[=, this]
lambda表达式支持模板语法
// pre C++20
auto func = [](auto vec) { using T = typename decltype(vec)::value_type; };
// since C++20
auto func = []<typename T>(std::vector<T> vec) {
// ...
};
// pre C++20
auto func = [](auto const& x){
using T = std::decay_t<decltype(x)>;
T copy = x; T::static_function();
using Iterator = typename T::iterator;
}
// since C++20
auto func = []<typename T>(const T& x){
T copy = x; T::static_function();
using Iterator = typename T::iterator;
}
// pre C++20
auto func = [](auto&& ...args) {
return foo(std::forward<decltype(args)>(args)...);
}
// since C++20
auto func = []<typename …T>(T&& …args){
return foo(std::forward(args)...);
}
初始化包展开
// Pre C++20
template<class F, class... Args>
auto delay_invoke(F f, Args... args){
return [f, args...]{
return std::invoke(f, args...);
}
}
// Since C++20
template<class F, class... Args>
auto delay_invoke(F f, Args... args){
// Pack Expansion: args = std::move(args)...
return [f = std::move(f), args = std::move(args)...](){
return std::invoke(f, args...);
}
}
template <typename IterT>
void workWithIterator(IterT it) {
typename std::iterator_traits<IterT>::value_type tmp(*it); // Before C++20
std::iterator_traits<IterT>::value_type tmp(*it); // Since C++20
}
constexpr函数可以在编译期执行,也可以在运行期执行,consteval函数只能在编译期执行,如果不满足要求则编译失败
constinit:强制指定以常量方式初始化
const char *GetStringDyn() { return "dynamic init"; }
constexpr const char *GetString(bool constInit) { return constInit ? "constant init" : GetStringDyn(); }
constinit const char *a = GetString(true); // compliant
constinit const char *b = GetString(false); // non-compliant
int main() { return 0; }
enum class Gender { Man, Women };
void foo1(Gender gender) {
switch (gender) {
case Gender::Man:
// ...
break;
case Gender::Women:
// ...
break;
default:
break;
}
return;
}
void foo2(Gender gender) {
switch (gender) {
using enum Gender; // just this
case Man:
// ...
break;
case Women:
// ...
break;
default:
break;
}
return;
}
在C++20中可以自定义绑定的第几个是哪个类型,而且可以制定解绑的个数。
自定义实现:
get(Type)
函数或者在类内实现Type::get()
成员函数get()
的返回路径数量必须与tuple_size
指定的数值相等,tuple_element
特化的索引数量(且必须从0开始)必须与tuple_size
指定的数值相等get()
函数中N的值对应的返回类型必须与tuple_element
对应索引指定的类型相同struct A {
int m_a;
int m_b;
};
struct X : private A {
std::string val1;
std::string val2;
};
template <int N>
auto &get(X &x) {
if constexpr (0 == N) {
return x.val2;
}
}
namespace std {
template <>
class tuple_size<X> : public std::integral_constant<int, 1> {};
template <>
class tuple_element<0, X> {
public:
using type = std::string;
};
} // namespace std
int main() {
X x;
auto &[y] = x;
// auto &[y1, y2] = x; // type 'X' decomposes into 1 elements, but 2 names were provided
return 0;
}
struct A {
int a;
int b;
};
struct X : protected A {
std::string value1;
std::string value2;
// 第二种方式,在类内实现get<>
template <int N>
auto &get() {
if constexpr (N == 0)
return value1;
else if constexpr (N == 1)
return a;
}
};
namespace std {
template <>
class tuple_size<X> : public std::integral_constant<int, 2> {};
template <>
class tuple_element<0, X> {
public:
using type = std::string;
};
template <>
class tuple_element<1, X> {
public:
using type = int;
};
} // namespace std
int main() {
X x;
auto &[y1, y2] = x; // y1为string类型,y2为int类型
return 0;
}
struct A {
int value;
constexpr bool operator==(const A &v) const { return value == v.value; }
};
template <auto a, auto b>
struct Equal {
static constexpr bool value = a == b;
};
int main() {
static constexpr A a{10}, b{20};
std::cout << std::boolalpha << Equal<a, b>::value << std::endl; // false
std::cout << std::boolalpha << Equal<a, a>::value << std::endl; // true
return 0;
}
不怎么理解这个特性,所使用编译器也不支持这个属性,以后有时间再探究。
在分支语句中,告诉编译器哪个分支更容易执行,哪个分支不容易被执行,方便编译器做优化:
constexpr long long fact(long long num) noexcept {
if (num > 1) [[likely]]
return num * fact(num - 1);
else [[unlikely]]
return 1;
return 1;
}
表明返回值不可忽略,可以添加提示信息,如下代码所示:
[[nodiscard("This return value is important!")]] void *getData() {
/* do some work */
return nullptr;
}
int main() {
getData();
return 0;
}
dynamic_cast
和typeid
atomic
智能指针atomic
atomic
智能指针使用时保证线程安全
C++20中可以简化函数模板的写法,如下:
void f1(auto); // same as template void f(T)
void f2(C1 auto); // same as template void f2(T), if C1 is a concept
void f3(C2 auto...); // same as template void f3(Ts...), if C2 is a concept
void f4(const C3 auto*, C4 auto&); // same as template void f4(const T*, U&);
template <class T, C U>
void g(T x, U y, C auto z); // same as template void g(T x, U y, W z);
头文件
,简单使用:
auto y1 = year{2021};
auto y2 = 2021y;
auto m1 = month{7};
auto m2 = July;
auto d1 = day{21};
auto d2 = 21d;
year_month_day date1{2021y, July, 21d};
auto date2 = 2021y / July / 21d;
year_month_day date3{Monday[3] / July / 2021};
weeks w{1}; // 一周
days d{w}; // 一周的天数
utc_clock, tai_clock, gps_clock, file_clock
system_clock::time_point t = sys_days{2021y / July / 21d}; // date -> time_point
auto date = year_month_day{floor<days>(t)}; // time_point -> date
auto datetime = sys_days{2021y / July / 21d} + 21h + 52min + 33s;
cout << datetime << endl; // 2021-07-21 21:52:33
zoned_time t1 = {"America/Denver", datetime};
auto t2 = zoned_time{"America/Denver", local_days{Monday[1] / July / 2021} + 9h};
auto t3 = zoned_time{current_zone(), system_clock::now()};
某段连续数据的视图,不持有数据,不分配和销毁数据,类似于std::string_view
。拷贝操作高效,可通过运行期确定长度,也可通过编译期确定长度。
int data[42];
std::span<int, 42> sa{data};
std::span sb{data};
包含 C++ 标准库版本, 发布日期, 版权证书, 特性宏等
Python风格的字符串格式化
std::string str = std::format("hello {}", "world");
cout << str << endl; // hello world
数学常量库,如图可以看到包含了很多数学常量:
用于获取代码位置信息,对于日志库和错误信息比较方便:
#include
#include
#include
using namespace std;
int main() {
auto loc = source_location::current();
string logInfo = format("{}:{}:{}: info: this is a note.", loc.file_name(),
loc.line(), loc.column());
cout << logInfo << endl; // E:\Code\cpp17\main.cpp:7:31: info: this is a note.
return 0;
}
判断当前环境为大端序还是小端序:
if constexpr (std::endian::native == std::endian::big) {
std::cout << "big-endian" << std::endl;
} else if constexpr (std::endian::native == std::endian::little) {
std::cout << "little-endian" << std::endl;
} else {
std::cout << "mixed-endian" << std::endl;
}
移除const,varient,引用属性
判断数组是否有界
starts_with
和ends_with
contains
查询某个键是否存在
增加shift_left
和shift_right
增加midpoint
方法方便计算中位数,该方法可避免溢出,还有lerp
函数,计算线性差值