How to filter elements from a collection into a new collection in a generic way (edge‑case‑safe) in C++

2 Answers

0 votes
//Key goals:
// Work with any container type (not just std::vector)
// Work with any element type
// Accept any predicate (lambda, function pointer, functor)
// Avoid undefined behavior and handle edge cases gracefully
// Keep the code clean and modular

#include <vector>
#include <list>
#include <string>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <type_traits>

// ------------------------------------------------------------
// Helper: conditionally call reserve() if the container supports it
// ------------------------------------------------------------
template <typename Container>
auto try_reserve(Container& c, std::size_t n)
    -> decltype(c.reserve(n), void())
{
    c.reserve(n); // Only participates in overload resolution if reserve exists
}

inline void try_reserve(...) 
{
    // Fallback: do nothing if reserve() is not available
}

// ------------------------------------------------------------
// Generic filter function
// - Works with any container supporting begin/end and push_back
// - Accepts any unary predicate
// - Returns a new container of the same type
// ------------------------------------------------------------
template <typename Container, typename Predicate>
Container filter(const Container& input, Predicate pred)
{
    Container output;

    // Reserve capacity when possible (e.g., for std::vector)
    try_reserve(output, input.size());

    std::copy_if(input.begin(), input.end(),
                 std::back_inserter(output),
                 pred);

    return output;
}

// ------------------------------------------------------------
// Helper to print any container
// ------------------------------------------------------------
template <typename Container>
void print(const Container& c, const std::string& label)
{
    std::cout << label << ": ";
    for (const auto& elem : c)
        std::cout << elem << " ";
    std::cout << "\n";
}

int main()
{
    // -------------------------
    // Test Case 1: Even numbers
    // -------------------------
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};
    auto evens = filter(nums, [](int x) { return x % 2 == 0; });

    print(nums,  "Original nums");
    print(evens, "Filtered evens");

    std::cout << "\n";

    // -------------------------
    // Test Case 2: Strings with length > 3
    // -------------------------
    std::vector<std::string> words = {"the", "world", "hi", "universe"};
    auto longWords = filter(words, [](const std::string& s) {
        return s.size() > 3;
    });

    print(words,     "Original words");
    print(longWords, "Filtered long words");

    std::cout << "\n";

    // -------------------------
    // Test Case 3: Filtering a std::list
    // -------------------------
    std::list<int> lst = {10, 15, 20, 25, 30};
    auto big = filter(lst, [](int x) { return x > 20; });

    print(lst, "Original list");
    print(big, "Filtered >20");
}


/*
OUTPUT:

Original nums: 1 2 3 4 5 6 
Filtered evens: 2 4 6 

Original words: the world hi universe 
Filtered long words: world universe 

Original list: 10 15 20 25 30 
Filtered >20: 25 30 

*/

 



answered Mar 29 by avibootz
0 votes
#include <iostream>
#include <vector>
#include <string>

// ------------------------------------------------------------
// filter: copies elements that satisfy a predicate into a new vector
// Predicate is a template parameter (works with lambdas, function pointers, functors)
// ------------------------------------------------------------
template <typename T, typename Predicate>
std::vector<T> filter(const std::vector<T>& input, Predicate predicate)
{
    std::vector<T> result;
    result.reserve(input.size()); // optimization: avoid repeated reallocations

    for (const auto& element : input) {
        if (predicate(element)) {
            result.push_back(element);
        }
    }

    return result;
}

// ------------------------------------------------------------
// Keep only even numbers
// ------------------------------------------------------------
bool isEven(const int& value)
{
    return value % 2 == 0;
}

// ------------------------------------------------------------
// Utility: print a vector
// ------------------------------------------------------------
template <typename T>
void printVector(const std::vector<T>& vec, const std::string& label)
{
    std::cout << label << ": [ ";
    for (const auto& v : vec)
        std::cout << v << " ";
    std::cout << "]\n";
}

// ------------------------------------------------------------
// Test cases
// ------------------------------------------------------------
int main()
{
    // Test 1: basic filtering with function pointer predicate
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
    auto evens = filter(numbers, isEven);

    printVector(numbers, "Original");
    printVector(evens, "Filtered (even)");

    // Test 2: filter values > 10 using a lambda
    std::vector<int> mixed = {3, 11, 8, 320, 1, 17};
    auto greaterThan10 = filter(mixed, [](int x) { return x > 10; });

    printVector(mixed, "Original");
    printVector(greaterThan10, "Filtered (>10)");

    // Test 3: empty input
    std::vector<int> empty;
    auto filteredEmpty = filter(empty, isEven);

    printVector(empty, "Original (empty)");
    printVector(filteredEmpty, "Filtered (empty)");
}



/*
OUTPUT:

Original: [ 1 2 3 4 5 6 ]
Filtered (even): [ 2 4 6 ]
Original: [ 3 11 8 320 1 17 ]
Filtered (>10): [ 11 320 17 ]
Original (empty): [ ]
Filtered (empty): [ ]

*/

 



answered Mar 29 by avibootz

Related questions

...