当引用函数时,我是否真的需要使用地址运算符?

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

Do I actually need the address of operator when referencing functions

问题

当回答另一个 Stack Overflow 的问题时,我将代码从

void foo();
...
std::thread t(foo);

改为了

std::thread t(&foo);

我对于为什么要添加 "&" 符号感到困惑。对我来说,添加取地址运算符 "&" 是一种习惯性动作。

在回答时,我突然想不起来为什么还需要这个 "&" 符号。然而,因为我模糊地记得这可能是你的编译器的一个非便携、非标准的好行为,所以我试图找到明确的证据。大部分都与 C 相关。至少有任何一个在 C++ 中真正需要使用 "&" 来引用函数的用例。

因为我理解中的 std::thread() 可能是一个糟糕的例子,因为它是一个带有 std::forward 的模板构造函数,我怀疑 decay-copy 正好给我一个指针。然而,由于在 C++23 中将更改为 auto std::forward(f),我不确定是否会再次出现问题。特别是因为 cppreference 上的引用:

> 函数不是对象:没有函数数组,函数不能按值传递或从其他函数返回。

通常我喜欢自己查阅 ISO/IEC 14882 中的标准文档,但我已经感到困惑,不知道从哪里开始。

简而言之:在 C++ 中是否还需要声明函数的地址?ISO 的规定是什么?我在哪里可以找到确切的规定?template< class Function, class... Args > explicit thread( Function&& f, Args&&... args ) 这方面的行为在 C++23 中会改变吗?

英文:

When answering another SO question, where I "corrected"

void foo();
...
std::thread t(foo);

to

std::thread t(&foo);

I stumbled about the question of "Why did you added the &?". For me it was an automatism to add the address of operator aka &/ampersand.

When formulating my answer I came up blank why I would need that ampersand anymore. However, because I remembered faintly this could be a non portable i.e. non standard nice behavior of your compiler gods, I was trying to find explicit proof. Most was just C related. At least any use case where I really need the & for function referencing in C++ anymore.

Because do my understanding std::thread() might be a bad example, as its a templated ctor, with that std::forward and I suspect that the decay-copy exactly gets me a pointer. However as this changes in C++23 to auto std::forward(f) I am unsure if then it might break again. Especially because of the quote of cppreference:

> Functions are not objects: there are no arrays of functions and functions cannot be passed by value or returned from other functions.

Usally I like to dig up standardese i.e. in the ISO/IEC 14882 myself, but I am already so confused, that I don't now where to start.

So in short: Do I need to state the address of functions anymore in C++? What's the ISO wording on it i.e. where can I find the exact ruling? Will the behavior of template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args )
change it that regard with C++23?

答案1

得分: 4

简短回答

在非静态成员函数的情况下,使用&/ampersand即地址运算符是明确必要的。对于所有其他函数,它是不需要的

示例

#include <iostream>
#include <thread>

void foo() {
    std::cout << 42 << "\n";
}

struct s {
    void nonStaticFunc() {
        std::cout << i << "\n";
    }
    int i{42};
};

int main() {
    s s1;
    void (s::*func_ptr)() = &s::nonStaticFunc;
    //void (s::*func_ptr2)() = s::nonStaticFunc;//error
    void (*func_pt3)() = foo;
    std::thread t(func_ptr, s1);
    std::thread t2(&s::nonStaticFunc, s1);
    //std::thread t3(s::nonStaticFunc, s1);//error
    std::thread t4(foo);
    t.join();
    t2.join();
    t4.join();
}

C++标准解释 ISO/IEC 14882 推理

从标识符到指针的转换

ISO/IEC 14882:2017 在 7 Standard conversions [conv] 中指出:

> 标准转换是具有内置含义的隐式转换。第7条列举了这些转换的完整集合。标准转换序列是按照以下顺序的标准转换序列:
... -- 零个或一个函数指针转换。

然后在 7.3 Function-to-pointer conversion [conv.func] 中指出:

> 1 函数类型 T 的左值可以转换为类型为“指向 T 的指针”的纯右值。结果是指向该函数的指针。59
2 [ 注意:有关函数重载的情况的附加规则,请参见 16.4。— 结束注意 ]

然而,草案更有帮助:

> 函数类型 T 的左值可以转换为类型为“指向 T 的指针”的纯右值。结果是指向该函数的指针。47
> 47) 此转换永远不适用于非静态成员函数,因为无法获得引用非静态成员函数的左值。

查看 2017 年官方 ISO 的注释,即重载查找没有给我带来任何见解,但是在非官方草案的注释中发现:我们无法获得常规成员函数的lvalue

那么为什么不能将lvalue转换为非静态成员函数

对于一个 C++ 语言律师来说,我对标准的了解只是初学者级别的,注意标准术语即标准语言是这样写的:this。我的推理如下:foosnonStaticFunc 只是标识符。然后当我们使用它们来分配函数指针时,我们有:

  1. foo 的情况下,即独立的函数,有一个未限定名称 [expr.prim.id.unqual]。
  2. s::nonStaticFunc 的情况下,即非静态成员函数,有一个限定名称 [expr.prim.id.qual]。

