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

use*_*670 8 c++ algorithm pointer-to-member c++-standard-library c++17

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

注意:此问题中引用的非静态成员函数应该与常规函数谓词不同,只是因为项引用作为隐式参数而不是显式参数传递.

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

#include <array>
#include <algorithm>
#include <iostream>
#include <functional>
#include <cassert>

template<typename TForwardIterator, typename TPredicate> TForwardIterator
vtt_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(vtt_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(vtt_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);
}
Run Code Online (Sandbox Code Playgroud)

Vit*_*meo 6

首先,假设问题是:

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

我可以想到多种原因:

  • 这个Callable概念是在C++ 11中引入的 - 前C++ 11算法并没有考虑到它的设计.

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

  • 考虑到现在还存在执行策略重载,这会显着增加过载量.或者,它可以使每个算法成为可变参数模板,其中参数包可以包含零个或一个参数(如果*this需要为a提供Callable).

  • std::invoke不是constexpr.使用它可能会阻止算法constexpr在将来被标记.


如果您指的是特定情况:

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

  • 谓词T在范围的每个类型元素上执行.

  • T 有一个可以用作谓词的成员函数.

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

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

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

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

或许......它还没有提出.如果您仍然认为在我提出原因之后拥有它是有价值的,那么您可以从这里开始学习如何撰写提案:https://isocpp.org/std/submit-a-proposal