标准是否允许(或是否会遇到矛盾)将成员函数调用视为自由函数呢?

huangapple go评论62阅读模式
英文:

Can the standard allow (or would run into contradictions) calling a member function as if it was free function?

问题

A member function pointer must be invoked using the .* (or ->*) syntax, so it can't be passed to a higher-order function:

#include <vector>

void for_each(auto const& v, auto f) {
  for (auto const& e : v)
    f(e);  // error: must use '.*' or '->*' to call pointer-to-member function in 'f (...)', e.g. '(... ->* f) (...)'
}

struct Foo {
  void bar();
};

int main() {
  std::vector<Foo> v(10);
  for_each(v, &Foo::bar);  // from here
}

The C++ Standard Library has two separate solutions to this: either I can use std::mem_fn() to get a free-function-like callable from a member function:

int main() {
  std::vector<Foo> v(10);
  for_each(v, std::mem_fn(&Foo::bar));  // OK
}

Or I can augment the higher-order function to use std::invoke (std::for_each already does this) instead of invoking the callable directly:

void for_each(auto const& v, auto f) {
  for (auto const& e : v)
    std::invoke(f, e);  // OK
}

But, since the syntax (&Foo::bar)(Foo{}) is invalid at the current time, couldn't the standard make it valid and equivalent to calling std::mem_fn() first on the &Foo::bar?

Effectively, this would mean "absorbing" the std::mem_fn() utility in the language.

Would that be possible? Or, would it have undesired side effects? I can't see how it could break anything, considering that it's currently invalid syntax.


As I wrote the question, a possible answer came to my mind: SFINAE could be relying on that syntax being invalid.

It the following snippet, for instance, the second static_assert would fail if the standard started to allow calling (&Foo::bar)(Foo{}):

#include <type_traits>
#include <vector>

struct Foo {
  void bar();
};

template<typename F, typename = void>
struct Trait : public std::false_type {};

template<typename F>
struct Trait<F, std::void_t<decltype(std::declval<F>()(std::declval<Foo>()))>>
    : public std::true_type {};

auto constexpr freeBar = [](Foo){};

int main() {
  static_assert(Trait<decltype(freeBar)>::value);
  static_assert(!Trait<decltype(&Foo::bar)>::value);
}

However, in the comments to my delete self-answer it was pointed out that this cannot be a reason to prevent the standard from adopting the syntax I'm thinking about.

After all, and more in general, if we wanted not to break code which uses SFINAE to detect invalid code, we could practically not add anything to the standard.

英文:

A member function pointer must be invoked using the .* (or -&gt;*) syntax, so it can't be passed to a higher-order function:

#include &lt;vector&gt;

void for_each(auto const&amp; v, auto f) {
  for (auto const&amp; e : v)
    f(e);  // error: must use &#39;.*&#39; or &#39;-&gt;*&#39; to call pointer-to-member function in &#39;f (...)&#39;, e.g. &#39;(... -&gt;* f) (...)&#39;
}

struct Foo {
  void bar();
};

int main() {
  std::vector&lt;Foo&gt; v(10);
  for_each(v, &amp;Foo::bar);  // from here
}

The C++ Standard Library has two separate solutions to this: either I can use std::mem_fn() to get a free-function-like callable from a member function:

int main() {
  std::vector&lt;Foo&gt; v(10);
  for_each(v, std::mem_fn(&amp;Foo::bar));  // OK
}

Or I can augment the higher-order function to use std::invoke (std::for_each already does this) instead of invoking the callable directly:

void for_each(auto const&amp; v, auto f) {
  for (auto const&amp; e : v)
    std::invoke(f, e);  // OK
}

But, since the syntax (&amp;Foo::bar)(Foo{}) is invalid at the current time, couldn't the standard make it valid and equivalent to calling std::mem_fn() first on the &amp;Foo::bar?

Effectively, this would mean "absorbing" the std::mem_fn() utility in the language.

Would that be possible? Or, would it have undesired side effects? I can't see how it could break anything, considering that it's currently invalid syntax.


As I wrote the question, a possible answer came to my mind: SFINAE could be relying on that syntax being invalid.

