이번 글부터 여기까지는 함수 이름 끝에 _if가 달려 있는 STL의 조건부 알고리즘(conditional algorithms)에 대해 알아본다. 함수 이름들이 굉장히 직관적으로 잘 지어져있듯이, 이 조건부 알고리즘들은 컨테이너의 요소들을 lambda function의 조건이 참인지 거짓인지 판별한 후, 그 결과에 따라 복사, 탐색, 삭제, 교체하는 역할을 한다.

std::copy_if()

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

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst;
    dst.reserve(src.size());
    std::copy_if(src.begin(), src.end(), std::back_inserter(dst), [](int x) { return x % 2 == 0; });

    // Output: 2 4
    for (auto i : dst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

src 내에서 조건을 만족하는 값을 dst의 뒷 쪽에 std::back_inserter를 통해 삽입하고 있는 것을 볼 수 있다.

로보틱스에서 활용 사례

두 가지 활용 사례에 대해 살펴보자. 먼저, Open3D의 코드 내 메모리를 release하는 부분에서 다음과 같이 사용되고 있다:

std::set<std::shared_ptr<RealBlock>, SizeOrder<RealBlock>>
                releasable_real_blocks;
                
std::copy_if(real_blocks_.begin(), real_blocks_.end(),
    std::inserter(releasable_real_blocks,
                  releasable_real_blocks.begin()),
    [this](const auto& r_block) { return IsReleasable(r_block); });

앞서 살펴본 예제에서는 std::back_inserter를 통해 vector의 뒷부분에 요소를 삽입했다면, 이 코드에서는 std::inserter를 통해 set에 삽입하는 방식으로 작성되어 있다. set의 경우에는 내부적으로 정렬 순서에 따라서 요소를 배치하기 때문에, std::back_inserter 는 지원되지 않는다. 아래는 이해를 돕기 위해 내가 작성한 예시 코드이다:

#include <algorithm>
#include <vector>
#include <set>
#include <iostream>

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

    // Error!
    // std::copy_if(src.begin(), src.end(), std::back_inserter(dst), [](int x) { return x % 2 == 0; });

    std::copy_if(src.begin(), src.end(), std::inserter(dst, dst.begin()), [](int x) { return x % 2 == 0; });

    // Output: 1.5 2 3.2 4
    for (auto i : dst) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

추가적으로, 다른 컨테이너(i.e., srcstd::vector이고 dststd::set임)간의 데이터 교환이라도 함수형 프로그래밍은 컨테이너의 iterator만 잘 주어진다면 유연하게 사용이 가능하다는 것을 확인할 수 있다.

또한 아래 OpenVINO 코드에서처럼 std::find와 결합하여 아래와 같이 많이들 활용한다:

std::vector<int64_t> range(data_rank_value);
std::iota(range.begin(), range.end(), 0);
std::vector<int64_t> indices;
std::copy_if(range.cbegin(), range.cend(), std::back_inserter(indices),
        [&axes](int64_t i) { return std::find(axes.cbegin(), axes.cend(), i) == axes.cend(); });

즉, 0 번째부터 N 번째의 range의 요소인 index i에 대하여, i값이 axes에 존재하지 않으면 indices에 요소를 복사한다.

여기서 cbegin()cend()는 const iterator를 리턴하는 함수로, begin()end()와 유사하지만, iterator를 통해 컨테이너의 요소를 수정할 수 없게 한다. 따라서 요소들이 실수로 변경되는 것을 방지할 수 있다.


Robotics 연구자/개발자를 위한 Modern C++ 시리즈입니다. 사용된 코드들은 여기에서 확인할 수 있습니다.

  1. Modern C++ for Robotics 1. Introduction
  2. Modern C++ for Robotics 2. 함수형 프로그래밍과 Lambda Expression
  3. Modern C++ for Robotics 3. Lambda Expression의 Anonymous Function과 Named Function
  4. Modern C++ for Robotics 4. std::for_each() 쉬운 설명
  5. Modern C++ for Robotics 5. std::insert() 쉬운 설명
  6. Modern C++ for Robotics 6. std::move() 쉬운 설명
  7. Modern C++ for Robotics 7. std::transform() 쉬운 설명
  8. Modern C++ for Robotics 8. std::accumulate() 쉬운 설명
  9. Modern C++ for Robotics 9. std::all_of(), std::any_of(), std::none_of() 쉬운 설명
  10. Modern C++ for Robotics 10. std::copy_if() 쉬운 설명
  11. Modern C++ for Robotics 11. std::find_if() 쉬운 설명
  12. Modern C++ for Robotics 12. std::remove_if() 쉬운 설명
  13. Modern C++ for Robotics 13. std::replace_if() 쉬운 설명