简体   繁体   中英

Use Template of binary predicate function as parameter (std::greater, std::equal_to ) C++

Why do I need to use 2 templates, one for each binary predicate, in the definition of a function?

I write a function that is almost the same that or std::min_element or std::max_element, just that instead of return 1 element, return all the positions that are equal to that limit (or min, or max)

I spend last time reading boost::minmax_element.hpp, and I copy the function and change the output and the while loop to create the function.

And works if I use

if( *first > *lim.front() ) 

But not If I use

if( comp(*first, *lim.front()) )

Here the function

template <typename INPUT_ITERATOR, class Compare>
std::vector<INPUT_ITERATOR>
basic_limit_positions( INPUT_ITERATOR first, INPUT_ITERATOR last, Compare comp, Compare comp_equal ) {

  std::vector<INPUT_ITERATOR> lim;

  if ( first == last ) {
    lim.push_back( last );
    return lim;
  }

  lim.push_back(first);
  while (++first != last) {

    if ( comp( *first, *lim.front() ) ) {
      lim.clear();
      lim.push_back( first );
    }
    else if ( comp_equal( *first, *lim.front() ) )
      lim.push_back( first );

  }

  return lim;
}

How I use it:

  std::vector< std::vector<uint64_t>::iterator > it = basic_limit_positions(v.begin(),
                                                                               v.end(),
                                                                               std::greater< uint64_t >(),
                                                                               std::equal_to< uint64_t > ()
                                                                               );

And the error:

test.cpp:40:80: error: no matching function for call to ‘basic_limit_positions(std::vector<long unsigned int>::iterator, std::vector<long unsigned int>::iterator, std::greater<long unsigned int>, std::equal_to<long unsigned int>)’
                                                                                );
                                                                                ^
In file included from test.cpp:10:0:
newton/algorithm/superior_positions.hpp:7:1: note: candidate: template<class INPUT_ITERATOR, class Compare> std::vector<INPUT_NUMBER> basic_limit_positions(INPUT_ITERATOR, INPUT_ITERATOR, Compare, Compare)
 basic_limit_positions( INPUT_ITERATOR first, INPUT_ITERATOR last, Compare comp, Compare comp_equal ) {
 ^~~~~~~~~~~~~~~~~~~~~
newton/algorithm/superior_positions.hpp:7:1: note:   template argument deduction/substitution failed:
test.cpp:40:80: note:   deduced conflicting types for parameter ‘Compare’ (‘std::greater<long unsigned int>’ and ‘std::equal_to<long unsigned int>’)
                                                                                );
                                                                                ^

So I think: "the problem is the template because, if deduced conflicting types for parameter 'Compare' , and the type is correct, the unique remain case is changing template name"

template <typename INPUT_ITERATOR, class Compare1, class Compare2>
std::vector<INPUT_ITERATOR>
basic_limit_positions( INPUT_ITERATOR first, INPUT_ITERATOR last, Compare1 comp, Compare2 comp_equal )

And... Works, But ¿? It's the same type, so I use the same template name to first and last Why!? can't use the same name to comp and comp_equal

Really don't have sense to me.

deduced conflicting types for parameter ‘Compare’

This is because std::greater<T> and std::equal_to<T> are different types. They're not functions of the form bool (*)(T,T) , but functors.

Your first and last iterators on the other hand, are two different instances of the same type.

And, as VTT says, you don't need two comparitors anyway; assuming comp is a sane < ,

const bool equal = !(comp(a,b) || comp(b,a));

ie, !((a<b) || (b<a)) => ((a>=b) && (b>=a)) => (a==b)


This is because std::greater<T> and std::equal_to<T> are different types

Why are different types, are not both functors?

The definitions look something like

template <typename T>
struct greater {
  constexpr bool operator()(const T& lhs, const T& rhs) {
    return lhs > rhs;
  }
};
template <typename T>
struct equal_to {
  constexpr bool operator()(const T& lhs, const T& rhs) {
    return lhs == rhs;
  }
};

Each struct or class you define is a different type. Each template defines a new type for each distinct instantiation (set of template parameters).

