为什么INVOKE总是取消引用数据成员,而不是在可能的情况下调用

Why does INVOKE always dereference data members instead of calling when possible?

本文关键字:情况下 调用 INVOKE 取消 引用 为什么 数据成员      更新时间:2023-10-16

这个问题(为什么C++11标准中的INVOKE功能指的是数据成员?)问为什么INVOKE讨论数据成员,但忽略了它们实际是如何被调用的。

这个问题(c++中的std::invoke是什么?)讨论了为什么要访问它们,但如果它们是可调用的,为什么不调用它们。

[功能要求]:

定义INVOKE(f,t1,t2,…,tN)如下:

  • (1.1)(t1.*f)(t2,…,tN)当f是指向类T的成员函数的指针并且是_­base_­of_­v<T、 remove_­reference_T<decltype(t1)>>为真
  • (1.2)(t1.get().*f)(t2,…,tN)当f是指向类T的成员函数的指针并且remove_­cvref_T<decltype(t1)>是reference_­wrapper的一个特殊化
  • (1.3)((*t1).*f)(t2,…,tN),当f是指向类T的成员函数的指针并且t1不满足前两项时
  • (1.4)当N==1并且f是指向类T的数据成员的指针并且是_;T、 remove_­reference_T<decltype(t1)>>为真
  • (1.5)t1.get().*f,当N==1并且f是指向类T的数据成员的指针并且remove_­cvref_T<decltype(t1)>是reference_­wrapper的一个特殊化
  • (1.6)(*t1).*f,当N==1并且f是指向类T的数据成员的指针并且t1不满足前两项时
  • (1.7)f(t1,t2,…,tN)

1.4到1.6处理对指向数据成员的指针的访问,这在给定函子和存储的可调用函数的情况下是有意义的。我不明白的是,为什么它不调用这些成员,而是简单地取消引用它们?我预计1.4将与1.1的语法并行,并且呃…如果f拥有operator ()将调用所讨论的对象

为什么会有这种限制,它有什么作用?


以下是一些需要澄清的代码:

#include <functional>
#include <iostream>
struct func1 
{ 
void operator()() { std::cout << "Invoked functorn"; }
};
void func2()
{
std::cout << "Invoked free functionn";
}
struct D1 {};
struct T1 {
func1 f1;
void func3() { std::cout << "Invoked member functionn"; }
D1 d1;
};
int main()
{
T1 t1;
func1 free_f1;
std::invoke(&T1::f1, t1);               //does nothing
std::invoke(&func1::operator(), t1.f1); //okay, so there is a workaround, if clumsy
std::invoke(&func2);                    //calls func2
std::invoke(&T1::func3, t1);            //calls func3
std::invoke(&T1::d1, t1);               //does nothing (expected)
std::invoke(free_f1);                   //works on non-member functors
return 0;
}

这编译得很好,但只在对invoke的第二次调用中调用func1()。我理解为什么INVOKE在第一个参数不是可调用对象的情况下只取消引用它。我的问题是,为什么标准不允许调用数据成员的可调用指针,也就是说,为什么标准没有强制要求在第一次使用std::invoke时调用f1


编辑:由于std::invoke是在C++17中添加的,我将这个问题标记为希望参与这个过程的人能提供一些线索。这是添加std::invoke()的原始论文,它实际上解释了它想要统一处理函子的动机:

尽管INVOKE表达式的行为可以通过现有标准库组件的组合来再现,但在这种解决方案中需要单独处理函子和成员指针。

在上面的代码中,您可以看到这项工作。。。只是不适用于指向本身就是函子的成员数据的指针。这只是疏忽吗?

在组装C++11的标准库时,它采用了来自各种Boost库的许多功能。就本次对话而言,以下Boost工具很重要:

  • bind
  • function
  • reference_wrapper
  • mem_fn

在一个或另一个级别上,这些都与一个可调用的东西有关,它可以接受一些类型的参数,并产生特定的返回值。因此,他们都试图以一致的方式对待可调用的事物。C++11在采用这些工具时,根据完美的转发规则发明了INVOKE的概念,使所有这些都能够引用一致的处理方式。

mem_fn唯一目的是获取指向成员的指针,并将其转换为可使用()直接调用的对象。对于指向成员函数的指针,显而易见的做法是调用所指向的成员函数,给定该函数的对象和任何参数。对于指向成员变量的指针,最明显的做法是在给定要访问的对象的情况下返回该成员变量的值。

将数据成员指针转换为返回变量本身的一元函子的功能非常有用。您可以使用类似std::transform的东西,将数据成员指针的mem_fn传递给它,以生成访问特定可访问数据成员的值序列。添加到C++20中的范围功能将使其更加有用,因为您可以创建转换的范围,只需获得成员指针即可操作子对象的序列。

但事情是这样的:无论成员子对象的类型是什么,你都希望它能工作。如果该子对象恰好是可调用的,那么mem_fn应该能够像访问任何其他对象一样访问可调用对象。它恰好是可调用的,这与mem_fn的目的无关。

但是boost::functionboost::bind可以采用成员指针。它们都基于boost::mem_fn的成员指针行为。因此,如果mem_fn将指向数据成员的指针视为返回该成员值的一元函子,那么所有这些都必须以这种方式对待指向数据成员。

因此,当C++11将所有这些编码成一个统一的概念时,它将该实践直接编码成INVOKE

因此,从词源学的角度来看,这就是INVOKE以这种方式工作的原因:因为所有这些都意味着相同地对待可调用函数,而mem_fn关于数据成员指针的全部意义在于将它们视为返回其值的一元函数。所以其他人也必须这样对待他们。

这不是一件好事吗?这不是正确的行为吗?如果成员指针指向的类型恰好是可调用的,那么您真的希望指向数据成员的指针的行为与不可调用的类型大不相同吗?这将使编写采用数据成员指针,然后对某个对象序列进行操作的泛型代码变得不可能。如果你不确定数据成员指针是否会引用子对象或调用子对象本身,你如何能够通用地访问它?