Search code examples
c++c++11stlstl-algorithm

Why does the std::copy_if signature not constrain the predicate type


Imagine we have the following situation:

struct A
{
    int i;
};

struct B
{
    A a;
    int other_things;
};

bool predicate( const A& a)
{
    return a.i > 123;
}

bool predicate( const B& b)
{
    return predicate(b.a);
}

int main()
{
    std::vector< A > a_source;
    std::vector< B > b_source;

    std::vector< A > a_target;
    std::vector< B > b_target;

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter( a_target ), predicate);
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter( b_target ), predicate);

    return 0;
}

Both the call to std::copy_if generate a compile error, because the correct overload of predicate() function cannot be infered by the compiler since the std::copy_if template signature accepts any type of predicate:

template<typename _IIter, 
         typename _OIter, 
         typename _Predicate>
_OIter copy_if( // etc...

I found the overload resolution working if I wrap the std::copy_if call into a more constrained template function:

template<typename _IIter, 
         typename _OIter, 
         typename _Predicate = bool( const typename std::iterator_traits<_IIter>::value_type& ) >
void copy_if( _IIter source_begin, 
              _IIter source_end, 
              _OIter target,  
              _Predicate pred)
{
    std::copy_if( source_begin, source_end, target, pred );
} 

My question is: why in the STL is it not already constrained like this? From what I've seen, if the _Predicate type is not a function that returns bool and accepts the iterated input type, it is going to generate a compiler error anyway. So why not putting this constrain already in the signature, so that overload resolution can work?


Solution

  • Because the predicate does not have to be a function, but it can be a functor too. And restricting functor type is close to impossible since it can be anything at all as long as it has operator() defined.

    Actually I suggest you convert the overloaded function to a polymorphic functor here:

    struct predicate {
        bool operator()( const A& a) const
        {
            return a.i > 123;
        }
    
        bool operator()( const B& b) const
        {
            return operator()(b.a);
        }
    }
    

    and call the functor with an instance, i.e.

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter( a_target ), predicate());
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter( b_target ), predicate());
    //                                                                                      ^^ here, see the ()
    

    Then the correct overload will be selected inside the algorithm.