C++17新特性

C++17新特性小记

C++17新特性 - 知乎 (zhihu.com)

C++17 - cppreference.com

1. 类模板实参推导 (CTAD)

为了实例化一个类模板,需要知晓但不需要指定每个模板实参。编译器会从初始化器的类型推导缺失的模板实参。

template
struct A {
    T t;
    U u;
    A(T _t, U _u) : t(_t), u(_u) {}
};

int main(int argc, char** argv) {
    std::pair p(2, 4.5);    // td::pair p(2, 4.5);
    A a(3.14159265, "hello world"); // double, string
    std::cout << a.t << ", " << a.u << std::endl;
    // 3.14159, hello world
    return 0;
}

2. 结构化绑定声明

绑定指定名称到初始化器的子对象或元素。

类似引用,结构化绑定是既存对象的别名。不同于引用的是,结构化绑定的类型不必为引用类型。

// 属性(可选) cv-auto 引用运算符(可选) [ 标识符列表 ] = 表达式 ;	(1)	
// 属性(可选) cv-auto 引用运算符(可选) [ 标识符列表 ] { 表达式 } ;	(2)	
// 属性(可选) cv-auto 引用运算符(可选) [ 标识符列表 ] ( 表达式 ) ;	(3)	

情况 1:绑定数组

int a[2] = {3, 6};

// creates e[2], copies a into e, 
// then x refers to e[0], y refers to e[1]
auto [x, y] = a;
// xr refers to a[0], yr refers to a[1]
auto& [xr, yr] = a;

std::cout << x << ", " << y << std::endl;        // 3, 6
std::cout << a[0] << ", " << a[1] << std::endl; // 3, 6
std::cout << xr << ", " << yr << std::endl;     // 3, 6

xr = 999; yr = -99;
std::cout << a[0] << ", " << a[1] << std::endl; // 999, -99
std::cout << xr << ", " << yr << std::endl;     // 999, -99

情况 2:绑定元组式类型

float x = 3.1415f;
char  y = 'w';
int   z = 996;
    
std::tuple tpl(x, std::move(y), z);
const auto& [a, b, c] = tpl;

std::cout << a << ", " << b << ", " << c << std::endl;
// 3.1415, w, 996

情况 3:绑定到数据成员

struct S {
	mutable int x = 2;
	volatile double y;
};
S s;
const auto [x, y] = s;
std::cout << x << ", " << y << std::endl; 
std::cout << s.x << ", " << s.y << std::endl; 
// 2, 4.63895e-310
// 2, 4.63895e-310

3. 折叠表达式

语法

( 形参包 运算符 ... )	(1)	
( ... 运算符 形参包 )	(2)	
( 形参包 运算符 ... 运算符 初值 )	(3)	
( 初值 运算符 ... 运算符 形参包 )	(4)	
// fold expression (C++17)
#include 

template
bool all(Args... args) {
    // ( ... 运算符 形参包 )
    // 一元左折叠
    return (... && args);
}

template
auto sum(Args... args) {
    // ( 形参包 运算符 ... )
    // 一元右折叠
    return (args + ...);
}

int main(int argc, char** argv) {
    std::cout << all(true, true, true, false) << std::endl; // 0
    std::cout << sum(true, true, true, false) << std::endl; // 3

    std::cout << sum(std::string("hello"), std::string(" "), std::string("world")) << std::endl;
    // hello world
    return 0;
}

4. constexpr lambda表达式

C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。

int main(int argc, char** argv) {
    constexpr auto f = [](int n) { return n * n; };
    static_assert(f(3) == 9, "error");
    return 0;
}

5. namespace 嵌套

namespace A {
    namespace B {
        namespace C {
            void f () {
                std::cout << "hello world" << std::endl;
            }
        }
    }   
}

int main(int argc, char** argv) {
    A::B::C::f();
    return 0;
}
// hello world

6. __has_include

将其他源文件包含到当前源文件中紧随指令之后的一行。

结果是 1 的 __has_include 只表明存在有指定名称的头或源文件。它并不意味着包含该头或源文件时不会导致错误,或它会包含任何有意义的内容。

#if __has_include()
#include 
#define has_iostream 1
#endif

