【问题标题】:Use Template of binary predicate function as parameter (std::greater, std::equal_to ) C++使用二元谓词函数模板作为参数 (std::greater, std::equal_to ) C++
【发布时间】:2018-09-21 03:58:11
【问题描述】:

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

我写了一个和std::min_element或std::max_element几乎一样的函数,只是不是返回1个元素,而是返回所有等于那个限制(或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>’)
                                                                                );
                                                                                ^

所以我认为:“问题出在模板上,因为如果为参数 'Compare' 推断出冲突类型,并且类型正确,则唯一的剩余大小写正在更改模板名称”

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 )

而且...有效,但是¿?它是相同的类型,所以我使用相同的模板名称来first和last为什么!? comp 和 comp_equal

不能使用相同的名称

对我来说真的没有意义。

【问题讨论】:

  • 您的模板需要两个不同的比较器模板参数,一个用于comp,另一个用于comp_equal。虽然根本不需要两个比较器,因为可以仅从 comp 找出相等性。
  • 怎么样?你不能使用 !comp
  • !((a &lt; b) || (b &lt; a))
  • 是的!我改了,if( comp( *first, *limit.front() ) )else if( !comp( *limit.front(), *first ) ),和!((a &lt; b) || (b &lt; a))一样,因为De Morgan(!(a &lt; b) &amp;&amp; !(b &lt; a))

标签: c++ templates functional-programming


【解决方案1】:
deduced conflicting types for parameter ‘Compare’

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

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

而且,正如 VTT 所说,您不需要两个比较器;假设 comp 是一个理智的&lt;

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

!((a&lt;b) || (b&lt;a)) => ((a&gt;=b) &amp;&amp; (b&gt;=a)) => (a==b)


这是因为std::greater&lt;T&gt;std::equal_to&lt;T&gt; 是不同的类型

为什么不同的类型,不都是函子?

定义看起来像

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&lt;T&gt; 类型,另一个具有 equal_to&lt;T&gt; 类型。

正如我上面提到的,虽然greater&lt;T&gt;equal_to&lt;T&gt; 是不同的类型,但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>);

【讨论】:

  • 是的。看上面的评论,我改变了最后一个如果,但没有使用!((a&lt;b) || (b&lt;a))
  • 为什么不同的类型,不都是函子?那就是……同类型。
  • 我很难说你是否真的知道类型是什么。 “函子”是一个适用于任何具有正确接口的类型的概念——更像是类型类而不是具体类型。
【解决方案2】:

compcomp_equal 的类型需要两个不同的模板类型参数,正如 VTT 在评论中所说,类型不同。

std::greater&lt;uint64_t&gt;std::equal_to&lt;uint64_t&gt; 根本不是同一类型。 它们恰好是具有相同参数的模板,否则它们不相关。

您可以为迭代器使用一个类型参数,因为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 的规则,因此一旦内联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;    
}

看看编译器是如何在内联后找出最优代码的:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多