为什么指向非静态成员函数的指针不能用作标准库算法的一元谓词

Why can pointers to non-static member functions not be used as a unary predicate for standard library algorithms?

本文关键字:算法 谓词 一元 标准 静态成员 函数 为什么 不能 指针      更新时间:2023-10-16

标准库中的许多算法都接受签名为bool (Type & item)的一元谓词,因此直接提供指向非静态成员函数的指针是不起作用的。考虑到似乎可以通过用对std::invoke的调用代替对谓词的operator ()的直接调用来解除这种限制,这似乎是相当严格的。也许提议的方法有一些我忽略了的缺点?

注意:这个问题中提到的非静态成员函数应该与常规函数谓词的不同之处在于,项引用是作为隐式参数而不是显式参数传递的。

示例代码(在线编译器):

#include <array>
#include <algorithm>
#include <iostream>
#include <functional>
#include <cassert>
template<typename TForwardIterator, typename TPredicate> TForwardIterator
my_find_if
(
const TForwardIterator p_items_begin
,   const TForwardIterator p_items_end
,   TPredicate &&          predicate
)
{
TForwardIterator p_item(p_items_begin);
//  while((p_items_end != p_item) && (!predicate(*p_item)))
while((p_items_end != p_item) && (!::std::invoke(predicate, *p_item)))
{
++p_item;
}
return(p_item);
}
class t_Ticket
{
private: int m_number;
public:
t_Ticket(const int number): m_number(number) {}
public: bool
Is_Lucky(void) const {return(8 == m_number);}
};
int main()
{
::std::array<t_Ticket, 10> tickets{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//  using standard library
auto p_ticket1(::std::find_if(tickets.begin(), tickets.end(), [](const t_Ticket & ticket) {return(ticket.Is_Lucky());}));
//  still works
auto p_ticket2(my_find_if(tickets.begin(), tickets.end(), [](const t_Ticket & ticket) {return(ticket.Is_Lucky());}));
//  same thing, but shorter and not sufferring from potential lambda code duplication
auto p_ticket3(my_find_if(tickets.begin(), tickets.end(), &t_Ticket::Is_Lucky));
//  using standard library like this won't compile
//auto p_ticket4(::std::find_if(tickets.begin(), tickets.end(), &t_Ticket::Is_Lucky));
assert(p_ticket1 == p_ticket2);
assert(p_ticket2 == p_ticket3);
return(0);
}

首先,假设问题是:

为什么算法不能将泛型Callable作为它们的谓词/动作?

我能想到多种原因:

  • Callable概念是在C++11中引入的——在设计C++11之前的算法时没有考虑到它。

  • 接受任何Callable都需要扩展概念(如Predicate),并允许算法有条件地为指向成员函数的指针附加一个参数。这可能会导致不必要的复杂性,可以通过简单地将接口限制为FunctionObject并强制用户进行"绑定"来避免这种复杂性。

  • 考虑到现在也存在执行策略过载,这将显著增加过载量。或者,它可以使每个算法都成为可变模板,其中参数包可以包含零个或一个参数(在需要为Callable提供*this的情况下)。

  • std::invoke不是constexpr。使用它可能会防止算法在未来被标记为constexpr


如果您指的是特定情况,其中:

  • 该算法在T的均匀范围内运行。

  • 谓词在该范围的每一个类型为T的元素上执行。

  • T有一个成员函数,可以用作谓词。

然后,我仍然可以想到一些可能的原因:

  • std::invoke仍然不是constexpr。我们可以避免将std::invoke用于.*,但我们需要为每个算法提供两个单独的实现。

  • 如果成员函数是template重载集,那么这将无法编译并使初学者感到困惑。通用Lambda没有这个问题。

  • 这将使算法的要求复杂化——需要对成员函数的类型/签名进行某种限制,以确保它在T的范围内可用。

或者。。。只是还没有人提出。如果在我提出这些理由后,你仍然认为这是一件有价值的事情,你可以从这里开始学习如何写一份提案:https://isocpp.org/std/submit-a-proposal