7. if和switch语句初始化

在if和switch语句中加入初始化语句部分,增加代码的简洁性。

if (int a = get_value(); a < 10) {
	std::cout << a << std::endl;
}
// 5

std::string str = "Hello world";

if (auto [pos, size] = std::pair(str.find("Hello"), str.size());
    pos != std::string::npos) {
	std::cout << pos << ", " << size << std::endl; 
}
// 0, 11

8. 内联变量

内联变量消除了将 C++ 代码打包为唯头文件的库的主要障碍。

拥有外部链接的包含于多个源文件的变量必须为 inline

// header file
struct A {
    static const int value;  
};
inline int const A::value = 10;

// ==========或者========
struct A {
    inline static const int value = 10;
}

9. lambda 表达式捕获 *this

当前对象的简单的以复制捕获

struct A {
    int a;
    void fun() {
        // 持有对象的拷贝
        [*this] { std::cout << a << std::endl; } ();
    }
};


int main(int argc, char** argv) {
    A a{314};
    a.fun();
    return 0;
}

10. 新属性[[fallthrough]][[nodiscard]][[maybe_unused]]

C++ 标准仅定义下列属性。

[[noreturn]](C++11) 指示函数不返回
[[carries_dependency]](C++11) 指示释放消费 std::memory_order 中的依赖链传入和传出该函数。
[[deprecated]](C++14)
[[deprecated("原因")]](C++14)
指示允许使用声明有此属性的名称或实体,但因 原因 而不鼓励使用。
[[fallthrough]](C++17) 指示从前一 case 标号直落是有意的,而在发生直落时给出警告的编译器不应该为此诊断。
[[nodiscard]](C++17) 鼓励编译器在返回值被舍弃时发布警告。
[[maybe_unused]](C++17) 压制编译器在未使用实体上的警告,若存在。
#include 

void fun(int x) {
    switch (x) {
        case 0:
            std::cout << "x = 0" << std::endl;
        case 1:
            std::cout << "x = 1" << std::endl;
            [[fallthrough]]; // 直落时不警告
        default: 
            std::cout << "x >= 0" << std::endl;
    }
}

[[nodiscard]] int g(int x) { return x + 6; }

// 提示编译器修饰的内容可能暂时没有使用,避免产生警告
[[maybe_unused]] int h(int x) { return x + 2; }

int main(int argc, char** argv) {
    fun(1);
    g(3); // warning: ignoring return value of ‘int g(int)’
    return 0;
}

11. 标准库头文件

chars_format(C++17) 指定 std::to_chars 和 std::from_chars 所用的格式 (枚举)
函数
from_chars(C++17) 转换字符序列到整数或浮点值 (函数)
to_chars(C++17) 转换整数或浮点值到字符序列 (函数)
#include 
#include 

int main(int argc, char** argv) {
    const std::string pi_str{"314159265"};

    int value = 0;
    std::from_chars(pi_str.data(), pi_str.data() + pi_str.size(), value);

    std::cout << sizeof(pi_str) << ", " << sizeof(value) << std::endl;
    // 32, 4

    std::string val_str = std::string("xxxx");
    const int v = 1234;
    std::to_chars(val_str.data(), val_str.data() + val_str.size(), v);
    
    std::cout << val_str << std::endl; // 1234
    std::cout << sizeof(val_str) << ", " << sizeof(v) << std::endl;
    // 32, 4
}

12. 标准库头文件

std::variant实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等

#include 
#include 
using std::cout;
using std::endl;

struct A { 
    A () = default;
    A(int i){} 
};

int main(int argc, char** argv) {
    std::variant var("hello");
    cout << var.index() << ", " << std::get<1>(var) << endl;
    var = 123;
    cout << var.index() << ", " << std::get<0>(var) << endl;
    
    // variant的第一个类型一般要有对应的构造函数,否则编译失败
    std::variant var_tmp;
    return 0;
}
// 1, hello
// 0, 123

可以使用std::monostate来打个桩,模拟一个空状态。

monostate用作非可默认构造类型的 variant 的首个可选项的占位符类型

13. 标准库头文件

std::optional可用于正常值和异常值的统一返回