It the following snippet, for instance, the second static_assert would fail if the standard started to allow calling (&amp;Foo::bar)(Foo{}):

#include &lt;type_traits&gt;
#include &lt;vector&gt;

struct Foo {
  void bar();
};

template&lt;typename F, typename = void&gt;
struct Trait : public std::false_type {};

template&lt;typename F&gt;
struct Trait&lt;F, std::void_t&lt;decltype(std::declval&lt;F&gt;()(std::declval&lt;Foo&gt;()))&gt;&gt;
    : public std::true_type {};

auto constexpr freeBar = [](Foo){};

int main() {
  static_assert(Trait&lt;decltype(freeBar)&gt;::value);
  static_assert(!Trait&lt;decltype(&amp;Foo::bar)&gt;::value);
}

However, in the comments to my delete self-answer it was pointed out that this cannot be a reason to prevent the standard from adopting the syntax I'm thinking about.

After all, and more in general, if we wanted not to break code which uses SFINAE to detect invalid code, we could practically not add anything to the standard.

答案1

得分: 1

把一个非静态成员函数视为带有额外对象参数的普通函数的想法(例如,像Python的C.f语法那样)当然已经提出过 多次 并且 提议,并且在这里也成为问题的主题。一个复杂之处在于指向成员的指针可以引用虚函数并调用最派生的函数;允许调用指向成员的指针可能会在第一个参数上执行动态调度,这将是新颖的(尽管没有“矛盾”任何东西)。

C++23实际上为不能是虚拟的显式对象成员函数提供了这个功能,通过为它们提供普通函数指针:

struct A {
  void f(this A&);
};
void (*fp)(A&)=A::f;     // OK
void (A::*pmf)()=&A::f;  // error: initializer has wrong type

与此同时,重要的是注意,语法(&Foo::bar)(Foo{})并非_显而易见_无效:C++20表示,对其进行重载解析,就好像它是f.bar(Foo{})的情况,其中f的类型为Foo ([over.match.call.general]/2)。当然,实际上无法进行此调用,因此假设它可以进行(可能触发具有一个以上参数的静态成员函数的模糊性)进行重载解析并不是很有帮助。此外,无论是GCC还是Clang都没有正确实现该重载解析(而是声称适用于over.over并失败),因此C++23更改了该语法的重载解析以与调用指向成员函数的指针的潜在(!)功能兼容。

所以(也许令人不满意)的答案是,在使这种变化起作用方面确实存在一些实际挑战,但在解决这些挑战方面也取得了一些进展。

英文:

The idea of treating a non-static member function as an ordinary function with the additional object parameter (as done, say, by Python's C.f syntax) has of course been proposed multiple times and has been the subject of questions here. One complication is that a pointer-to-member can refer to a virtual function and calls the most-derived function; allowing a pointer-to-member to be called would presumably perform dynamic dispatch on the first argument, which would be novel (albeit without "contradicting" anything).

C++23 actually provides this for explicit-object member functions that cannot be virtual by providing ordinary pointers to functions for them:

struct A {
  void f(this A&amp;);
};
void (*fp)(A&amp;)=A::f;     // OK
void (A::*pmf)()=&amp;A::f;  // error: initializer has wrong type

Meanwhile, it's important to note that the syntax (&amp;Foo::bar)(Foo{}) is not prima facie invalid: C++20 says that overload resolution proceeds for it as if it were f.bar(Foo{}) for some f of type Foo&nbsp;([over.match.call.general]/2). Of course, this call cannot actually be made, so performing overload resolution as if it could (potentially triggering ambiguities with static member functions with one more parameter) is not very helpful. Moreover, neither GCC nor Clang implements that overload resolution properly (instead claiming that [over.over] applies and fails), so C++23 changes overload resolution for that syntax to instead be compatible with the potential(!) feature of calling a pointer-to-member function.

So the (perhaps unsatisfying) answer is that there are real challenges in making such a change work, but there has also been some progress in addressing them.

huangapple
  • 本文由 发表于 2023年2月24日 03:54:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75549705.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定