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

时间:2017-11-11 12:11:10

标签: c++ algorithm c++17 c++-standard-library pointer-to-member

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

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

示例代码(online compiler):

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

1 个答案:

答案 0 :(得分:6)

首先,假设问题是:

  

为什么算法不能将通用Callable作为谓词/动作?

我可以想到多种原因:

  • {+ 1}}概念是在C ++ 11中引入的 - 前C ++ 11算法的设计并未考虑到这一点。

  • 接受任何Callable都需要扩展Predicate之类的概念,并允许算法有条件地为指向成员函数的指针添加一个额外的参数。这可能会导致不必要的复杂性,只需将界面限制为Callable并强制用户执行“绑定”即可避免。

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

  • std::invoke is not constexpr。使用它可能会阻止将来将算法标记为Callable

如果您指的是以下特定情况:

  • 该算法在constexpr的同类范围内运行。

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

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

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

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

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

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

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