Bootstrap

std::ranges::remove, std::ranges::remove_if std::ranges::remove_copy, std::ranges::remove_copy_if

std::ranges::remove

 C++20 引入的算法,用于从范围中移除满足特定条件的元素。它是标准库算法 std::remove 的范围化版本,结合了范围库的便利性。以下是详细说明和示例:


功能

  • 作用:将范围中不满足条件的元素移动到范围前部,并返回新的逻辑结尾迭代器。
  • 特点
    • 不真正删除元素,仅通过移动元素实现“逻辑移除”。
    • 实际需配合容器的 erase 方法完成物理删除。
  • 复杂度:线性复杂度 O(n),n 为范围大小。

原型

template< std::permutable I, std::sentinel_for<I> S,

          class T, class Proj = std::identity >
requires std::indirect_binary_predicate
             <ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr ranges::subrange<I>

    remove( I first, S last, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< std::permutable I, std::sentinel_for<I> S,

          class Proj = std::identity,
          class T = std::projected_value_t<I, Proj> >
requires std::indirect_binary_predicate
             <ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr ranges::subrange<I>

    remove( I first, S last, const T& value, Proj proj = {} );
(since C++26)
(2)
template< ranges::forward_range R,

          class T, class Proj = std::identity >
requires std::permutable<ranges::iterator_t<R>> &&
         std::indirect_binary_predicate
             <ranges::equal_to,
              std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr ranges::borrowed_subrange_t<R>

    remove( R&& r, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< ranges::forward_range R,

          class Proj = std::identity,
          class T = std::projected_value_t<ranges::iterator_t<R>, Proj> >
requires std::permutable<ranges::iterator_t<R>> &&
         std::indirect_binary_predicate
             <ranges::equal_to,
              std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr ranges::borrowed_subrange_t<R>

    remove( R&& r, const T& value, Proj proj = {} );

参数

  • first, last:要处理的范围迭代器。
  • r:要处理的范围(可直接传递容器)。
  • value:要移除的元素值。

返回值

  • 返回 subrange,表示移除后的新逻辑范围(从原开始到新结尾)。

示例 1

#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

int main() {
    std::vector<int> vec{1, 2, 3, 2, 5, 2, 6};

    // 使用 ranges::remove 移除所有值为 2 的元素
    auto [new_begin, new_end] = std::ranges::remove(vec, 2);
    
    // 物理删除
    vec.erase(new_begin, new_end);

    // 输出结果
    for (int num : vec) {
        std::cout << num << " "; // 输出:1 3 5 6
    }
}

说明

  1. 逻辑移除ranges::remove 将非 2 的元素移动到前面,返回新逻辑结尾。
  2. 物理删除:通过 vec.erase() 截断容器,实际删除多余元素。

应用场景

  • 需要从容器中移除特定元素时。
  • 结合范围库简化代码。

注意:若需自定义移除条件,可使用 ranges::remove_if

std::ranges::remove_if

template< std::permutable I, std::sentinel_for<I> S,

          class Proj = std::identity,
          std::indirect_unary_predicate<std::projected<I, Proj>> Pred >
constexpr ranges::subrange<I>

    remove_if( I first, S last, Pred pred, Proj proj = {} );
(3)(since C++20)
template< ranges::forward_range R,

          class Proj = std::identity,
          std::indirect_unary_predicate
              <std::projected<ranges::iterator_t<R>, Proj>> Pred >
requires std::permutable<ranges::iterator_t<R>>
constexpr ranges::borrowed_subrange_t<R>

    remove_if( R&& r, Pred pred, Proj proj = {} );

C++20 引入的算法,用于从范围中移除满足特定条件的元素。它是 std::remove_if 的范围化版本,结合了范围库的简洁性,允许直接操作容器或视图。以下是详细说明和示例:


功能

  • 作用:将范围中不满足条件的元素移动到范围前部,返回新的逻辑结尾迭代器。
  • 特点
    • 不实际删除元素,仅通过移动元素实现“逻辑移除”。
    • 需配合容器的 erase 方法完成物理删除。
  • 复杂度:线性复杂度 O(n),n 为范围大小。

参数

  • first, last 或 r:要处理的范围(迭代器对或直接传递容器)。
  • pred谓词函数,返回 true 的元素会被标记为“待移除”。
  • proj(可选):投影函数,用于在应用 pred 前对元素进行转换。

返回值

  • 返回 subrange,表示移除后的新逻辑范围(从原开始到新结尾)。

示例 1 移除所有偶数

#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

int main() {
    std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8};

    // 使用 ranges::remove_if 移除所有偶数
    auto [new_begin, new_end] = std::ranges::remove_if(vec, 
        [](int x) { return x % 2 == 0; } // 谓词:判断是否为偶数
    );
    
    // 物理删除
    vec.erase(new_begin, new_end);

    // 输出结果
    for (int num : vec) {
        std::cout << num << " "; // 输出:1 3 5 7
    }
}

输出:

1 3 5 7

示例 2 投影的使用

投影 (proj) 对元素的某个属性进行条件判断(如结构体的字段)。

#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

// 定义结构体
struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people = {
        {"Alice", 30},
        {"Bob", 15},
        {"Charlie", 20},
        {"David", 17},
        {"Eve", 19}
    };

    // 使用 ranges::remove_if + 投影
    auto result = std::ranges::remove_if(people,
        [](int age) { 
            return age < 18;          // 谓词:判断年龄是否小于 18
        },
        &Person::age                   // 投影:将 Person 映射到其 age 字段
    );

    // 物理删除
    people.erase(result.begin(), result.end());

    // 输出结果
    for (const auto& person : people) {
        std::cout << person.name << " (" << person.age << ")\n";
    }
}

