为什么INVOKE总是取消引用数据成员,而不是在可能的情况下调用
Why does INVOKE always dereference data members instead of calling when possible?
这个问题(为什么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::function
和boost::bind
可以采用成员指针。它们都基于boost::mem_fn
的成员指针行为。因此,如果mem_fn
将指向数据成员的指针视为返回该成员值的一元函子,那么所有这些都必须以这种方式对待指向数据成员。
因此,当C++11将所有这些编码成一个统一的概念时,它将该实践直接编码成INVOKE
。
因此,从词源学的角度来看,这就是INVOKE
以这种方式工作的原因:因为所有这些都意味着相同地对待可调用函数,而mem_fn
关于数据成员指针的全部意义在于将它们视为返回其值的一元函数。所以其他人也必须这样对待他们。
这不是一件好事吗?这不是正确的行为吗?如果成员指针指向的类型恰好是可调用的,那么您真的希望指向数据成员的指针的行为与不可调用的类型大不相同吗?这将使编写采用数据成员指针,然后对某个对象序列进行操作的泛型代码变得不可能。如果你不确定数据成员指针是否会引用子对象或调用子对象本身,你如何能够通用地访问它?
- 如果 std::vector::clear() 不是静态的,如何在没有实例的情况下调用它?
- C++ - 如何在不调用其属性的情况下调用类?
- C++ 在不释放内存的情况下调用析构函数
- Python在不引用类名的情况下调用类函数
- 为什么在这种情况下调用非常量右值移动构造函数?
- 是否可以在没有显式专用化的情况下调用可变参数模板函数?
- 使用按引用调用时,不能在没有对象的情况下调用成员函数 const
- 您可以在不调用构造函数的情况下调用攻击器吗?
- 命名空间:不能在没有对象的情况下调用成员函数
- 哪些语言将在没有显式桥接的情况下调用C++
- 如何在不重新索引顶点的情况下调用"boost::remove_vertex"?
- C :在没有对象实例的情况下调用非静态成员函数
- 在不实例化的情况下调用不同派生类的虚拟方法
- C 如何在没有错误的情况下调用void函数
- 在MSVC上的数组初始化期间,destructor在不复制或移动构造方的情况下调用
- 在不指定实例化的情况下调用类模板的静态方法的方法
- 为什么INVOKE总是取消引用数据成员,而不是在可能的情况下调用
- 是否可以在不实例化派生类的情况下调用该类的虚拟函数
- 无法在没有对象的情况下调用成员函数(尽管我相信我确实初始化了它)
- 为什么在没有赋值运算符的情况下调用转换构造函数