在第2种情况下,2017 年的 ISO 在 8.1.4.2.2 中指出:

> 表示类的嵌套名指示符,可选择后跟关键字 template(17.2),然后跟该类(12.2)或其基类(第13条)的成员的名称,是限定标识符;6.4.3.1 描述了出现在限定标识符中的类成员的名称查找。结果是成员。结果的类型是成员的类型。如果成员是静态成员函数或数据成员,则结果是左值;否则是纯右值。

在这里查看草案版本

结论

因此,我们只能在 ISO 2017 标准中获得表达式类别 [basic.lval],即最新草案中的值类别 [basic.lval](它是 [expr.prim.id] 的子章节,在最新草案中已从 6. basic 移动)的纯右值,对于 s::nonStaticFunc。除非我们明确使用一元&amp;运算符,如 最新草案中的 [expr.prim.id.qual](5.2) 明确说明的那样。我想知道它是否包括 std::addressof。然而,没有lvalue,没有标准转换,因此需要&amp;。我从 clang 得到的错误提示表明,否则 C++ 作为一个形式语言将会过于模糊和难以解析给编译器编写者。

预期中的 std::thread 构造函数与此问题无关。

英文:

Short Answer

The &/ampersand i.e. address of operator is explicitly necessary in case of non static member functions. It is not needed for all other functions.


Example

#include &lt;iostream&gt;
#include &lt;thread&gt;

void foo() {
    std::cout &lt;&lt; 42 &lt;&lt; &quot;\n&quot;;
}

struct s {
    void nonStaticFunc() {
        std::cout &lt;&lt; i &lt;&lt; &quot;\n&quot;;
    }
    int i{42};
};

int main() {
    s s1;
    void (s::*func_ptr)() = &amp;s::nonStaticFunc;
    //void (s::*func_ptr2)() = s::nonStaticFunc;//error
    void (*func_pt3)() = foo;
    std::thread t(func_ptr, s1);
    std::thread t2(&amp;s::nonStaticFunc, s1);
    //std::thread t3(s::nonStaticFunc, s1);//error
    std::thread t4(foo);
    t.join();
    t2.join();
    t4.join();
}

C++ Standardese ISO/IEC 14882 reasoning##

Conversion from identifier to pointer

ISO/IEC 14882:2017 states in 7 Standard conversions [conv]

> Standard conversions are implicit conversions with built-in meaning. Clause 7 enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:
... --Zero or one function pointer conversion.

and then in 7.3 Function-to-pointer conversion [conv.func]

> 1 An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.59
2 [ Note: See 16.4 for additional rules for the case where the function is overloaded. — end note ]

However never drafts are more helpful here:

> An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.47
> 47) This conversion never applies to non-static member functions because an lvalue that refers to a non-static member function cannot be obtained.

Looking into the note of the 2017 official iso i.e. the overload lookup did not bring me any insight, but the note of the unofficial draft: Seems we can not get an lvalue to a regular member function.

So why not lvalue to a non-static member function

For a C++ language lawyer my knowledge of the Standard is only that of a beginner, note that standard terms aka standardese is written like this. My reasoning is the following: foo, s and nonStaticFunc are just identifiers. Then when using them to assign a function pointer for example we have:

  1. a Unqualified name(s) [expr.prim.id.unqual] in case of foo i.e. the stand alone function
  2. a Qualified names() [expr.prim.id.qual] in case of s::nonStaticFunc i.e. a non-static member functions

In case of 2. the 2017 ISO states in 8.1.4.2.2:

> A nested-name-specifier that denotes a class, optionally followed by the keyword template (17.2), and then followed by the name of a member of either that class (12.2) or one of its base classes (Clause 13), is a qualified-id; 6.4.3.1 describes name lookup for class members that appear in qualified-ids. The result is the member. The type of the result is the type of the member. The result is an lvalue if the member is a static member function or a data member and a prvalue otherwise.

See here for the draft version

Conclusion

So we only get a Expression category [basic.lval] in ISO 2017 standardese i.e. Value category [basic.lval] (which is sub chapter of [expr.prim.id] in the newest draft since it moved from 6. basic) of prvalue for s::nonStaticFunc. Except if we explicitly use the unary&amp; operator as the latest draft in [expr.prim.id.qual] (5.2) explicitly states. Which I would wonder if it includes std::addressof. However, no lvalue no Standard conversions, hence the &amp; is needed. The errors I get from clang suggests to me that otherwise the C++ as a formal language would otherwise be too ambiguous and hard to parse for compiler writes.

The std::thread ctor as expected is not relevant to this pickle.

huangapple
  • 本文由 发表于 2023年7月27日 15:58:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76777630.html
匿名

发表评论

匿名网友

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

确定