1. structured binding
Binds the specified names to subobjects or elements of the initializer. Like a reference, a structured binding is an alias to an existing object. Unlike a reference, a structured binding does not have to be of a reference type.
类似引用,可以绑定到已经存在的结构化对象的成员,但不一定是引用类型(即可以是copy)
语法:
auto (thread local | static) ref-qualifier [identifier-list] = exp;
auto (thread local | static) ref-qualifier [identifier-list]{exp};
auto (thread local | static) ref-qualifier [identifier-list](exp);
其中,
ref-qualifier
: 可以选择是否是引用类型(&,&&)或拷贝identifier-list
: 列出被绑定的成员的别名
Case study:
1.1 bind to array
// clang -o bind_array -std=c++17 bind_array.cc
// g++ -o bind_array -std=gnu++17 bind_array.cc
#include <cstdio>
void foo_bind_array() {
int a[2] = {1, 2};
// NOTE. make a copy of a, and [x, y] refer to the copied temp array.
auto [x, y] = a;
// NOTE. [xr, yr] refers to original array `a` directly.
auto &[xr, yr] = a;
printf("original addr:%p\n", (void *)a);
printf("after cp, addr:%p,%p\n", (void *)&x, (void *)&y);
printf("after ref, addr:%p,%p\n", (void *)&xr, (void *)&yr);
printf("x,y={%d,%d}\n", x, y);
printf("xr,yr={%d,%d}\n", xr, yr);
}
int main() {
foo_bind_array();
return 0;
}
output:
original addr:0x7fffffffe2c8
after cp, addr:0x7fffffffe2d0,0x7fffffffe2d4
after ref, addr:0x7fffffffe2c8,0x7fffffffe2cc
x,y={1,2}
xr,yr={1,2}
1.2 bind to tuple
// clang -o bind_tuple -std=c++17 bind_tuple.cc
// g++ -o bind_tuple -std=gnu++17 bind_tuple.cc
#include <cstdio>
#include <tuple>
void foo_bind_tuple() {
float x{1.23};
char y{'x'};
int z{996};
printf("x:type(float), val(%f), addr(%p)\n", x, (void *)&x);
printf("y:type(char), val(%c), addr(%p)\n", y, (void *)&y);
printf("z:type(int), val(%d), addr(%p)\n", z, (void *)&z);
//std::tuple<float &, char &&, int> tpl(x, std::move<char&&>(y), z);
std::tuple<float &, char &&, int> tpl(x, std::forward<char&&>(y), z);
const auto &[a, b, c] = tpl;
printf("a:type(float), val(%f), addr(%p)\n", a, (void *)&a);
printf("b:type(char), val(%c), addr(%p)\n", b, (void *)&b);
printf("c:type(int), val(%d), addr(%p)\n", c, (void *)&c);
// clang-format off
// using Tpl = const std::tuple<float&, char&&, int>;
// a names a structured binding that refers to x (initialized from get<0>(tpl))
// decltype(a) is std::tuple_element<0, Tpl>::type, i.e. float&
// b names a structured binding that refers to y (initialized from get<1>(tpl))
// decltype(b) is std::tuple_element<1, Tpl>::type, i.e. char&&
// c names a structured binding that refers to the third component of tpl, get<2>(tpl)
// decltype(c) is std::tuple_element<2, Tpl>::type, i.e. const int
// clang-format on
}
int main() {
foo_bind_tuple();
return 0;
}
output:
x:type(float), val(1.230000), addr(0x7fffffffe298)
y:type(char), val(x), addr(0x7fffffffe297)
z:type(int), val(996), addr(0x7fffffffe29c)
a:type(float), val(1.230000), addr(0x7fffffffe298)
b:type(char), val(x), addr(0x7fffffffe297)
c:type(int), val(996), addr(0x7fffffffe2c0)
1.3 bind struct data member
// clang -o bind_data_member -std=c++17 bind_data_member.cc
// g++ -o bind_data_member -std=gnu++17 bind_data_member.cc
#include <cstdio>
struct S {
mutable int x1 : 2; // mutable data member, could be modified by const
// member function
volatile double y1;
void dump() {
printf("x1:%d,addr:%p, y1:%lf, addr:%p\n", x1, (void *)this, y1,
(void *)&y1);
}
};
void foo_bind_data_member_ref() {
printf("\n%s\n", __func__);
S s{1, 2.3};
const auto &[x, y] = s; // x is an int lvalue identifying the 2-bit bit-field
// y is a const volatile double lvalue
printf("Before modify:\n");
s.dump();
x = 0; // S.x1 is mutable data member, could be modified although [x, y] is
// const value.
// y = -1.23; // Error: y is const-qualified
printf("After modify:\n");
s.dump();
}
void foo_bind_data_member_cp() {
printf("\n%s\n", __func__);
S s{1, 2.3};
const auto [x, y] = s; // x is an int lvalue identifying the 2-bit bit-field
// y is a const volatile double lvalue
printf("Before modify:\n");
s.dump();
x = 0; // S.x1 is mutable data member, could be modified although [x, y] is
// const value.
// y = -1.23; // Error: y is const-qualified
printf("After modify:\n");
s.dump();
}
int main() {
foo_bind_data_member_ref();
foo_bind_data_member_cp();
return 0;
}
output:
foo_bind_data_member_ref
Before modify:
x1:1,addr:0x7fffffffe2b0, y1:2.300000, addr:0x7fffffffe2b8
After modify:
x1:0,addr:0x7fffffffe2b0, y1:2.300000, addr:0x7fffffffe2b8
foo_bind_data_member_cp
Before modify:
x1:1,addr:0x7fffffffe2b0, y1:2.300000, addr:0x7fffffffe2b8
After modify:
x1:1,addr:0x7fffffffe2b0, y1:2.300000, addr:0x7fffffffe2b8
1.4 bind to map
// clang -o bind_map -std=c++17 -lstdc++ bind_map.cc
// g++ -o bind_map -std=gnu++17 -lstdc++ bind_map.cc
#include <cstdio>
#include <string>
#include <unordered_map>
struct Key {
int _key;
Key(const int k) : _key(k) {}
struct local_hash {
std::size_t operator()(const Key &k) const noexcept {
return std::hash<int>{}(k._key);
}
};
struct local_eq {
bool operator()(const Key &l, const Key &r) const noexcept {
return l._key == r._key;
}
};
};
struct Value {
std::string _val;
Value(const std::string &v) : _val(v) {}
};
typedef std::unordered_map<Key, Value, Key::local_hash, Key::local_eq> map_t;
void foo_bind_map_ref() {
printf("\n%s\n", __func__);
map_t _map{{Key(0), Value("0_val")},
{Key(1), Value("1_val")},
{Key(2), Value("2_val")}};
/*
NOTE: try_emplace 是C++17的新特性,返回一个tuple,其中第二个表示是否成功。
*/
#define insert_map(seq) \
do { \
if (const auto &[iter, success] = _map.try_emplace(seq, #seq "_val"); \
!success) { \
printf("emplace fail\n"); \
} \
} while (0)
insert_map(10);
insert_map(11);
insert_map(12);
#undef insert_map
for (const auto &[key, val] : _map) {
printf("k:%d, v:%s\n", key._key, val._val.c_str());
}
}
int main() {
foo_bind_map_ref();
return 0;
}
output:
foo_bind_map_ref
k:12, v:12_val
k:11, v:11_val
k:10, v:10_val
k:0, v:0_val
k:1, v:1_val
k:2, v:2_val
1.5 captured by lambda
// clang -o bind_captured_by_lambda -std=c++17 bind_captured_by_lambda.cc
// g++ -o bind_captured_by_lambda -std=gnu++17 bind_captured_by_lambda.cc
#include <cstdio>
#include <cassert>
struct S {
int p{6}, q{7};
};
int main() {
const auto &[b, d] = S{};
auto l = [b, d] { return b * d; }; // valid since C++20 for both clang and g++. clang not handle this before C++20.
// auto l = [b=b, d=d] { return b * d; }; // force passing `b,d` into lambda is ok.
assert(l() == 42);
return 0;
}
2. tuple implicit deduction
C++17增加了tuple类型的推导,不需要手动指定模板参数
// clang++ -o tuple_implicit_infer -std=c++17 tuple_implicit_infer.cc
// g++ -o tuple_implicit_infer -std=gnu++17 tuple_implicit_infer.cc
#include <cstdio>
#include <string>
#include <tuple>
void before_cpp17() {
int a0 = 123;
float a1 = 3.21;
char a2 = 'y';
std::string a3("tuple");
std::tuple<int, float, char, std::string> tpl(a0, a1, a2, a3);
}
void after_cpp17() {
int a0 = 123;
float a1 = 3.21;
char a2 = 'y';
std::string a3("tuple");
std::tuple tpl(a0, a1, a2, a3);
}
int main() {
before_cpp17();
after_cpp17();
return 0;
}
3. if
3.1 if constexpr
The statement that begins with if constexpr is known as the constexpr if statement.
编译时分支判断,可以部分替代类似#if..#elif
甚至用模板实现的重载等。
Case study:
// clang++ -o if_constexpr -std=c++17 if_constexpr.cc
// g++ -o if_constexpr -std=gnu++17 if_constexpr.cc
#include <type_traits>
// 通过 `std::is_pointer_v`在编译期间判断类型,选择不同分支
template<typename T>
auto get_value(T t)
{
if constexpr (std::is_pointer_v<T>)
return *t; // deduces return type to int for T = int*
else
return t; // deduces return type to int for T = int
}
// 类似`#if true`的功能
extern int x; // no definition of x required
int f()
{
if constexpr (true)
return 0;
else if (x)
return x;
else
return -x;
}
void f2()
{
if constexpr(false)
{
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
// 检查模板参数数量
template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs)
{
// ... handle p
if constexpr (sizeof...(rs) > 0)
g(rs...); // never instantiated with an empty argument list.
}
// 编译期逻辑推导
template<class T>
void g2()
{
auto lm = [=](auto p)
{
if constexpr (sizeof(T) == 1 && sizeof p == 1)
{
// this condition remains value-dependent after instantiation of g<T>,
// which affects implicit lambda captures
// this compound statement may be discarded only after
// instantiation of the lambda body
}
};
}
// 检查是否是数值类型
template<typename T>
void f3()
{
if constexpr (std::is_arithmetic_v<T>) {}
// ...
else {
using invalid_array = int[-1]; // ill-formed: invalid for every T
static_assert(false, "Must be arithmetic"); // ill-formed before CWG2518
}
}
3.2 if init-statement
支持在if
条件判断语句中进行临时变量初始化
std::map<int, std::string> m;
std::mutex mx;
extern bool shared_flag; // guarded by mx
int demo()
{
if (auto it = m.find(10); it != m.end())
return it->second.size();
if (char buf[10]; std::fgets(buf, 10, stdin))
m[0] += buf;
if (std::lock_guard lock(mx); shared_flag)
{
unsafe_ping();
shared_flag = false;
}
if (int s; int count = ReadBytesWithSignal(&s))
{
publish(count);
raise(s);
}
if (const auto keywords = {"if", "for", "while"};
std::ranges::any_of(keywords, [&tok](const char* kw) { return tok == kw; }))
{
std::cerr << "Token must not be a keyword\n";
}
}
4. shared_mutex
C++11的std::mutex
是独占的(即不区分读写锁),通过lock
或try_lock
去占取mutex。C++17的shared_mutex
提供更细的控制,可以被lock
或lock_shared
加锁,前者独占(write)后者共享(read)。
std::shared_mutex mutex_;
std::unique_lock lock(mutex_);// write mode
std::shared_lock lock(mutex_);// read mode
5. string_view
The class template basic_string_view describes an object that can refer to a constant contiguous sequence of CharT with the first element of the sequence at position zero. A typical implementation holds only two members: a pointer to constant CharT and a size.
std::string_view
对字符串不具有所有权,成员对象有字符串指针地址和字符串size。其成员函数substr
等是在做指针偏移,不涉及字符串拷贝,所以效率更高。
// clang++ -o string_view -std=c++17 string_view.cc
// g++ -o string_view -std=gnu++17 string_view.cc
#include <iostream>
#include <string>
#include <string_view>
#include <cstdio>
int main()
{
constexpr std::string_view unicode[] { "▀▄─", "▄▀─", "▀─▄", "▄─▀" };
for (int y{}, p{}; y != 6; ++y, p = ((p + 1) % 4))
{
for (int x{}; x != 16; ++x)
std::cout << unicode[p];
std::cout << '\n';
}
// substr
const std::string org_str("123456abcdef");
const std::string_view view_str(org_str);
const std::string_view sub_str = view_str.substr(/*pos*/4, /*len*/4);
printf("org str addr:%p, str size:%ld\n", (void*)org_str.data(), org_str.size());
printf("view str addr:%p, str size:%ld\n", (void*)view_str.data(), view_str.size());
printf("sub str addr:%p, str size:%ld\n", (void*)sub_str.data(), sub_str.size());
return 0;
}
output:
...
org str addr:0x7fffffffe280, str size:12
view str addr:0x7fffffffe280, str size:12
sub str addr:0x7fffffffe284, str size:4
6. std::any
类似 void *
的含义,但any
会记录类型,在std::any_cast
时做类型检查,在对象释放时根据类型进行析构。
case study:
#include <any>
#include <iostream>
int main()
{
std::cout << std::boolalpha;
// any type
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
// bad cast
try
{
a = 1;
std::cout << std::any_cast<float>(a) << '\n';
}
catch (const std::bad_any_cast& e)
{
std::cout << "cast int to float: " << e.what() << '\n';
}
// has value
a = 2;
if (a.has_value())
std::cout << a.type().name() << ": has value " << std::any_cast<int>(a) << '\n';
// reset
a.reset();
if (!a.has_value())
std::cout << "no value\n";
// pointer to contained data
a = 3;
int* i = std::any_cast<int>(&a);
std::cout << *i << '\n';
}
output:
i: 1
d: 3.14
b: true
cast int to float: bad any_cast
i: has value 2
no value
3
7. std::optional
The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.
llvm已经把llvm::optional替换成标准库的实现了. 常用于函数返回值类型。
#include <iostream>
#include <optional>
#include <string>
// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b)
{
if (b)
return "Godzilla";
return {};
}
// std::nullopt can be used to create any (empty) std::optional
auto create2(bool b)
{
return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}
int main()
{
auto va = create(false);
if (!va) {
std::cout << "check optional value through bool operator\n";
}
if (!va.has_value()) {
std::cout << "check optional value through has_value member func\n";
}
std::cout << "create(false) returned "
<< create(false).value_or("empty") << '\n';
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create2(true))
std::cout << "create2(true) returned " << *str << '\n';
}
output:
check optional value through bool operator
check optional value through has_value member func
create(false) returned empty
create2(true) returned Godzilla
8. std::variant
The class template std::variant represents a type-safe union.
union结构体中的各个类型可选的,std::variant
效果相同,多了一些helper function如holds_alternative
,get
。
但是union是所有成员共用内存,variant似乎是每个成员都独有内存。
#include <cassert>
#include <iostream>
#include <string>
#include <variant>
union UnionTy {
int v0;
float v1;
double v2;
};
int main()
{
UnionTy u;
u.v0 = 123;
std::cout << "size of UnionTy: " << sizeof(u) << "\n";
std::variant<int, float, double> v, w;
std::cout << "size of variant<int, float, double>: " << sizeof(v) << "\n";
v = 42; // v contains int
int i = std::get<int>(v);
assert(42 == i); // succeeds
w = std::get<int>(v);
w = std::get<0>(v); // same effect as the previous line
w = v; // same effect as the previous line
// std::get<double>(v); // error: no double in [int, float]
// std::get<3>(v); // error: valid index values are 0 and 1
try
{
std::get<float>(w); // w contains int, not float: will throw
}
catch (const std::bad_variant_access& ex)
{
std::cout << ex.what() << '\n';
}
using namespace std::literals;
std::variant<std::string> x("abc");
// converting constructors work when unambiguous
x = "def"; // converting assignment also works when unambiguous
std::variant<std::string, void const*> y("abc");
// casts to void const * when passed a char const *
assert(std::holds_alternative<void const*>(y)); // succeeds
y = "xyz"s;// set y contains std::string
assert(std::holds_alternative<std::string>(y)); // succeeds
}
output:
size of UnionTy: 8
size of variant<int, float, double>: 16
std::get: wrong index for variant