4

In writing test code for this question I found that the commented line below does not compile on GCC 4.7.2:

#include <thread>
#include <iostream>

struct S {
    void f() {
        std::cout << "Calling f()" << std::endl;
    }
};

int main()
{
    S s;
    // std::thread t(&S::f, s); // does not compile?
    std::thread t(&S::f, &s);
    t.join();
}

But cppreference seems to claim that the "this" argument can be passed equivalently as an object, reference to object, or pointer to object:

If f is pointer to a member function of class T, then it is called. The return value is ignored. Effectively, the following code is executed: (t1.*f)(t2, ..., tN) if the type of t1 is either T, reference to T or reference to type derived from T. ((*t1).*f)(t2, ..., tN) otherwise.

I actually think this sounds terrible, and would prefer std::thread only allow either pointer or reference semantics instead of accepting them interchangeably, but given that it seems like it's supposed to, is the above a GCC/libstdc++ bug (or am I misinterpreting cppreference)?

Community
  • 1
  • 1
Stephen Lin
  • 5,470
  • 26
  • 48

1 Answers1

4

Seems like tonight it's GCC Bug Party :-)

Jokes aside, this is most certainly a bug. My answer to the linked question actually contains the proof, but since it is not emphasized, I will repeat it here.

This is how the INVOKE facility, in terms of which the behavior of std::thread's constructor (see the linked answer) is defined in the C++11 Standard

Define INVOKE (f, t1, t2, ..., tN) as follows:

(t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;

— ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;

— t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;

— (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;

— f(t1, t2, ..., tN) in all other cases.

The sentence in bold font effectively specifies that the line:

std::thread t(&S::f, s);

Should compile. Therefore, this qualifies as a bug.

Besides, that does line compile on GCC 4.8.0 (beta) and Clang 3.2.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Great, thanks! Now do you have any idea whose idea it was to allow interchangable reference and pointer semantics here? Obviously the intent is clear either way, but it's odd to me to allow both... – Stephen Lin Feb 26 '13 at 02:59
  • @StephenLin: Greater flexibility, I would say. You have two options rather than one. Another thing I can think of is that `operator *` and `operator ->*` can be overloaded, while `operator .` and `operator .*` cannot. – Andy Prowl Feb 26 '13 at 03:01
  • OK, that's irksome but I suppose I'll have to be content with that answer – Stephen Lin Feb 26 '13 at 03:05
  • Slight nitpick but the first line (referring to "pointer to a member function") should be bold, not the third (referring to "pointer to member data"), right? – Stephen Lin Feb 26 '13 at 03:07
  • The reason for allowing both forms is that to pass a temporary it needs to work with non-pointers (objects and references) but to work with smart pointers such as `shared_ptr` it needs to dereference the ptr-like thing. Supporting both those uses cases is important, so that the new thread (or call wrapper, or other type defined in terms of _INVOKE_) can take ownership of its arguments, so they don't go out of scope before they are needed. – Jonathan Wakely Mar 02 '13 at 01:35
  • This was fixed for GCC 4.8 by the patch for http://gcc.gnu.org/PR55463 but also needs to be fixed for 4.7, so I've created http://gcc.gnu.org/PR56505 – Jonathan Wakely Mar 02 '13 at 01:53
  • @JonathanWakely as a follow-up, do you know what's with INVOKE on a member data pointer returning the value of the data, rather than evaluating the data as a callable? To quote an earlier comment I made [here](http://stackoverflow.com/questions/15080015/stdthread-with-pointer-to-data-member) "If I bound f = std::mem_fn(&X::n) and then called f(&x), I would expect either an error or x.n(), not x.n...there might be a valid use case for binding to member data that way, but why not through something called std::mem_data?" – Stephen Lin Mar 02 '13 at 01:59
  • I've never needed that behaviour, whereas I often use `std::bind(std::pair::second, _1)` or similar constructs relying on the standard behaviour, and that's what the Boost components have done for many years without complaint – Jonathan Wakely Mar 02 '13 at 10:51
  • @JonathanWakely ok, I guess that makes sense – Stephen Lin Mar 04 '13 at 00:34