c++17特性

C++17特性

类模板的模板参数推导

类模板通过构造函数的参数类型自动推导模板类型参数,而无需显式指定模板参数

#include <iostream>

template <typename T>
class MyClass {
public:
    T value;
    MyClass(T val) : value(val) {}
};

int main() {
    MyClass obj(10);  // 编译器推导 T 为 int
    std::cout << obj.value << std::endl;  // 输出: 10
}

使用 auto 声明非类型模板参数

非类型模板参数是模板参数中的一个特殊类型,它并不是类型(如 intdouble),而是一个常量值或常量表达式。这些值可以是整数、指针、引用、甚至是用户定义的类型实例

template <auto N>

auto N 会被推导为传入的值的类型,,如果传入 42N 的类型会推导为 int;如果传入 "hello"N 会推导为 const char*

示例

#include <iostream>
#include <string>

template <auto N>
void print() {
    std::cout << N << std::endl;
}

int main() {
    print<42>();         // 自动推导 N 为 int
    print<3.14>();       // 自动推导 N 为 double
    print<'A'>();        // 自动推导 N 为 char
    print<std::string("Hello")>();  // 自动推导 N 为 const char*
}
  • 自动类型推导:通过 auto,模板可以根据传入的值推导出参数的类型。
  • 非类型参数的值:可以传递常量值、字符串字面量、甚至是常量表达式。
  • 类型限制:非类型模板参数的类型推导受限于 C++ 标准,必须是常量或可以推导为常量的类型。

折叠表达式

折叠表达式的形式可以分为以下几种(其中 op 表示 C++ 中的二元运算符,如 +, -, *, /, &&, || 等):

  1. 一元折叠(Unary fold)
    • 左折叠(... op pack)
    • 右折叠(pack op ...)
  2. 二元折叠(Binary fold)
    • 左折叠(init op ... op pack)
    • 右折叠(pack op ... op init)

其中,参数包 pack 在展开时会将所有参数依次用运算符连接。例如,如果 pack(a, b, c, d),运算符为 +,那么:

  • (... + pack) 展开后相当于:(((a + b) + c) + d)
  • (pack + ...) 展开后相当于:(a + (b + (c + d)))
  • (init + ... + pack) 展开后相当于:(((init + a) + b) + c) + d
  • (pack + ... + init) 展开后相当于:(a + (b + (c + (d + init))))

[!WARNING]

⚠️ 对于大多数交换律成立的运算符(如 +, *),左折叠或右折叠结果相同;但如果运算符不满足结合律或交换律(如减法 -、除法 /、逗号运算符 , 等),就需要根据实际需要来选择左折叠还是右折叠

示例

求和

传统写法

#include <iostream>
#include <initializer_list>

template <typename... Args>
auto sum(Args... args) {
    // 利用 std::initializer_list 的构造来展开参数包
    auto list = {args...};
    int total = 0;
    for (auto v : list) {
        total += v;
    }
    return total;
}

int main() {
    std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出:15
    return 0;
}

使用折叠表达式

#include <iostream>

template <typename... Args>
auto fold_sum(Args... args) {
    // 使用一元左折叠 (... + args)
    return (args + ...);
}

int main() {
    std::cout << fold_sum(1, 2, 3, 4, 5) << std::endl; // 输出:15
    return 0;
}

(args + ...) 在参数包 (1, 2, 3, 4, 5) 上展开时,相当于 1 + (2 + (3 + (4 + 5)))

带初始值的折叠

例如:给一个字符串前面加上前缀,或者在数值计算中加一个初始值

#include <iostream>

template <typename... Args>
auto fold_sum_init(int init, Args... args) {
    // 左折叠: (init + ... + args)
    return (init + ... + args);
}

int main() {
    std::cout << fold_sum_init(100, 1, 2, 3, 4, 5) << std::endl;
    // 相当于: (((((100 + 1) + 2) + 3) + 4) + 5) = 115
    return 0;
}

逻辑折叠

折叠表达式也常用于对多个布尔值进行逻辑运算。比如,想判断一组条件是否全部为真(逻辑与)或只要有一个条件为真(逻辑或)

#include <iostream>

template <typename... Bools>
bool all_true(Bools... bools) {
    // 左折叠写法:(... && bools)
    return (... && bools);
}

template <typename... Bools>
bool any_true(Bools... bools) {
    // 左折叠写法:(... || bools)
    return (... || bools);
}

int main() {
    std::cout << std::boolalpha;
    std::cout << "all_true(true, true, false): " << all_true(true, true, false) << std::endl;
    std::cout << "any_true(true, true, false): " << any_true(true, true, false) << std::endl;

    // 输出:
    // all_true(true, true, false): false
    // any_true(true, true, false): true
    return 0;
}

  • (... && bools) 相当于 true && (true && false)

  • (... || bools) 相当于 true || (true || false)

