C++17 之 "折叠表达式"

本文介绍了C++17中的折叠表达式特性,包括支持的操作符、左折叠与右折叠的区别,以及如何处理空参数包的情况。并通过实例展示了折叠表达式的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++11 提供了可变模板参数包, 使函数可以接受任意数量的参数. 但在 C++11
中展开参数包稍显麻烦, 而 C++17 的折叠表达式使得展开参数包变得容易,
其基本语法是使用 (...) 的语法形式进行展开.

支持的操作符

折叠表达式支持 32 个操作符: +, -, *, /, %, ^, &, |, =, <,
>, <<, >>, +=, -=, *=, /=, %=, ^=, &=, |=, <<=,
>>=,==, !=, <=, >=, &&, ||, ,, .*, ->*.

折叠分类

C++17 的折叠表达式根据参数包的位置分为左折叠和右折叠,
根据操作的对象数量分为一元折叠和二元折叠.

例 1: 左折叠

//
template <typename ... Ts>
auto sum(Ts ... ts)
{
    return (... + ts);
}

//
int the_sum {sum(1, 2, 3, 4, 5)}; // the_sum 的值为 15

//
std::string a {"Hello "};
std::string b {"World"};
std::string the_string {sum(a, b)}; // the_string 的值为 "Hello World"

例 2: 右折叠

//
template <typename ... Ts>
auto sum(Ts ... ts)
{
    return (ts + ...);
}

例 1 中, 参数包 ... 位于操作符的左侧, 故尔称为左折叠. 如果 ...
位于操作符右侧, 则称为右折叠, 如例 2 所示. 就例 1 与例 2 而言,
左折叠与右折叠的效果是相同的. 对于 int the_sum {sum(1, 2, 3, 4, 5)};
左折叠的展开方式为 1 + (2 + (3 + (4 + 5))), 右折叠的展开方式为
(((1 + 2) + 3) + 4) + 5.

在例 1 与 例 2 中, 如果参数包包含的参数数量为 0, 即为空包, 会产生编译错误, 如
int the_sum {sum()};, 大致的错误输出如下

In instantiation of 'auto sum(Ts ...) [with Ts = {}]':
error: fold of empty expansion over operator+
     return (... + ts);

若要解决空参数包的编译错误, 针对例 1, 可以加上一个数值 0,
可以解决编译错误又可以使得语义不变, 这个 0 相当于缺省值. 通过加上一个数值,
折叠就变成了二元折叠, 如例 3 所示.

例 3: 二元折叠

template <typename ... Ts>
auto sum(Ts ... ts)
{
    // 二元右折叠
    return (ts + ... + 0);

    // 二元左折叠
    // return (0 + ... + ts);
}

此时对于 int the_sum {sum(1, 2, 3, 4, 5)};,
折叠的展开方式为 1 + (2 + (3 + (4 + (5 + 0)))).

空参数包

空参数包就是参数包中不含任何参数. 对于大多数操作符, 空参数包将会引发编译错误.
对于 &&||, 空参数包是合法的, 其中 && 的展开结果为 true, ||
的展开结果为 false. 在逗号 , 操作符中, 空参数包也合法, 展开为 void().

其它例子

例 4: 计算指定区间内包含指定数值的个数

template <typename R, typename ... Ts>
auto count(const R& range, Ts ... ts)
{
    return (std::count(std::begin(range), std::end(range), ts) + ...);
}

...

std::vector<int> v {1, 2, 3, 4, 5};
count(v,         2, 5);          // returns 2
count(v,         100, 200);      // returns 0
count("abcdefg", 'x', 'y', 'z'); // returns 0
count("abcdefg", 'a', 'd', 'f'); // returns 3

例 5: 检查插入多个元素是否成功

template <typename T, typename ... Ts>
bool insert_all(T &set, Ts ... ts)
{
    return (set.insert(ts).second && ...);
}

...

std::set<int> my_set {1, 2, 3};
insert_all(my_set, 4, 5, 6); // Returns true, my_set 值为 {1, 2, 3, 4, 5, 6}
insert_all(my_set, 7, 2, 8); // Returns false, my_set 值为 {1, 2, 3, 4, 5, 6, 7}
                             // 插入 2 时出错, 8 不会被插入

例 6: 检查给的数值是否全部在指定范围内

最后

  • 对于一元右折叠 (E op …) 具体展开为 E1 op (… op (EN-1 op EN)).
  • 对于一元左折叠 (… op E) 具体展开为 (( E1 op E2) op …) op En.
  • 对于二元右折叠 (E op … op I) 具体展开为 E1 op (… op (EN-1 op (EN op I))).
  • 对于二元左折叠 (I op … op E) 具体展开为 (((I op E1) op E2) op …) op E2.

左折叠与右折叠的语义并非总是相同的. 比如对于加法和乘法, 左折叠与右折叠的语义是相同的, 但是对于减法与除法, 其语义是不同的.