#include 
#include 
#include 

using std::cout;
using std::endl;

std::optional my_stoi(const std::string &s) {
    try {
        return std::stoi(s);
    } catch (...) {
        // `nullopt_t` 类型对象 (常量)
        return std::nullopt;
    }
}

int* my_stoi_2(const std::string &s) {
    try {
        return &(std::stoi(s));
    } catch (...) {
        return nullptr;
    }
}

int main(int argc, char** argv) {
    std::string s {"xaba"};
    std::optional o = my_stoi(s);
    if (o) {
        cout << *o << endl;
    } else {
        cout << "error" << endl;
    }
    return 0;
}
// error

14. 标准库头文件

可保有任何可复制构造 (CopyConstructible) 类型的实例的对象。

函数
std::swap(std::any)(C++17) 特化 std::swap 算法 (函数)
make_any(C++17) 创建 any 对象 (函数模板)
any_cast(C++17) 对被容纳对象的类型安全访问 (函数模板)
#include 
#include 
using std::cout;
using std::endl;

template
void print_any(const std::any& a) {
    if (a.has_value()) {
        cout << a.type().name() << ", " << std::any_cast(a) << endl;
    }
}

int main(int argc, char** argv) {
    std::any a = 3.14159265;
    print_any(a);    

    a = std::string{"abcdef"};
    print_any(a);  

    return 0;
}
// d, 3.14159
// NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE, abcdef

15. std::apply

定义于头文件

template  constexpr decltype(auto) apply(F&& f, Tuple&& t); 
// f - 要调用的可调用 (Callable) 对象
// t - 以其元素为 f 的参数的元组
#include 
#include 
using std::cout;
using std::endl;

template
T add_fun_template(const T& a, const T& b) { return a + b; }

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

int main(int argc, char** argv) {
    // error : 无法推导函数类型
    // // cout << std::apply(add_fun_template, std::pair(2.0f, 3.0f)) << endl;
    // cout << std::apply(add_fun_template, std::pair(2.0f, 3.0f)) << endl;

    // OK : lambda模板
    cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << endl;
    cout << std::apply(add_lambda, std::pair("2.0f", "3.0f")) << endl;
    
    return 0;
}

直接打印tuple

template
std::ostream& operator<<(std::ostream& os, std::tuple const& theTuple) {
    std::apply (
        [&os](Ts const&... tupleArgs) {
            os << '[';
            std::size_t n{0};
            ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
            os << ']';
        }, theTuple
    );
    return os;
}

// 直接打印tuple
cout << std::tuple(25, "Hello", 9.31f, 'c') << endl;

16. std::make_from_tuple

定义于头文件

template 
constexpr T make_from_tuple(Tuple&& t);
// 注意t是右引用值
#include 
#include 
 
struct Foo {
    Foo(int first, float second, int third) {
        std::cout << first << ", " << second << ", " << third << "\n";
    }
};
 
int main()
{
   auto tuple = std::make_tuple(42, 3.14f, 0);
   std::make_from_tuple(std::move(tuple));
}
// 42, 3.14, 0

17. 标准库头文件

此头文件是字符串库的一部分。

std::string_view来获取一个字符串的视图,字符串视图并不真正的创建或者拷贝字符串,而只是拥有一个字符串的查看功能。std

避免传递一个string对象引发拷贝构造

#include 
#include 
#include 

using std::cout;
using std::endl;

void print_str(std::string_view stv) {
    cout << stv << endl;
}

int main(int argc, char** argv) {
    std::string str = "hello world";
    cout << str << endl;
    print_str(str);
    return 0;
}
// hello world
// hello world

17. std::as_const

定义于头文件

//  将左值引用组成 t 的 const 类型
template 
constexpr std::add_const_t& as_const(T& t) noexcept;

// 删除 const 右值引用重载,以禁止右值参数
template 
void as_const(const T&&) = delete;

可能的实现

template 
constexpr std::add_const_t& as_const(T& t) noexcept
{
    return t;
}
std::string mutableString = "Hello World!";
auto&& constRef = std::as_const(mutableString);

你可能感兴趣的:(工作,c++,开发语言,后端)