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 <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++ 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:
- a Unqualified name(s) [expr.prim.id.unqual] in case of
foo
i.e. the stand alone function
- 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&
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 &
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.