They're both functors because they both have an operator() . More specifically they're both binary predicates because they both have a function call operator of the form bool operator()(T,T) . But even when you instantiate them both for the same T, one has type greater<T> and the other has type equal_to<T> .

As I mentioned above, although greater<T> and equal_to<T> are different types, bool (*)(T,T) is a single type (a pointer to a binary predicate nonmember function) which could be used for either:

template <typename T>
bool f_greater(T const &lhs, const T& rhs) {
  return lhs > rhs;
}
template <typename T>
bool f_equal_to(T const &lhs, const T& rhs) {
  return lhs == rhs;
}

typedef bool(*binary_predicate)(int const&, int const&);
bool greater_or_equal(int a, int b,
                      binary_predicate g,
                      binary_predicate e)
{
  return g(a,b) || e(a,b);
}

// ... example use ...
greater_or_equal(17, 12, &f_greater<int>, &f_equal_to<int>);

The reason you need two different template type parameters for the types of comp and comp_equal is, as VTT stated in a comment, that the types are different.

std::greater<uint64_t> and std::equal_to<uint64_t> are not at all the same type. They happen to be templates that have the same parameter, but otherwise they are not related.

You can use one type parameter for the iterator, because first and last have the same type.

You could write your own comparison function objects like this:

struct my_greater {
    bool operator()(uint32_t a, uint32_t b) {
        return a > b;
    }
};

That is roughly how std::greater looks like, except that it is a template where you can select the input type.

Even though the signature of the call operator of std::equal is the same, the compiler must call a different function.

As user Useless wrote in a not at all useless answer, a template parameter is not necessarily a function pointer, which would be compatible as soon as the signatures match.

Because you can use an arbitrary type as comparison operator, there are many useful things that can be done. I will use a minimum function as an example to better show the concepts:

// here I provided a default for the less comparison, 
// so you do not have to write it
// first we have a default for the type
// and then we have a default for the actual value
template <typename T, typename Less = std::less<T>>
T myMin(T a, T b, Less less=Less{}) {
   return less(b, a) ? b : a;
}

First of all, if your comparators are simple like std::greater and std::equal, the compiler can inline the operator() and generate optimal code like in the following example:

int min_int(int a, int b) {
  return myMin(a, b);
}

int max_int(int a, int b) {
  return myMin(a, b, std::greater<int>{});
}

gcc generates nice code for this, note how less and greater were inlined:

min_int(int, int):
  cmp edi, esi
  mov eax, esi
  cmovle eax, edi
  ret
max_int(int, int):
  cmp edi, esi
  mov eax, esi
  cmovge eax, edi
  ret

If the type was a function pointer, the compiler could only do this inlining if the outer function itself is inlined and the arguments are compile time constants (unless it is using some rather advanced optimizations).

Note that you could explicitly use a function pointer as the template argument if you really needed to figure out which function to call at runtime.

int pointy_min(int a, int b, bool(*less)(int, int)) {
    return myMin(a, b, less);    
}

In this example the compiler knows nothing about the less argument and must therefore generate more code for calling the function pointer.

Or you could write function objects with their own state, whatever you want, without compromising the efficiency of the common case at all.

Regarding the equality comparison that you can build from greater or less, note that the compiler is well aware of the meaning of less and greater and De Morgan's rules so it will usually optimize well once it inlines std::greater::operator() . Just write whatever you find the most obvious and readable, unless you suspect somebody is going to use your template with a heavyweight comparison function.

template<typename T, typename Less = std::less<T>>
bool myEqual(T a, T b, Less less=Less{}) {
    return !(less(a,b) || less(b,a));
}

bool intEqual(int a, int b) {
    return myEqual(a, b);
}

bool otherEqual(int a, int b) {
    return a == b;    
}

See how the compiler figured out the optimal code after inlining:

intEqual(int, int): # @intEqual(int, int)
  cmp edi, esi
  sete al
  ret
otherEqual(int, int): # @otherEqual(int, int)
  cmp edi, esi
  sete al
  ret

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM