提供方法指针和派生类型对象时,std::bind的一致性

Conformance of std::bind when providing a method pointer and an object of derived type

本文关键字:std bind 一致性 对象 方法 供方 指针 类型 派生      更新时间:2023-10-16

这个问题基本上是我给出的答案的后果。我刚刚意识到标准中的措辞似乎省略了一些情况。考虑这段代码:

#include <iostream>
#include <functional>
struct foo
{
  void f(int v) { std::cout << v << std::endl; }
};
struct bar : foo {};
int main()
{
  bar obj;
  std::bind(&foo::f, obj, 1)();
}

标准在20.8.9.1.2中描述了std::bind的影响以及当它被调用时会发生什么。转发到20.8.2,相关部分为:

20.8.2 Requirements [function .require]

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

- (t1.*f)(t2, ..., tN),当f是指向T类成员函数的指针,t1T类型的对象或对T类型对象的引用或对T派生类型对象的引用时;

- ((*t1).*f)(t2, ..., tN),当f是指向类T成员函数的指针,且t1不是上一项中描述的类型;

- t1.*f,当N == 1f是指向类T的成员数据的指针,t1T类型的对象或对T类型的对象的引用或对从T派生的类型的对象的引用时;

- (*t1).*fN == 1f是指向类成员数据的指针时,Tt1不是上一项中描述的类型之一;

- f(t1, t2, ..., tN)

读到这里,似乎第一个列表项允许三种情况:

  • t1是类型为T的对象
  • 或对T
  • 类型对象的引用
  • 或对从T
  • 派生的类型对象的引用

,但在我的例子中,两者都不是。这是一个从T派生的类型,但没有引用。上面列出的情况不应该是:

  • t1是类型为T的对象
  • T
  • 派生类型的对象
  • 或对T
  • 类型对象的引用
  • 或对从T
  • 派生的类型的对象的引用

当然,这同样适用于20.8.2中的第三个列表项。

问题1:由于GCC和Clang都接受我的代码,我想知道这是一个违反标准的缺陷报告,还是我只是读错了。

问题2:对于多重/虚拟继承,即使类型是从T派生的,也不可能简单地在它上面调用(t1.*f)(...),对吗?这也是我应该关心的问题吗?还是说标准定义是从"派生"来的?在给定的上下文中?

c++ 11标准第20.8.9.1.3段以INVOKE() 伪函数的方式定义了调用std::bind()的效果:

返回:一个具有弱结果类型(20.8.2)的转发呼叫包装器gg(u1, u2, ..., uM)的效力为INVOKE (fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN), result_of<FD cv & (V1, V2, ..., VN)>::type)

请注意,对std::forward<>的调用将始终返回引用类型,无论是左值引用还是右值引用。根据第20.2.3/1-2段,事实上:

template <class T> constexpr T&& forward(typename remove_reference<T>::type& t) noexcept;

template <class T> constexpr T&& forward(typename remove_reference<T>::type&& t) noexcept;

返回: static_cast<T&&>(t)

因此,INVOKE伪函数(注意:不是bind()函数!)将在这里用引用调用,这是由您引用的定义支持的。

我相信标准本身从来没有使用INVOKE()伪函数(这只是一些内部形式)与对象(不是对对象的引用)从T派生的类型。

但是,这并不意味着您不能使用从T派生的类型的对象(不是对象的引用)调用std::bind()或其他根据INVOKE()给出定义的函数。