其他运算符折叠

所有 C++ 中的二元运算符都可以与折叠表达式配合使用,包括逗号运算符、位运算符、比较运算符等

#include <iostream>

template <typename... Args>
void print_all(Args... args) {
    // 逗号运算符折叠: (... , (std::cout << args << " "))
    // 可以理解为:((std::cout << args1 << " "), (std::cout << args2 << " "), ...)
    ((std::cout << args << " "), ...);
    std::cout << std::endl;
}

int main() {
    print_all(1, 2.5, "hello", 'c');
    // 输出:1 2.5 hello c 
    return 0;
}

((std::cout << args << " "), ...) 是一元右折叠,按照参数包的顺序依次输出元素

注意事项

  1. 折叠方向
    • 结合律不成立时,要谨慎选择使用左折叠还是右折叠。
    • 对于运算符如 -/ 等,左折叠和右折叠的计算结果往往不同
  2. 空参数包
    • 如果参数包展开时为空,折叠表达式会变得无效。例如 (args + ...) 在空参数包下没有定义意义,可能会导致编译错误。
    • 如果需要处理空包,可以使用带初始值的二元折叠 (init + ... + args),这样当包为空时,表达式就退化为 init
  3. 合法的二元运算符
    • 并非所有的符号都能用作折叠表达式中的 op,必须是 C++ 中定义了的二元运算符,如 +, -, *, /, %, ==, !=, &&, ||, &, |, ^, <<, >>, , 等。
#include <iostream>

// 1. 不带初始值的折叠求和
template <typename... Args>
auto fold_sum(Args... args) {
    return (args + ...);
}

// 2. 带初始值的折叠求和
template <typename... Args>
auto fold_sum_init(int init, Args... args) {
    return (init + ... + args);
}

// 3. 逻辑折叠
template <typename... Bools>
bool all_true(Bools... bools) {
    return (... && bools);
}

template <typename... Bools>
bool any_true(Bools... bools) {
    return (... || bools);
}

// 4. 逗号运算符折叠
template <typename... Args>
void print_all(Args... args) {
    ((std::cout << args << " "), ...);
    std::cout << std::endl;
}

int main() {
    std::cout << "fold_sum(1,2,3,4,5): " << fold_sum(1,2,3,4,5) << std::endl;
    std::cout << "fold_sum_init(100,1,2,3,4,5): " << fold_sum_init(100,1,2,3,4,5) << std::endl;
    
    std::cout << std::boolalpha;
    std::cout << "all_true(true,true,false): " << all_true(true, true, false) << std::endl;
    std::cout << "any_true(true,true,false): " << any_true(true, true, false) << std::endl;
    
    print_all(1, 2.5, "hello", 'c');

    return 0;
}

constexpr lambda

允许在编译时执行lambda表达式

捕获的变量必须是constexpr或常量表达式

注意

  • Lambda的捕获列表必须为空([])。
  • Lambda内部的代码只能调用其他constexpr函数或操作。
  • Lambda返回值和所有逻辑都必须满足constexpr的要求。
constexpr auto lambda_name = [](/* 参数列表 */) constexpr -> 返回类型 {
    // constexpr 函数体
};

示例

  1. 基础用法

    #include <iostream>
    
    constexpr auto square = [](int x) constexpr { return x * x; };
    
    constexpr int result = square(5); // 编译期计算
    
    int main() {
        std::cout << "Square of 5 is " << result << '\n'; // 输出 25
        return 0;
    }
    
  2. 使用constexpr lambda实现简单的编译期逻辑

#include <iostream>

// constexpr lambda for factorial
constexpr auto factorial = [](int n) constexpr {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
};

constexpr int fact_5 = factorial(5); // 编译期计算

int main() {
    std::cout << "Factorial of 5 is " << fact_5 << '\n'; // 输出 120
    return 0;
}
  1. 配合std::array进行编译期数组初始化

    #include <array>
    #include <iostream>
    
    constexpr auto generate_fibonacci = [](size_t n) constexpr {
        std::array<int, 10> fib = {0, 1};
        for (size_t i = 2; i < n; ++i) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib;
    };
    
    constexpr auto fib_array = generate_fibonacci(10);
    
    int main() {
        for (const auto& num : fib_array) {
            std::cout << num << " "; // 输出 0 1 1 2 3 5 8 13 21 34
        }
        std::cout << '\n';
        return 0;
    }
    
  2. C++20结合模板与编译期推导

    #include <iostream>
    #include <array>
    
    template <typename Lambda>
    constexpr auto apply_to_array(const std::array<int, 5>& arr, Lambda lambda) {
        std::array<int, 5> result{};
        for (size_t i = 0; i < arr.size(); ++i) {
            result[i] = lambda(arr[i]);
        }
        return result;
    }
    
    constexpr auto square = [](int x) constexpr { return x * x; };
    
    constexpr auto input_array = std::array<int, 5>{1, 2, 3, 4, 5};
    constexpr auto squared_array = apply_to_array(input_array, square);
    
    int main() {
        for (const auto& val : squared_array) {
            std::cout << val << " "; // 输出 1 4 9 16 25
        }
        std::cout << '\n';
        return 0;
    }
    