输出

Alice (30) Charlie (20) Eve (19)

关键解释

  1. 投影的作用

    • &Person::age 将每个 Person 对象映射到其 age 字段。
    • 谓词 [](int age) { ... } 实际接收的是投影后的 age 值,而非整个 Person 对象。
  2. 执行流程

    • remove_if 遍历 people 中的每个 Person 对象。
    • 对每个对象,先应用投影(提取 age),再应用谓词判断 age < 18
    • 移除所有满足条件(年龄 <18)的对象。

更复杂的投影示例

若需要根据字符串长度移除姓名长度超过 5 的人:

auto result = std::ranges::remove_if(people,
    [](size_t len) { return len > 5; },    // 谓词:判断长度是否 >5
    [](const Person& p) {                  // 投影:将 Person 映射到其 name 的长度
        return p.name.size();
    }
);

// 结果:移除 "Charlie"(长度 7)

std::ranges::remove_copy

 C++20 引入的算法,用于将范围中不满足移除条件的元素复制到目标范围。与 std::ranges::remove 不同,它不会修改原始范围,而是直接生成过滤后的副本。以下是详细说明和示例:


功能

  • 作用:复制原始范围中不等于指定值的元素到目标范围。
  • 特点
    • 不修改原始范围,纯复制操作。
    • 需要提前保证目标范围有足够空间,或使用插入迭代器(如 back_inserter)。
  • 复杂度:线性复杂度 O(n),n 为原始范围大小。

原型

Defined in header <algorithm>

Call signature

(1)
template< std::input_iterator I, std::sentinel_for<I> S,

          std::weakly_incrementable O, class T, class Proj = std::identity >