例 7: 左右折叠不同语义

template<typename... Args>
auto left_sub(Args&&... args) {
    return (... - args);
}

template<typename... Args>
auto right_sub(Args&&... args) {
    return (args - ...);
}

...

auto a = left_sub(2, 3, 4);  // ((2 - ) -3 ) - 4) = -5
auto b = right_sub(2, 3, 4); // (2 - (3 - 4)) = 3
<think>我们正在处理一个关于C++17折叠表达式在分配内存时出现无效(invalid)问题的情况。根据用户提供的引用和问题描述,用户可能在使用折叠表达式进行内存分配时遇到了问题(如语法错误或使用不当)。结合引用[2]提到的多态分配器(polymorphic allocators),我们推测用户可能在使用折叠表达式与内存分配器结合时出现了问题。 首先,我们需要明确折叠表达式(fold expressions)是C++17引入的新特性,用于简化变参模板(variadic templates)的展开操作。折叠表达式可以用于二元操作符,例如:`(args op ... op init)` 或 `(init op ... op args)` 等形式。 用户提到“allocate memory invalid”,可能是指在使用折叠表达式进行内存分配时出现了无效的分配(如空指针、分配失败等)或语法错误。 ### 常见问题分析 1. **语法错误**:折叠表达式的语法可能使用不当。 2. **内存分配失败**:在折叠表达式中使用分配器时,可能没有正确处理分配失败的情况。 3. **与分配器的结合使用**:引用[2]提到了C++17的多态分配器(如`monotonic_buffer_resource`等),用户可能在使用折叠表达式配合这些分配器时出现问题。 ### 折叠表达式基本用法 折叠表达式的基本形式有四种: 1. 一元右折叠:`(pack op ...)` 2. 一元左折叠:`(... op pack)` 3. 二元右折叠:`(pack op ... op init)` 4. 二元左折叠:`(init op ... op pack)` 例如,使用折叠表达式计算参数包的和: ```cpp template<typename... Args> auto sum(Args... args) { return (args + ...); // 一元右折叠:args0 + (args1 + ...) } ``` ### 折叠表达式分配内存可能的问题 假设用户试图使用折叠表达式为多个对象分配内存,并可能使用了多态分配器。例如: ```cpp #include <memory> #include <memory_resource> // 使用折叠表达式分配多个对象 template<typename T, typename... Args> std::pmr::vector<T> allocate_objects(std::pmr::memory_resource* mr, Args&&... args) { // 错误示例:直接使用折叠表达式分配多个对象? // 注意:下面这行代码是错误示例,仅用于说明问题 return { (new T(std::forward<Args>(args)))... }; // 这样会返回一个初始化列表,每个元素都是T*,但无法直接用于构造vector } ``` 上面的代码试图为每个参数使用`new`分配一个对象,并返回一个初始化列表(initializer_list)的指针。但这样返回的是一组指针,而且没有使用分配器,并且存在内存泄漏的风险。 ### 正确使用折叠表达式分配内存 如果用户想要使用多态分配器(如`monotonic_buffer_resource`)来分配多个对象,并放入容器中,可以这样: ```cpp #include <vector> #include <memory_resource> template<typename T, typename... Args> std::pmr::vector<T> allocate_objects(std::pmr::memory_resource* mr, Args&&... args) { std::pmr::vector<T> vec(mr); // 使用折叠表达式和逗号运算符,逐个构造并添加到向量 (vec.emplace_back(std::forward<Args>(args)), ...); return vec; } ``` 这里使用了逗号运算符的折叠表达式,对每个参数调用`emplace_back`,将参数包中的每个参数分别构造一个对象添加到向量中。 ### 可能的问题和解决方案 1. **参数包为空**:当参数包为空时,折叠表达式可能产生未定义行为(对于一元折叠)或编译错误(对于二元折叠)。因此,需要确保参数包非空,或者处理空包的情况。 2. **分配失败**:多态分配器可能会抛出`std::bad_alloc`异常(如`new_delete_resource`)或根据分配器的行为而不同。用户需要处理异常。 3. **与分配器结合使用**:在构造对象时,要确保使用分配器分配内存。上面的例子中,`std::pmr::vector`内部使用分配器,而`emplace_back`使用该分配器分配内存。 ### 示例:使用折叠表达式和分配器创建对象数组 ```cpp #include <iostream> #include <memory_resource> #include <vector> int main() { char buffer[1024] = {}; // 栈上缓冲区 std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; auto vec = allocate_objects<int>(&pool, 1, 2, 3, 4, 5); for (int x : vec) { std::cout << x << ' '; } std::cout << '\n'; } ``` ### 错误情况:无效的内存分配 如果用户在使用折叠表达式时直接使用`new`,而没有考虑分配器,那么分配的内存可能不在分配器的控制之下,从而导致多态分配器的行为不一致。此外,如果分配器是`null_memory_resource`,那么调用`allocate`会抛出`std::bad_alloc`[^2]。 ### 总结建议 1. **检查折叠表达式语法**:确保折叠表达式的使用符合C++17标准。 2. **使用容器管理内存**:避免直接使用`new`,而是使用容器(如`std::pmr::vector`)和其`emplace_back`方法,这样可以利用分配器。 3. **处理异常**:当使用分配器时,注意捕获`std::bad_alloc`异常。 4. **空参数包处理**:如果参数包可能为空,使用编译时条件判断,例如: ```cpp if constexpr (sizeof...(args) > 0) { (vec.emplace_back(std::forward<Args>(args)), ...); } ``` 如果用户的问题是由于上述原因导致的,按照上述建议修改代码应该可以解决。 ### 相关问题 §§相关问题§§ 1. 如何在C++17折叠表达式中正确处理空参数包? 2. C++17多态分配器(如`monotonic_buffer_resource`)的工作原理是什么? 3. 在折叠表达式中如何捕获并处理异常(如`std::bad_alloc`)? 4. 如何将折叠表达式与智能指针结合使用来管理内存?</think>在 C++17 中使用折叠表达式进行内存分配时遇到无效分配问题,通常是由语法错误、类型不匹配或资源管理不当引起的。以下是常见原因和解决方案: ### 一、常见问题原因分析 1. **折叠表达式语法错误** ```cpp // 错误示例:缺少操作符 template<typename... Args> void allocate(Args... args) { auto ptr = (new args)...; // 语法错误!缺少操作符 } ``` 2. **类型不匹配** ```cpp // 错误示例:尝试分配不同类型对象 auto resources = (new int(10), new double(3.14)); // 类型不匹配 ``` 3. **内存资源未正确初始化**(参考引用[2]) ```cpp std::pmr::monotonic_buffer_resource pool; // 错误:未将资源传递给分配器 auto p1 = new int(42); // 未使用多态分配器 ``` 4. **控制流错误**(参考引用[3]) ```cpp // 错误:缺少条件控制流 template<typename... Args> void allocate(Args... args) { (new args, ...); // 无条件分配所有参数 } ``` ### 二、正确解决方案 #### 1. 基础折叠表达式分配 ```cpp #include <memory> template<typename T, typename... Args> auto make_shared_fold(Args&&... args) { // 正确使用折叠表达式初始化 return std::make_shared<T>((args)...); } // 使用示例 auto ptr = make_shared_fold<std::string>(5, 'A'); // 创建 "AAAAA" ``` #### 2. 使用多态分配器(参考引用[2]) ```cpp #include <memory_resource> void polymorphic_allocation() { char buffer[1024]; // 栈缓冲区 std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; // 使用折叠表达式分配多个对象 auto allocator = std::pmr::polymorphic_allocator<>(&pool); auto p1 = allocator.new_object<int>(42); auto p2 = allocator.new_object<double>(3.14); // 确保在pool生命周期内使用对象 std::cout << *p1 << ", " << *p2 << std::endl; } // 自动释放所有内存[^2] ``` #### 3. 安全控制流实现(参考引用[3]) ```cpp template<typename... Args> void conditional_allocation(Args... args) { // 带条件的折叠表达式 ((std::is_constructible_v<std::string, Args> ? (std::cout << "Allocating: " << args << "\n", new std::string(args)) : nullptr), ...); } // 使用示例 conditional_allocation("Text", 123); // 只分配字符串类型 ``` ### 三、关键注意事项 1. **生命周期管理** - 使用 `std::unique_ptr` 或 `std::shared_ptr` 包装裸指针 ```cpp auto resources = std::make_tuple( std::unique_ptr<int>(new int(10)), std::unique_ptr<double>(new double(3.14)) ); ``` 2. **多态分配器选择**(参考引用[2]) | 分配器类型 | 适用场景 | |-------------------------------|-------------------------| | `synchronized_pool_resource` | 多线程环境 | | `monotonic_buffer_resource` | 短期对象/高性能场景 | | `null_memory_resource()` | 强制失败测试 | 3. **折叠表达式操作符** - 逗号操作符 `(expr, ...)` - 逻辑与 `(condition && ...)` - 三元运算符 `(condition ? expr : void())...` ### 四、错误排查指南 ```mermaid graph TD A[分配无效问题] --> B{检查语法} B -->|正确| C{类型匹配} B -->|错误| H[修正操作符] C -->|匹配| D{资源管理} C -->|不匹配| I[统一类型] D -->|智能指针| E[成功] D -->|裸指针| F{生命周期检查} F -->|正确| E F -->|错误| G[使用RAII包装] ``` > **重要提示**:在多线程环境中优先使用 `synchronized_pool_resource` 避免竞争条件,其内部使用互斥锁保证线程安全[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值