Lambda按值捕获this指针

在c++11,Lambda捕获this是引用捕获,c++17添加了*this的按值捕获

struct MyObj {
  int value {123};
  auto getValueCopy() {
    return [*this] { return value; };
  }
  auto getValueRef() {
    return [this] { return value; };
  }
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321

内联变量

inline描述符可以用于变量与函数

在C++17之前,定义全局或静态变量,需要使用extern进行声明,并且在一个源文件中进行定义,而使用inline可以在头文件中进行定义,被多个源文件进行包含也不会导致重复定义

示例

  1. 定义全局变量

    common.h

    inline int a = 10;
    

    test.cpp

    #include "common.h"
    void test(){
        a += 10;
    }
    

    main.cpp

    #include <iostream>
    #include "common.h"
    
    extern void test();
    
    int main() {
        a = 10;
        test();
        std::cout << "Value: " << a << std::endl;
        return 0;
    }
    
  2. 类静态变量

    可以直接在声明类内定义一个静态成员变量,而不需要在源文件中进行初始化

    #include <iostream>
    
    class MyClass {
    public:
        // 内联静态成员变量
        inline static int staticValue = 10;
    };
    
    int main() {
        std::cout << "Static Value: " << MyClass::staticValue << std::endl;
    
        // 修改静态变量
        MyClass::staticValue = 20;
        std::cout << "Updated Value: " << MyClass::staticValue << std::endl;
    
        return 0;
    }
    

结构化绑定

解构元组类对象:std::tuplestd::pairstd::array

示例

  1. 解构元组

    #include <iostream>
    #include <tuple>
    
    int main() {
        std::tuple<int, double, std::string> data = {42, 3.14, "C++17"};
    
        // 使用结构化绑定
        auto [id, value, text] = data;
    
        std::cout << "ID: " << id << ", Value: " << value << ", Text: " << text << std::endl;
        return 0;
    }
    
  2. 解构std::pair

    #include <iostream>
    #include <utility>
    
    int main() {
        std::pair<int, std::string> pairData = {1, "Hello"};
    
        // 解构 pair
        auto [key, value] = pairData;
    
        std::cout << "Key: " << key << ", Value: " << value << std::endl;
        return 0;
    }
    
    
  3. 解构数组

    #include <iostream>
    
    int main() {
        int arr[] = {10, 20, 30};
    
        // 解构数组
        auto [a, b, c] = arr;
    
        std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
        return 0;
    }
    
  4. 解构结构体

    结构体必须是聚合类型,或者实现了 std::tuple_sizestd::tuple_element,默认是按值捕获方式,可以使用auto&auto&&更改捕获方式

    #include <iostream>
    #include <string>
    
    struct Person {
        std::string name;
        int age;
        double height;
    };
    
    int main() {
        Person p{"Alice", 30, 5.5};
    
        // 解构结构体
        auto [name, age, height] = p;
    
        std::cout << "Name: " << name << ", Age: " << age << ", Height: " << height << std::endl;
        return 0;
    }
    

constexpr if

在编译时根据条件选择性地包含或排除代码,

if constexpr (condition) {
    // 当 condition 为 true 时,编译器会包含这段代码
} else {
    // 当 condition 为 false 时,编译器会包含这段代码
}
  • condition:必须是一个编译时可以确定的布尔表达式。
  • if constexpr:与普通 if 的区别是,编译器在编译时就会评估 condition 并选择代码路径,而不是在运行时判断。

示例

#include <iostream>

template <typename T>
void printType(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral type: " << value << std::endl;
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Floating-point type: " << value << std::endl;
    } else {
        std::cout << "Other type" << std::endl;
    }
}

int main() {
    printType(42);         // 输出: Integral type: 42
    printType(3.14);       // 输出: Floating-point type: 3.14
    printType("C++17");    // 输出: Other type
    return 0;
}

std::optional

std::any

std::string_view

std::invoke

std::apply

std::filesystem

posted @ 2025-04-18 11:10  Midraos  阅读(79)  评论(0)    收藏  举报