requires std::indirectly_copyable<I, O> &&
         std::indirect_binary_predicate
             <ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr remove_copy_result<I, O>

    remove_copy( I first, S last, O result, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< std::input_iterator I, std::sentinel_for<I> S,

          std::weakly_incrementable O, class Proj = std::identity,
          class T = std::projected_value_t<I, Proj> >
requires std::indirectly_copyable<I, O> &&
         std::indirect_binary_predicate
             <ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr remove_copy_result<I, O>

    remove_copy( I first, S last, O result, const T& value, Proj proj = {} );
(since C++26)
(2)
template< ranges::input_range R,

          std::weakly_incrementable O, class T, class Proj = std::identity >
requires std::indirectly_copyable<ranges::iterator_t<R>, O> &&
         std::indirect_binary_predicate
             <ranges::equal_to,
              std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr remove_copy_result<ranges::borrowed_iterator_t<R>, O>

    remove_copy( R&& r, O result, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< ranges::input_range R,

          std::weakly_incrementable O, class Proj = std::identity,
          class T = std::projected_value_t<ranges::iterator_t<R>, Proj> >
requires std::indirectly_copyable<ranges::iterator_t<R>, O> &&
         std::indirect_binary_predicate
             <ranges::equal_to,
              std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr remove_copy_result<ranges::borrowed_iterator_t<R>, O>

    remove_copy( R&& r, O result, const T& value, Proj proj = {} );

参数

参数  说明

 first, last 或 r : 输入范围(要处理的原始数据)

result :目标范围的起始迭代器(复制结果的输出位置)

value: 要移除的元素值(所有等于此值的元素会被复制)

 proj : 投影函数(可选,用于在比较前转换元素)


返回值

返回一个 remove_copy_result 结构体,包含:

  • .in:输入范围的结束迭代器(即 last
  • .out:目标范围的结束迭代器(最后一个复制元素的下一个位置)

示例 1:基础用法

移除所有 2 并复制到新容器:

#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <iterator> // 用于 back_inserter

int main() {
    std::vector<int> src{1, 2, 3, 2, 5, 2, 6};
    std::vector<int> dst;

    // 复制所有不等于 2 的元素到 dst
    std::ranges::remove_copy(src, std::back_inserter(dst), 2);

    // 输出结果
    for (int num : dst) {
        std::cout << num << " "; // 输出:1 3 5 6
    }
}

输出:

1 3 5 7

示例 2:使用投影(Projection)

复制所有非空字符串到新容器

#include <vector>
#include <string>
#include <algorithm>
#include <ranges>
#include <iterator>

int main() {
    std::vector<std::string> src{"Hello", "", "World", "", "C++"};
    std::vector<std::string> dst;

    // 投影:将字符串转换为长度,移除长度为 0 的元素
    std::ranges::remove_copy(
        src, 
        std::back_inserter(dst), 
        0,                          // 要移除的投影值(长度=0)
        [](const std::string& s) {  // 投影函数:返回字符串长度
            return s.size();
        }
    );
}

此时,dst 内容:Hello, World, C++

示例 3:结构体过滤

复制所有年龄 ≥18 的 Person 对象:

#include <vector>
#include <algorithm>
#include <ranges>
#include <iterator>

struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> src{
        {"Alice", 30}, {"Bob", 15}, {"Charlie", 20}
    };
    std::vector<Person> dst;

    // 移除年龄 <18 的人(保留年龄 >=18 的)
    std::ranges::remove_copy(
        src,
        std::back_inserter(dst),
        18,                     // 要比较的阈值
        [](const Person& p) {   // 投影:提取年龄
            return p.age;
        },
        std::ranges::less{}     // 比较方式:若 age < 18 则移除
    );

    // dst 内容:Alice (30), Charlie (20)
}

应用场景

  1. 数据备份:保留原始数据,生成过滤后的副本。
  2. 流水线处理:与其他范围适配器(如 views::filter)结合使用。
  3. 条件导出:将满足条件的数据导出到文件、网络或另一个容器。

注意事项

  • 若目标范围空间不足,需使用 std::back_inserter 或提前 reserve()
  • 投影函数应轻量,避免副作用(如修改元素或外部状态)。
  • 若需要自定义条件(非简单值比较),改用 std::ranges::remove_copy_if

std::ranges::remove_copy_if

C++20 引入的算法,用于将输入范围中不满足条件的元素复制到目标范围,同时跳过满足条件的元素。它是 std::remove_copy_if 的范围化版本,更适配现代 C++ 的范围操作。

template< std::input_iterator I, std::sentinel_for<I> S,

          std::weakly_incrementable O, class Proj = std::identity,
          std::indirect_unary_predicate<std::projected<I, Proj>> Pred >
requires std::indirectly_copyable<I, O>
constexpr remove_copy_if_result<I, O>

    remove_copy_if( I first, S last, O result, Pred pred, Proj proj = {} );
(3)(since C++20)
template< ranges::input_range R,

          std::weakly_incrementable O, class Proj = std::identity,
          std::indirect_unary_predicate<
              std::projected<ranges::iterator_t<R>, Proj>> Pred >
requires std::indirectly_copyable<ranges::iterator_t<R>, O>
constexpr remove_copy_if_result<ranges::borrowed_iterator_t<R>, O>

    remove_copy_if( R&& r, O result, Pred pred, Proj proj = {} );
  • 参数

    • first, last 或 r:输入范围。
    • result:目标范围的起始迭代器。
    • pred:一元谓词,若元素满足条件(返回 true),则跳过该元素。
    • proj:投影函数(可选),用于对元素进行转换后再应用谓词。
  • 返回值

    • 包含两个迭代器的结构体:
      • in:输入范围的结束迭代器。
      • out:目标范围最后一个复制元素的下一个位置。

功能说明

  1. 遍历输入范围,对每个元素应用投影函数 proj
  2. 检查谓词 pred
    • 若 pred 返回 false,则将该元素复制到 result
    • 若 pred 返回 true,则跳过该元素。
  3. 返回结果迭代器,方便后续操作。

示例代码

#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator> // 用于 std::ostream_iterator


int main() {
    std::vector<int> src = {1, 2, 3, 4, 5, 6};
    std::vector<int> dst;

    // 移除所有偶数(即保留奇数)
    auto is_even = [](int x) { return x % 2 == 0; };
    std::ranges::remove_copy_if(src, std::back_inserter(dst), is_even);

    // 输出结果
    std::copy(dst.begin(), dst.end(), std::ostream_iterator<int>(std::cout, " "));
    // 输出:1 3 5 
    std::cout << std::endl;

    std::vector<int> src2 = {1, 2, 3, 4, 5, 6};
    std::vector<int> dst2;
    //dst2.reserve(src2.size()); std::vector<int> dst2(src2.size())之前有区别
    //std::cout << dst2.size() << ", " << dst2.capacity() << std::endl;
   // std::copy_if(src2.begin(), src2.end(), std::back_inserter(dst2), is_even);
   dst2.reserve(src2.size());
   std::ranges::copy_if(src2, std::back_inserter(dst2), is_even);
    // 输出结果
    std::copy(dst2.begin(), dst2.end(), std::ostream_iterator<int>(std::cout, " "));
}

 

关键点

  • 不修改原范围:仅复制符合条件的元素到目标范围,原数据保持不变。
  • 目标空间需足够:需确保 result 有足够空间(或使用 std::back_inserter 动态扩展)。
  • 谓词逻辑:注意谓词是“跳过满足条件的元素”,与 std::ranges::copy_if(复制满足条件的元素)正好相反。
;