使用二进制谓词函数模板作为参数(std :: greater,std :: equal_to)C ++

时间:2018-04-11 10:02:22

标签: c++ templates functional-programming

为什么我需要在函数的定义中使用2个模板,每个模板对应一个二元谓词?

我编写的函数与std :: min_element或std :: max_element几乎相同,只是返回所有等于该限制的位置(或min,或者最大值)

我上次阅读boost :: minmax_element.hpp,然后复制函数并更改输出和while循环以创建函数。

有效我使用

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

不是我使用

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

这里的功能

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;
}

我如何使用它:

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

错误:

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>’)
                                                                                );
                                                                                ^

所以我认为:“问题是模板,因为,如果推断出参数'比较'的冲突类型,并且类型正确,则唯一的保留情况是更改模板名称”

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 )

而且......工作,但是¿?它是相同的类型,所以我使用相同的模板名称来第一个和最后一个为什么!?不能使用相同的名称 comp和comp_equal

对我来说真的没有意义。

2 个答案:

答案 0 :(得分:3)

deduced conflicting types for parameter ‘Compare’

这是因为std::greater<T>std::equal_to<T>是不同的类型。它们不是bool (*)(T,T)形式的函数,而是函子。

另一方面,您的第一个和最后一个迭代器是两个相同类型的不同实例。

而且,正如VTT所说,无论如何你不需要两个比较器;假设comp是理智的<

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

即,!((a<b) || (b<a)) =&gt; ((a>=b) && (b>=a)) =&gt; (a==b)

  
    

这是因为std::greater<T>std::equal_to<T>是不同的类型

  
     

为什么不同的类型,不是两个算子?

这些定义类似于

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;
  }
};

您定义的每个structclass都是不同的类型。每个模板为每个不同的实例化(模板参数集)定义一个新类型。

他们都是仿函数,因为他们都有operator()。 更具体地说,它们都是二元谓词,因为它们都具有bool operator()(T,T)形式的函数调用运算符。但即使你为同一个T实例化它们,一个类型为greater<T>,另一个类型为equal_to<T>

正如我上面提到的,尽管greater<T>equal_to<T>是不同的类型,bool (*)(T,T)是单一类型(指向二元谓词非成员函数的指针),它可以用于:

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>);

答案 1 :(得分:3)

对于compcomp_equal类型,您需要两个不同模板类型参数的原因是,正如VTT在评论中所述,类型不同。

std::greater<uint64_t>std::equal_to<uint64_t>根本不是同一类型。 它们恰好是具有相同参数的模板,但是它们不相关。

您可以为迭代器使用一个类型参数,因为firstlast具有相同的类型。

您可以编写自己的比较函数对象,如下所示:

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

这大致是std :: greater的样子,除了它是一个可以选择输入类型的模板。

即使std :: equal的调用运算符的签名相同,编译器也必须调用不同的函数。

由于用户Useless在一个完全无用的答案中写道,模板参数不一定是函数指针,只要签名匹配就会兼容。

因为你可以使用任意类型作为比较运算符,所以可以做很多有用的事情。我将使用最小函数作为示例来更好地显示概念:

// 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;
}

首先,如果您的比较器很简单,比如std :: greater和std :: equal,编译器可以内联operator()并生成最佳代码,如下例所示:

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为此生成了很好的代码,请注意lessgreater的内联方式:

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

如果类型是函数指针,编译器只能在内联函数本身内联且参数是编译时常量的情况下执行此内联(除非它使用了一些相当高级的优化)。

请注意,如果您确实需要确定在运行时调用哪个函数,则可以显式使用函数指针作为模板参数。

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

在这个例子中,编译器对less参数一无所知,因此必须生成更多用于调用函数指针的代码。

或者你可以用自己的状态编写函数对象,无论你想要什么,都不会影响普通案例的效率。

关于您可以从更大或更小的构建进行的相等比较,请注意编译器很清楚较少和更大的含义以及De Morgan的规则,因此它通常会在内联{{1 }}。只要写下你发现最明显和最易读的内容,除非你怀疑有人会将你的模板用于重量级比较功能。

std::greater::operator()

了解编译器在内联后如何计算出最佳代码:

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;    
}