C++17 中那些值得关注的特性

总的来说C++17相比C++11的新特性来说新特性不算多,做了一些小幅改进。C++17增加了数十项新特性,值得关注的特性大概有下面这些:

  • constexpr if

  • constexpr lambda

  • fold expression

  • void_t

  • structured binding

  • std::apply, std::invoke

  • string_view

  • parallel STL

  • inline variable

剩下的有一些来自于boost库,比如variant,any、optional和filesystem等特性,string_view其实在boost里也有。还有一些是语法糖,比如if init、deduction guide、guaranteed copy Elision、template、nested namespace、single param static_assert等特性。我接下来会介绍C++17主要的一些特性,介绍它们的基本用法和作用,让读者对C++17的新特性有一个基本的了解。

fold expression

C++11增加了一个新特性可变模版参数(variadic template),它可以接受任意个模版参数在参数包中,参数包是三个点…,它不能直接展开,需要通过一些特殊的方法才能展开,导致在使用的时候有点难度。现在C++17解决了这个问题,让参数包的展开变得容易了,Fold expression就是方便展开参数包的。

fold expression的语义

fold expression有4种语义:

  • unary right fold (pack op …)

  • unary left fold (… op pack)

  • binary right fold (pack op … op init)

  • binary left fold (init op … op pack)

其中pack代表变参,比如args,op代表操作符,fold expression支持32种操作符:

引用


1

+ - * / % ^ & | = > += -= *= /= %= ^= &= |= >= == != = && || , .* ->*


unary right fold的含义

fold (E op …) 意味着 E1 op (… op (EN-1 op EN)).

顾名思义,从右边开始fold,看它是left fold还是right fold我们可以根据参数包…所在的位置来判断,当参数包…在操作符右边的时候就是right fold,在左边的时候就是left fold。我们来看一个具体的例子:

1

2

3

4

5

6

template<typename... Args>

auto add_val(Args&&... args) {

    return (args +  ...);

}

auto t = add_val(1,2,3,4); //10

right fold的过程是这样的:(1+(2+(3+4))),从右边开始fold。

unary left fold的含义

fold (… op E) 意味着 ((E1 op E2) op …) op EN。

对于+这种满足交换律的操作符来说left fold和right fold是一样的,比如上面的例子你也可以写成left fold。

1

2

3

4

5

6

template<typename... Args>

auto add_val(Args&&... args) {

    return (... + args);

}

auto t = add_val(1,2,3,4); //10

对于不满足交换律的操作符来说就要注意了,比如减法。

1

2

3

4

5

6

7

8

9

10

11

12

13

template<typename... Args>

auto sub_val_right(Args&&... args) {

    return (args - ...);

}

template<typename... Args>

auto sub_val_left(Args&&... args) {

    return (... - args);

}

auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3

auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5

这次right fold和left fold的结果就不一样。

binary fold的含义

Binary right fold (E op … op I) 意味着 E1 op (… op (EN-1 op (EN op I)))。

Binary left fold (I op … op E) 意味着 (((I op E1) op E2) op …) op E2。

其中E代表变参,比如args,op代表操作符,I代表一个初始变量。

二元fold的语义和一元fold的语义是相同的,看一个二元操作符的例子:

1

2

3

4

5

6

7

8

9

10

11

12

template<typename... Args>

auto sub_one_left(Args&&... args) {

    return (1 - ... - args);

}

template<typename... Args>

auto sub_one_right(Args&&... args) {

    return (args - ... - 1);

}

auto t = sub_one_left(234);// (((1-2)-3)-4) = -8

auto t1 = sub_one_right(234);//(2-(3-(4-1))) = 2

相信通过这个例子大家应该对C++17的fold expression有了基本的了解。

comma fold

在C++17之前,我们经常使用逗号表达式和std::initializer_list来将变参一个个传入一个函数。比如像下面这个例子:

1

2

3

4

5

6

7

8

9

10

11

12

template<typename T>

void print_arg(T t)

{

    std::cout << t << std::endl;

}

template<typename... Args>

void print2(Args... args)

{

    //int a[] = { (printarg(args), 0)... };

    std::initializer_list<int>{(print_arg(args), 0)...};

}

这种写法比较繁琐,用fold expression就会变得很简单了。

1

2

3

4

5

template<typename... Args>

void print3(Args... args)

{

    (print_arg(args), ...);

}

这是right fold,你也可以写成left fold,对于comma来说两种写法是一样的,参数都是从左至右传入print_arg函数。

1

2

3

4

5

template<typename... Args>

void print3(Args... args)

{

    (..., print_arg(args));

}

你也可以通过binary fold这样写:

1

2

3

4

template<typename ...Args>

void printer(Args&&... args) {

    (std::cout << ... << args) << '\n';

}

也许你会觉得能写成这样:

1

2

3

4

template<typename ...Args>

void printer(Args&&... args) {

    (std::cout << args << ...) << '\n';

}

但这样写是不合法的,根据binary fold的语法,参数包…必须在操作符中间,因此上面的这种写法不符合语法要求。

借助comma fold我们可以简化代码,假如我们希望实现tuple的for_each算法,像这样:

1

for_each(std::make_tuple(2.5, 10, 'a'),[](auto e) { std::cout << e<< '\n'; });

这个for_each将会遍历tuple的元素并打印出来。在C++17之前我们如果要实现这个算法的话,需要借助逗号表达式和std::initializer_list来实现,类似于这样:

1

2

3

4

template <typename... Args, typename Func, std::size_t... Idx>

void for_each(const std::tuple& t, Func&& f, std::index_sequence<Idx...>) {

    (void)std::initializer_list<int> { (f(std::get<Idx>(t)), void(), 0)...};

}

这样写比较繁琐不直观,现在借助fold expression我们可以简化代码了。

1

2

3

4

template <typename... Args, typename Func, std::size_t... Idx>

void for_each(const std::tuple<Args...>& t, Func&& f, std::index_sequence<Idx...>) {

    (f(std::get<Idx>(t)), ...);

}

借助coma fold我们可以写很简洁的代码了。


猜你喜欢

转载自blog.51cto.com/13959004/2285268