Search code examples
c++algorithmc++11templatesdecltype

Is it correct to use dectlype to deduce the data type in template's element type?


Hello I am asked to simulate the algorithm std::copy_if in this example to copy the odd values into s list of integers and the even ones also int another list so here is my example:

#include <list>
#include <vector>
#include <iostream>

template <typename T>
bool is_odd_pred(T x) {
    return x % 2 != 0;
}

template <typename T>
bool is_even_pred(T x) {
    return !(x % 2);
}


template <class T, class U>
void cpy_if(T first, T last, U destIt, bool(*pfnPred)(decltype(*first + 1))) {
    while (first != last) {
        if (pfnPred(*first))
            *destIt++ = *first;
        ++first;
    }
}



int main() {

    std::vector<int> v1{ 10, 27, 57, 77, 81, 24, 16, 23, 28 };

    for (auto i : v1)
        std::cout << i << ", ";
    std::cout << std::endl;

    std::list<int> li_odd;
    //cpy_if(v1.cbegin(), v1.cend(), li.begin(),
    //  [](decltype(*v1.cbegin() + 1) a) { return (a % 2 != 0);  });

    cpy_if(v1.cbegin(), v1.cend(), std::back_inserter(li_odd), is_odd_pred);


    for (auto i : li_odd)
        std::cout << i << ", ";
    std::cout << std::endl;

    std::list<int> li_even;

    cpy_if(v1.cbegin(), v1.cend(), std::back_inserter(li_even), is_even_pred);


    for (auto i : li_even)
        std::cout << i << ", ";
    std::cout << std::endl;
}

The program looks to work fine but Is it that correct context to use decltype in my cpy_if algorithm? Because I only made the algorithm has two elements types which are considered to be iterators.? Thank you for any help, tips, suggestion.


Solution

  • The glaring problem is that *first + 1 doesn't work for some types.

    I assume you've added it to prevent decltype from giving you a reference. If so, a more reliable equivalent is:

    bool(*pfnPred)(std::remove_cv_t<std::remove_reference_t<decltype(*first)>>)
    

    Starting from C++20, it can be shortened to:

    bool(*pfnPred)(std::remove_cvref_t<decltype(*first)>)
    

    Next, using a function pointer imposes unnecessary restrictions on the predicate.

    It doesn't let you use stateful lambdas, or functions with slightly different parameter types (such as bool pred(const int &) {...}).

    Rather than using a function pointer, you can make your function accept arbitrary callable objects. (This is how the standard algorithms work.)

    template <class T, class U, class F>
    void cpy_if(T first, T last, U destIt, F pfnPred) {
        while (first != last) {
            if (pfnPred(*first))
                *destIt++ = *first;
            ++first;
        }
    }
    

    For extra safety, you can use pfnPred(std::as_const(*first)) to prevent the predicate from accepting a parameter by a non-const reference and modifying it.