4

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?

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • 5
    You don't need it, you never needed it, in C or C++, in any version of either language from K&R C throughout to C++23. – n. m. could be an AI Jul 27 '23 at 08:05
  • Can you point me to the standardese that says that function name expressions are pointer to the function? – Superlokkus Jul 27 '23 at 08:08
  • 2
    You use `&foo` to get the address of the function. That is ok. If you don't take the address, a plain `foo` will decay to a pointer to the function. So really no difference. – BoP Jul 27 '23 at 08:08
  • Could you point me to that decay rule in the standard, if you would be so kind? :-) – Superlokkus Jul 27 '23 at 08:13
  • _function name expressions are pointer to the function_ They are not – Language Lawyer Jul 27 '23 at 08:20
  • @Superlokkus - This is something I learned in C about a 100 years ago. Functions and arrays are not like the other types, and turn themselves into pointers at the drop of a hat. Haven't looked into how C++ formulates it, sorry. – BoP Jul 27 '23 at 08:20
  • 1
    They are not. They decay to pointers to functions in certain contexts [ref](https://eel.is/c++draft/conv.func). The question was not about that though. It was about whether you need the `&`. You don't. – n. m. could be an AI Jul 27 '23 at 08:21
  • "This conversion never applies to non-static member functions because an lvalue that refers to a non-static member function cannot be obtained." I interpret that for non static member functions I still need the explicit &?! – Superlokkus Jul 27 '23 at 08:28
  • 1
    See [this](https://eel.is/c++draft/conv#general) and [this](https://eel.is/c++draft/conv#func). – chrysante Jul 27 '23 at 08:30
  • 1
    If you want to get a pointer to a non static member functions, then yes you need a `&`. – n. m. could be an AI Jul 27 '23 at 08:33
  • @n.m.willseey'allonReddit Or you need to start with a lambda expression (which I find preferable due to lambda capture syntax) – Pepijn Kramer Jul 27 '23 at 08:56
  • Well I wait 1-2 hours to give others a change to object or add missing standard pointers, after that, iff no sensible answer was posted, I will wrap up that with some examples in an answer myself. Thanks for the helpful comments @chrysante and n. m. will see y'all on Reddit But I knew that something made me always add an &. Now I know its only non static member functions. – Superlokkus Jul 27 '23 at 09:03
  • Do I read https://eel.is/c++draft/expr.prim.id.qual#5 right, that type of an expression (by the qualified name lookup) of a non static member function is not specified? (Unless as combined with the mentioned address of)? @n.m.willseey'allonReddit – Superlokkus Jul 27 '23 at 10:58
  • _The type of the expression is the type of the result_ — this is «the type is not specified»??? – Language Lawyer Jul 27 '23 at 11:00
  • Pardon my jargon: The value category of an expression of a non static member function is not specified? – Superlokkus Jul 27 '23 at 11:07
  • _and a prvalue otherwise_ ? – Language Lawyer Jul 27 '23 at 11:15
  • Ah didn't see the "and a prvalue otherwise." at the end of the page until now. Feel free to criticize my fresh answer @LanguageLawyer :-) – Superlokkus Jul 27 '23 at 16:38

1 Answers1

4

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:

  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& 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.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57